1 // Copyright 2008-present Contributors to the OpenImageIO project.
2 // SPDX-License-Identifier: BSD-3-Clause
3 // https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
4 #include "sgi_pvt.h"
5 #include <OpenImageIO/dassert.h>
6 #include <OpenImageIO/fmath.h>
7 
8 OIIO_PLUGIN_NAMESPACE_BEGIN
9 
10 // Obligatory material to make this a recognizeable imageio plugin:
11 OIIO_PLUGIN_EXPORTS_BEGIN
12 
13 OIIO_EXPORT int sgi_imageio_version = OIIO_PLUGIN_VERSION;
14 
15 OIIO_EXPORT const char*
sgi_imageio_library_version()16 sgi_imageio_library_version()
17 {
18     return nullptr;
19 }
20 
21 OIIO_EXPORT ImageInput*
sgi_input_imageio_create()22 sgi_input_imageio_create()
23 {
24     return new SgiInput;
25 }
26 
27 OIIO_EXPORT const char* sgi_input_extensions[] = { "sgi", "rgb",  "rgba", "bw",
28                                                    "int", "inta", nullptr };
29 
30 OIIO_PLUGIN_EXPORTS_END
31 
32 
33 
34 bool
valid_file(const std::string & filename) const35 SgiInput::valid_file(const std::string& filename) const
36 {
37     FILE* fd = Filesystem::fopen(filename, "rb");
38     if (!fd)
39         return false;
40     int16_t magic;
41     bool ok = (::fread(&magic, sizeof(magic), 1, fd) == 1)
42               && (magic == sgi_pvt::SGI_MAGIC);
43     fclose(fd);
44     return ok;
45 }
46 
47 
48 
49 bool
open(const std::string & name,ImageSpec & spec)50 SgiInput::open(const std::string& name, ImageSpec& spec)
51 {
52     // saving name for later use
53     m_filename = name;
54 
55     m_fd = Filesystem::fopen(m_filename, "rb");
56     if (!m_fd) {
57         errorf("Could not open file \"%s\"", name);
58         return false;
59     }
60 
61     if (!read_header())
62         return false;
63 
64     if (m_sgi_header.magic != sgi_pvt::SGI_MAGIC) {
65         errorf("\"%s\" is not a SGI file, magic number doesn't match",
66                m_filename);
67         close();
68         return false;
69     }
70 
71     int height    = 0;
72     int nchannels = 0;
73     switch (m_sgi_header.dimension) {
74     case sgi_pvt::ONE_SCANLINE_ONE_CHANNEL:
75         height    = 1;
76         nchannels = 1;
77         break;
78     case sgi_pvt::MULTI_SCANLINE_ONE_CHANNEL:
79         height    = m_sgi_header.ysize;
80         nchannels = 1;
81         break;
82     case sgi_pvt::MULTI_SCANLINE_MULTI_CHANNEL:
83         height    = m_sgi_header.ysize;
84         nchannels = m_sgi_header.zsize;
85         break;
86     default:
87         errorf("Bad dimension: %d", m_sgi_header.dimension);
88         close();
89         return false;
90     }
91 
92     if (m_sgi_header.colormap == sgi_pvt::COLORMAP
93         || m_sgi_header.colormap == sgi_pvt::SCREEN) {
94         errorf("COLORMAP and SCREEN color map types aren't supported");
95         close();
96         return false;
97     }
98 
99     m_spec = ImageSpec(m_sgi_header.xsize, height, nchannels,
100                        m_sgi_header.bpc == 1 ? TypeDesc::UINT8
101                                              : TypeDesc::UINT16);
102     if (strlen(m_sgi_header.imagename))
103         m_spec.attribute("ImageDescription", m_sgi_header.imagename);
104 
105     if (m_sgi_header.storage == sgi_pvt::RLE) {
106         m_spec.attribute("compression", "rle");
107         if (!read_offset_tables())
108             return false;
109     }
110 
111     spec = m_spec;
112     return true;
113 }
114 
115 
116 
117 bool
read_native_scanline(int subimage,int miplevel,int y,int,void * data)118 SgiInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/,
119                                void* data)
120 {
121     lock_guard lock(m_mutex);
122     if (!seek_subimage(subimage, miplevel))
123         return false;
124 
125     if (y < 0 || y > m_spec.height)
126         return false;
127 
128     y = m_spec.height - y - 1;
129 
130     ptrdiff_t bpc = m_sgi_header.bpc;
131     std::vector<std::vector<unsigned char>> channeldata(m_spec.nchannels);
132     if (m_sgi_header.storage == sgi_pvt::RLE) {
133         // reading and uncompressing first channel (red in RGBA images)
134         for (int c = 0; c < m_spec.nchannels; ++c) {
135             // offset for this scanline/channel
136             ptrdiff_t off             = y + c * m_spec.height;
137             ptrdiff_t scanline_offset = start_tab[off];
138             ptrdiff_t scanline_length = length_tab[off];
139             channeldata[c].resize(m_spec.width * bpc);
140             uncompress_rle_channel(scanline_offset, scanline_length,
141                                    &(channeldata[c][0]));
142         }
143     } else {
144         // non-RLE case -- just read directly into our channel data
145         for (int c = 0; c < m_spec.nchannels; ++c) {
146             // offset for this scanline/channel
147             ptrdiff_t off             = y + c * m_spec.height;
148             ptrdiff_t scanline_offset = sgi_pvt::SGI_HEADER_LEN
149                                         + off * m_spec.width * bpc;
150             Filesystem::fseek(m_fd, scanline_offset, SEEK_SET);
151             channeldata[c].resize(m_spec.width * bpc);
152             if (!fread(&(channeldata[c][0]), 1, m_spec.width * bpc))
153                 return false;
154         }
155     }
156 
157     if (m_spec.nchannels == 1) {
158         // If just one channel, no interleaving is necessary, just memcpy
159         memcpy(data, &(channeldata[0][0]), channeldata[0].size());
160     } else {
161         unsigned char* cdata = (unsigned char*)data;
162         for (int x = 0; x < m_spec.width; ++x) {
163             for (int c = 0; c < m_spec.nchannels; ++c) {
164                 *cdata++ = channeldata[c][x * bpc];
165                 if (bpc == 2)
166                     *cdata++ = channeldata[c][x * bpc + 1];
167             }
168         }
169     }
170 
171     // Swap endianness if needed
172     if (bpc == 2 && littleendian())
173         swap_endian((unsigned short*)data, m_spec.width * m_spec.nchannels);
174 
175     return true;
176 }
177 
178 
179 
180 bool
uncompress_rle_channel(int scanline_off,int scanline_len,unsigned char * out)181 SgiInput::uncompress_rle_channel(int scanline_off, int scanline_len,
182                                  unsigned char* out)
183 {
184     int bpc = m_sgi_header.bpc;
185     std::unique_ptr<unsigned char[]> rle_scanline(
186         new unsigned char[scanline_len]);
187     Filesystem::fseek(m_fd, scanline_off, SEEK_SET);
188     if (!fread(&rle_scanline[0], 1, scanline_len))
189         return false;
190     int limit = m_spec.width;
191     int i     = 0;
192     if (bpc == 1) {
193         // 1 bit per channel
194         while (i < scanline_len) {
195             // Read a byte, it is the count.
196             unsigned char value = rle_scanline[i++];
197             int count           = value & 0x7F;
198             // If the count is zero, we're done
199             if (!count)
200                 break;
201             // If the high bit is set, we just copy the next 'count' values
202             if (value & 0x80) {
203                 while (count--) {
204                     OIIO_DASSERT(i < scanline_len && limit > 0);
205                     *(out++) = rle_scanline[i++];
206                     --limit;
207                 }
208             }
209             // If the high bit is zero, we copy the NEXT value, count times
210             else {
211                 value = rle_scanline[i++];
212                 while (count--) {
213                     OIIO_DASSERT(limit > 0);
214                     *(out++) = value;
215                     --limit;
216                 }
217             }
218         }
219     } else if (bpc == 2) {
220         // 2 bits per channel
221         while (i < scanline_len) {
222             // Read a byte, it is the count.
223             unsigned short value = (rle_scanline[i] << 8) | rle_scanline[i + 1];
224             i += 2;
225             int count = value & 0x7F;
226             // If the count is zero, we're done
227             if (!count)
228                 break;
229             // If the high bit is set, we just copy the next 'count' values
230             if (value & 0x80) {
231                 while (count--) {
232                     OIIO_DASSERT(i + 1 < scanline_len && limit > 0);
233                     *(out++) = rle_scanline[i++];
234                     *(out++) = rle_scanline[i++];
235                     --limit;
236                 }
237             }
238             // If the high bit is zero, we copy the NEXT value, count times
239             else {
240                 while (count--) {
241                     OIIO_DASSERT(limit > 0);
242                     *(out++) = rle_scanline[i];
243                     *(out++) = rle_scanline[i + 1];
244                     --limit;
245                 }
246                 i += 2;
247             }
248         }
249     } else {
250         errorf("Unknown bytes per channel %d", bpc);
251         return false;
252     }
253     if (i != scanline_len || limit != 0) {
254         errorf("Corrupt RLE data");
255         return false;
256     }
257 
258     return true;
259 }
260 
261 
262 
263 bool
close()264 SgiInput::close()
265 {
266     if (m_fd)
267         fclose(m_fd);
268     init();
269     return true;
270 }
271 
272 
273 
274 bool
read_header()275 SgiInput::read_header()
276 {
277     if (!fread(&m_sgi_header.magic, sizeof(m_sgi_header.magic), 1)
278         || !fread(&m_sgi_header.storage, sizeof(m_sgi_header.storage), 1)
279         || !fread(&m_sgi_header.bpc, sizeof(m_sgi_header.bpc), 1)
280         || !fread(&m_sgi_header.dimension, sizeof(m_sgi_header.dimension), 1)
281         || !fread(&m_sgi_header.xsize, sizeof(m_sgi_header.xsize), 1)
282         || !fread(&m_sgi_header.ysize, sizeof(m_sgi_header.ysize), 1)
283         || !fread(&m_sgi_header.zsize, sizeof(m_sgi_header.zsize), 1)
284         || !fread(&m_sgi_header.pixmin, sizeof(m_sgi_header.pixmin), 1)
285         || !fread(&m_sgi_header.pixmax, sizeof(m_sgi_header.pixmax), 1)
286         || !fread(&m_sgi_header.dummy, sizeof(m_sgi_header.dummy), 1)
287         || !fread(&m_sgi_header.imagename, sizeof(m_sgi_header.imagename), 1))
288         return false;
289 
290     m_sgi_header.imagename[79] = '\0';
291     if (!fread(&m_sgi_header.colormap, sizeof(m_sgi_header.colormap), 1))
292         return false;
293 
294     //don't read dummy bytes
295     Filesystem::fseek(m_fd, 404, SEEK_CUR);
296 
297     if (littleendian()) {
298         swap_endian(&m_sgi_header.magic);
299         swap_endian(&m_sgi_header.dimension);
300         swap_endian(&m_sgi_header.xsize);
301         swap_endian(&m_sgi_header.ysize);
302         swap_endian(&m_sgi_header.zsize);
303         swap_endian(&m_sgi_header.pixmin);
304         swap_endian(&m_sgi_header.pixmax);
305         swap_endian(&m_sgi_header.colormap);
306     }
307     return true;
308 }
309 
310 
311 
312 bool
read_offset_tables()313 SgiInput::read_offset_tables()
314 {
315     int tables_size = m_sgi_header.ysize * m_sgi_header.zsize;
316     start_tab.resize(tables_size);
317     length_tab.resize(tables_size);
318     if (!fread(&start_tab[0], sizeof(uint32_t), tables_size)
319         || !fread(&length_tab[0], sizeof(uint32_t), tables_size))
320         return false;
321 
322     if (littleendian()) {
323         swap_endian(&length_tab[0], length_tab.size());
324         swap_endian(&start_tab[0], start_tab.size());
325     }
326     return true;
327 }
328 
329 OIIO_PLUGIN_NAMESPACE_END
330