@@ -26,12 +26,19 @@ class SgiOutput final : public ImageOutput {
2626 std::string m_filename;
2727 std::vector<unsigned char > m_scratch;
2828 unsigned int m_dither;
29- std::vector<unsigned char > m_tilebuffer;
29+ bool m_want_rle;
30+ std::vector<unsigned char > m_uncompressed_image;
3031
3132 void init () { ioproxy_clear (); }
3233
3334 bool create_and_write_header ();
3435
36+ bool write_scanline_raw (int y, const unsigned char * data);
37+ bool write_scanline_rle (int y, const unsigned char * data, int64_t & offset,
38+ std::vector<int >& start_table,
39+ std::vector<int >& length_table);
40+ bool write_buffered_pixels ();
41+
3542 // / Helper - write, with error detection
3643 template <class T >
3744 bool fwrite (const T* buf, size_t itemsize = sizeof (T), size_t nitems = 1)
@@ -85,10 +92,12 @@ SgiOutput::open(const std::string& name, const ImageSpec& spec, OpenMode mode)
8592 ? m_spec.get_int_attribute (" oiio:dither" , 0 )
8693 : 0 ;
8794
95+ m_want_rle = m_spec.get_string_attribute (" compression" ) == " rle" ;
96+
8897 // If user asked for tiles -- which this format doesn't support, emulate
89- // it by buffering the whole image.
90- if (m_spec.tile_width && m_spec.tile_height )
91- m_tilebuffer .resize (m_spec.image_bytes ());
98+ // it by buffering the whole image. RLE is treated similarly.
99+ if (m_want_rle || ( m_spec.tile_width && m_spec.tile_height ) )
100+ m_uncompressed_image .resize (m_spec.image_bytes ());
92101
93102 return create_and_write_header ();
94103}
@@ -102,32 +111,57 @@ SgiOutput::write_scanline(int y, int z, TypeDesc format, const void* data,
102111 y = m_spec.height - y - 1 ;
103112 data = to_native_scanline (format, data, xstride, m_scratch, m_dither, y, z);
104113
114+ // If we are writing RLE data, just copy into the uncompressed buffer
115+ if (m_want_rle) {
116+ const auto scaneline_size = m_spec.scanline_bytes ();
117+ memcpy (&m_uncompressed_image[y * scaneline_size], data, scaneline_size);
118+
119+ return true ;
120+ }
121+
122+ return write_scanline_raw (y, (const unsigned char *)data);
123+ }
124+
125+
126+
127+ bool
128+ SgiOutput::write_tile (int x, int y, int z, TypeDesc format, const void * data,
129+ stride_t xstride, stride_t ystride, stride_t zstride)
130+ {
131+ // Emulate tiles by buffering the whole image
132+ return copy_tile_to_image_buffer (x, y, z, format, data, xstride, ystride,
133+ zstride, &m_uncompressed_image[0 ]);
134+ }
135+
136+
137+
138+ bool
139+ SgiOutput::write_scanline_raw (int y, const unsigned char * data)
140+ {
105141 // In SGI format all channels are saved to file separately: first, all
106142 // channel 1 scanlines are saved, then all channel2 scanlines are saved
107143 // and so on.
108- //
109- // Note that since SGI images are pretty archaic and most probably
110- // people won't be too picky about full flexibility writing them, we
111- // content ourselves with only writing uncompressed data, and don't
112- // attempt to write with RLE encoding.
113144
114145 size_t bpc = m_spec.format .size (); // bytes per channel
115146 std::unique_ptr<unsigned char []> channeldata (
116147 new unsigned char [m_spec.width * bpc]);
117148
118149 for (int64_t c = 0 ; c < m_spec.nchannels ; ++c) {
119- unsigned char * cdata = ( unsigned char *) data + c * bpc;
150+ const unsigned char * cdata = data + c * bpc;
120151 for (int64_t x = 0 ; x < m_spec.width ; ++x) {
121152 channeldata[x * bpc] = cdata[0 ];
122153 if (bpc == 2 )
123154 channeldata[x * bpc + 1 ] = cdata[1 ];
124155 cdata += m_spec.nchannels * bpc; // advance to next pixel
125156 }
157+
126158 if (bpc == 2 && littleendian ())
127159 swap_endian ((unsigned short *)&channeldata[0 ], m_spec.width );
160+
128161 ptrdiff_t scanline_offset = sgi_pvt::SGI_HEADER_LEN
129162 + ptrdiff_t (c * m_spec.height + y)
130163 * m_spec.width * bpc;
164+
131165 ioseek (scanline_offset);
132166 if (!iowrite (&channeldata[0 ], 1 , m_spec.width * bpc)) {
133167 return false ;
@@ -139,13 +173,179 @@ SgiOutput::write_scanline(int y, int z, TypeDesc format, const void* data,
139173
140174
141175
176+ static bool
177+ data_equals (const unsigned char * data, int bpc, imagesize_t off1,
178+ imagesize_t off2)
179+ {
180+ if (bpc == 1 ) {
181+ return data[off1] == data[off2];
182+ } else {
183+ return data[off1] == data[off2] && data[off1 + 1 ] == data[off2 + 1 ];
184+ }
185+ }
186+
187+
188+
189+ static void
190+ data_set (unsigned char * data, int bpc, imagesize_t off,
191+ const unsigned char * val)
192+ {
193+ if (bpc == 1 ) {
194+ data[off] = val[0 ];
195+ } else {
196+ data[off] = val[1 ];
197+ data[off + 1 ] = val[0 ];
198+ }
199+ }
200+
201+
202+
203+ static void
204+ data_set (unsigned char * data, int bpc, imagesize_t off, const short val)
205+ {
206+ if (bpc == 1 ) {
207+ data[off] = static_cast <unsigned char >(val);
208+ } else {
209+ data[off] = static_cast <unsigned char >(val >> 8 );
210+ data[off + 1 ] = static_cast <unsigned char >(val & 0xFF );
211+ }
212+ }
213+
214+
215+
142216bool
143- SgiOutput::write_tile (int x, int y, int z, TypeDesc format, const void * data,
144- stride_t xstride, stride_t ystride, stride_t zstride)
217+ SgiOutput::write_scanline_rle (int y, const unsigned char * data, int64_t & offset,
218+ std::vector<int >& offset_table,
219+ std::vector<int >& length_table)
145220{
146- // Emulate tiles by buffering the whole image
147- return copy_tile_to_image_buffer (x, y, z, format, data, xstride, ystride,
148- zstride, &m_tilebuffer[0 ]);
221+ const size_t bpc = m_spec.format .size (); // bytes per channel
222+ const size_t xstride = m_spec.nchannels * bpc;
223+ const imagesize_t scanline_bytes = m_spec.scanline_bytes ();
224+
225+ // Account for the worst case length when every pixel is different
226+ m_scratch.resize (bpc * (m_spec.width + (m_spec.width / 127 + 2 )));
227+
228+ for (int64_t c = 0 ; c < m_spec.nchannels ; ++c) {
229+ const unsigned char * cdata = data + c * bpc;
230+
231+ imagesize_t out = 0 ;
232+ imagesize_t pos = 0 ;
233+ while (pos < scanline_bytes) {
234+ imagesize_t start = pos;
235+ // Find the first run meeting a minimum length of 3
236+ imagesize_t ahead_1 = pos + xstride;
237+ imagesize_t ahead_2 = pos + xstride * 2 ;
238+ while (ahead_2 < scanline_bytes
239+ && (!data_equals (cdata, bpc, ahead_1, ahead_2)
240+ || !data_equals (cdata, bpc, pos, ahead_1))) {
241+ pos += xstride;
242+ ahead_1 += xstride;
243+ ahead_2 += xstride;
244+ }
245+ if (ahead_2 >= scanline_bytes) {
246+ // No more runs, just dump the rest as literals
247+ pos = scanline_bytes;
248+ }
249+ int count = int ((pos - start) / xstride);
250+ while (count) {
251+ int todo = (count > 127 ) ? 127 : count;
252+ count -= todo;
253+ data_set (m_scratch.data (), bpc, out, 0x80 | todo);
254+ out += bpc;
255+ while (todo) {
256+ data_set (m_scratch.data (), bpc, out, cdata + start);
257+ out += bpc;
258+ start += xstride;
259+ todo -= 1 ;
260+ }
261+ }
262+ start = pos;
263+ if (start >= scanline_bytes)
264+ break ;
265+ pos += xstride;
266+ while (pos < scanline_bytes
267+ && data_equals (cdata, bpc, start, pos)) {
268+ pos += xstride;
269+ }
270+ count = int ((pos - start) / xstride);
271+ while (count) {
272+ int curr_run = (count > 127 ) ? 127 : count;
273+ count -= curr_run;
274+ data_set (m_scratch.data (), bpc, out, curr_run);
275+ out += bpc;
276+ data_set (m_scratch.data (), bpc, out, cdata + start);
277+ out += bpc;
278+ }
279+ }
280+ data_set (m_scratch.data (), bpc, out, short (0 ));
281+ out += bpc;
282+
283+ // Fill in details about the scanline
284+ const int table_index = c * m_spec.height + y;
285+ offset_table[table_index] = static_cast <int >(offset);
286+ length_table[table_index] = static_cast <int >(out);
287+
288+ // Write the compressed data
289+ if (!iowrite (&m_scratch[0 ], 1 , out))
290+ return false ;
291+ offset += out;
292+ }
293+
294+ return true ;
295+ }
296+
297+
298+
299+ bool
300+ SgiOutput::write_buffered_pixels ()
301+ {
302+ OIIO_ASSERT (m_uncompressed_image.size ());
303+
304+ const auto scanline_bytes = m_spec.scanline_bytes ();
305+ if (m_want_rle) {
306+ // Prepare RLE tables
307+ const int64_t table_size = m_spec.height * m_spec.nchannels ;
308+ const int64_t table_size_bytes = table_size * sizeof (int );
309+ std::vector<int > offset_table;
310+ std::vector<int > length_table;
311+ offset_table.resize (table_size);
312+ length_table.resize (table_size);
313+
314+ // Skip over the tables and start at the data area
315+ int64_t offset = sgi_pvt::SGI_HEADER_LEN + 2 * table_size_bytes;
316+ ioseek (offset);
317+
318+ // Write RLE compressed data
319+ for (int y = 0 ; y < m_spec.height ; ++y) {
320+ const unsigned char * scanline_data
321+ = &m_uncompressed_image[y * scanline_bytes];
322+ if (!write_scanline_rle (y, scanline_data, offset, offset_table,
323+ length_table))
324+ return false ;
325+ }
326+
327+ // Write the tables now that they're filled in with offsets/lengths
328+ ioseek (sgi_pvt::SGI_HEADER_LEN);
329+ if (littleendian ()) {
330+ swap_endian (&offset_table[0 ], table_size);
331+ swap_endian (&length_table[0 ], table_size);
332+ }
333+ if (!iowrite (&offset_table[0 ], 1 , table_size_bytes))
334+ return false ;
335+ if (!iowrite (&length_table[0 ], 1 , table_size_bytes))
336+ return false ;
337+
338+ } else {
339+ // Write raw data
340+ for (int y = 0 ; y < m_spec.height ; ++y) {
341+ unsigned char * scanline_data
342+ = &m_uncompressed_image[y * scanline_bytes];
343+ if (!write_scanline_raw (y, scanline_data))
344+ return false ;
345+ }
346+ }
347+
348+ return true ;
149349}
150350
151351
@@ -160,15 +360,22 @@ SgiOutput::close()
160360
161361 bool ok = true ;
162362 if (m_spec.tile_width ) {
163- // Handle tile emulation -- output the buffered pixels
164- OIIO_ASSERT (m_tilebuffer .size ());
363+ // We've been emulating tiles; now dump as scanlines.
364+ OIIO_ASSERT (m_uncompressed_image .size ());
165365 ok &= write_scanlines (m_spec.y , m_spec.y + m_spec.height , 0 ,
166- m_spec.format , &m_tilebuffer[0 ]);
167- m_tilebuffer.clear ();
168- m_tilebuffer.shrink_to_fit ();
366+ m_spec.format , &m_uncompressed_image[0 ]);
169367 }
170368
369+ // If we want RLE encoding or we were tiled, output all the processed scanlines now.
370+ if (ok && (m_want_rle || m_spec.tile_width )) {
371+ ok &= write_buffered_pixels ();
372+ }
373+
374+ m_uncompressed_image.clear ();
375+ m_uncompressed_image.shrink_to_fit ();
376+
171377 init ();
378+
172379 return ok;
173380}
174381
@@ -179,7 +386,7 @@ SgiOutput::create_and_write_header()
179386{
180387 sgi_pvt::SgiHeader sgi_header;
181388 sgi_header.magic = sgi_pvt::SGI_MAGIC;
182- sgi_header.storage = sgi_pvt::VERBATIM;
389+ sgi_header.storage = m_want_rle ? sgi_pvt::RLE : sgi_pvt::VERBATIM;
183390 sgi_header.bpc = m_spec.format .size ();
184391
185392 if (m_spec.height == 1 && m_spec.nchannels == 1 )
0 commit comments