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