Made Unix builds more likely to be Y2038-compliant
[deark.git] / modules / sgiimage.c
blob7943b6188e0d2ce6ca838ae893fa78000fa28e82
1 // This file is part of Deark.
2 // Copyright (C) 2022 Jason Summers
3 // See the file COPYING for terms of use.
5 // SGI image / RGB / IRIS
7 #include <deark-private.h>
8 DE_DECLARE_MODULE(de_module_sgiimage);
10 #define CMI_NORMAL 0
11 #define CMI_RGB332 1
12 #define CMI_PALETTED 2
13 #define CMI_PALETTE_ONLY 3
15 struct sgiimage_offsettab_entry {
16 u32 pos;
17 u32 len;
20 struct sgiimage_ctx {
21 de_encoding input_encoding;
22 u8 storage_fmt;
23 UI bytes_per_sample;
24 UI dimension_count;
25 u32 pix_min, pix_max;
26 u32 colormap_id; // CMI_*
27 i64 width;
28 i64 height;
29 i64 num_channels;
31 u8 is_grayscale;
32 u8 has_alpha;
33 u8 warned_bad_offset;
34 i64 rowspan;
35 i64 num_scanlines;
36 i64 total_unc_size;
37 de_ucstring *name;
40 static const char *get_cmprtype_name(u8 n)
42 const char *name = NULL;
44 switch(n) {
45 case 0: name = "none"; break;
46 case 1: name = "RLE"; break;
48 return name?name:"?";
51 static const char *get_cmi_name(u32 n)
53 const char *name = NULL;
55 switch(n) {
56 case CMI_NORMAL: name = "normal image"; break;
57 case CMI_RGB332: name = "RGB332"; break;
58 case CMI_PALETTED: name = "paletted"; break;
59 case CMI_PALETTE_ONLY: name = "palette only"; break;
61 return name?name:"?";
64 static UI sgiimage_getsample_p(struct sgiimage_ctx *d, dbuf *f, i64 *ppos)
66 UI s;
68 if(d->bytes_per_sample==2) {
69 s = (UI)dbuf_getu16be_p(f, ppos);
71 else {
72 s = dbuf_getbyte_p(f, ppos);
74 return s;
77 static void sgiimage_decode_image(deark *c, struct sgiimage_ctx *d,
78 dbuf *inf, i64 pos1, de_bitmap *img, de_bitmap *imglo)
80 i64 pos = pos1;
81 i64 pn;
82 i64 i, j;
84 for(pn=0; pn<d->num_channels; pn++) {
85 i64 samplenum;
87 if(d->has_alpha && pn==(d->num_channels-1))
88 samplenum = 3;
89 else
90 samplenum = pn;
92 for(j=0; j<d->height; j++) {
93 for(i=0; i<d->width; i++) {
94 UI s;
96 s = sgiimage_getsample_p(d, inf, &pos);
98 if(imglo) {
99 de_bitmap_setsample(img, i, j, samplenum, (de_colorsample)(s>>8));
100 de_bitmap_setsample(imglo, i, j, samplenum, (s&0xff));
102 else {
103 de_bitmap_setsample(img, i, j, samplenum, (de_colorsample)s);
110 static void sgiimage_decompress_rle_scanline(deark *c,
111 struct sgiimage_ctx *d, i64 scanline_num, i64 pos1, i64 len,
112 dbuf *unc_pixels)
114 i64 curpos;
115 i64 endpos;
116 i64 num_dcmpr_bytes = 0;
118 curpos = pos1;
119 endpos = pos1 + len;
121 // Some files seem to set the offset to 0 for a nonexistent alpha channel.
122 if(pos1 < 20) {
123 if(!d->warned_bad_offset) {
124 de_warn(c, "Bad offset at scanline %"I64_FMT": %"I64_FMT,
125 scanline_num, pos1);
126 d->warned_bad_offset = 1;
128 goto done;
131 while(1) {
132 UI n;
133 i64 count;
135 if(curpos >= endpos) break; // end of input
136 if(num_dcmpr_bytes >= d->rowspan) break; // sufficient output
138 n = sgiimage_getsample_p(d, c->infile, &curpos);
139 count = (i64)(n & 0x7f);
140 if(count==0) break;
141 if(n & 0x80) { // noncompressed run
142 dbuf_copy(c->infile, curpos, count*d->bytes_per_sample, unc_pixels);
143 curpos += count*d->bytes_per_sample;
145 else { // RLE run
146 UI n2;
148 n2 = sgiimage_getsample_p(d, c->infile, &curpos);
149 if(d->bytes_per_sample==2) {
150 i64 k2;
152 for(k2=0; k2<count; k2++) {
153 dbuf_writeu16be(unc_pixels, (i64)n2);
156 else {
157 dbuf_write_run(unc_pixels, (u8)n2, count);
160 num_dcmpr_bytes += count*d->bytes_per_sample;
163 done:
167 static void sgiimage_decompress_rle(deark *c, struct sgiimage_ctx *d,
168 dbuf *unc_pixels)
170 struct sgiimage_offsettab_entry *offsettab = NULL;
171 i64 i;
172 i64 pos;
173 i64 first_alpha_scanline;
175 offsettab = de_mallocarray(c, d->num_scanlines,
176 sizeof(struct sgiimage_offsettab_entry));
178 pos = 512;
179 de_dbg(c, "scanline table at %"I64_FMT, pos);
180 de_dbg_indent(c, 1);
182 de_dbg(c, "offsets at %"I64_FMT, pos);
183 for(i=0; i<d->num_scanlines; i++) {
184 offsettab[i].pos = (u32)de_getu32be_p(&pos);
186 de_dbg(c, "lengths at %"I64_FMT, pos);
187 for(i=0; i<d->num_scanlines; i++) {
188 offsettab[i].len = (u32)de_getu32be_p(&pos);
190 de_dbg(c, "table end: %"I64_FMT, pos);
192 if(c->debug_level>=2) {
193 for(i=0; i<d->num_scanlines; i++) {
194 de_dbg2(c, "scanline[%"I64_FMT"]: offs=%u len=%u", i,
195 (UI)offsettab[i].pos, (UI)offsettab[i].len);
198 de_dbg_indent(c, -1);
200 if(d->has_alpha) {
201 first_alpha_scanline = d->height * (d->num_channels-1);
203 else {
204 first_alpha_scanline = d->num_scanlines;
207 for(i=0; i<d->num_scanlines; i++) {
208 i64 expected_ulen;
209 i64 actual_ulen;
211 sgiimage_decompress_rle_scanline(c, d, i,
212 (i64)offsettab[i].pos, (i64)offsettab[i].len, unc_pixels);
214 expected_ulen = (i+1) * d->rowspan;
215 actual_ulen = dbuf_get_length(unc_pixels);
216 if(actual_ulen != expected_ulen) {
217 if((actual_ulen < expected_ulen) && i>=first_alpha_scanline) {
218 // If we didn't decompress enough bytes, don't default to 0 (invisible)
219 // if this is the alpha channel.
220 dbuf_write_run(unc_pixels, 0xff, expected_ulen - actual_ulen);
222 else {
223 dbuf_truncate(unc_pixels, expected_ulen);
228 dbuf_flush(unc_pixels);
229 de_free(c, offsettab);
232 static void do_sgiimage_image(deark *c, struct sgiimage_ctx *d)
234 dbuf *unc_pixels = NULL;
235 de_bitmap *img = NULL;
236 de_bitmap *imglo = NULL;
238 d->is_grayscale = (d->num_channels<=2);
239 d->has_alpha = (d->num_channels==2 || d->num_channels==4);
241 if(!de_good_image_dimensions(c, d->width, d->height)) goto done;
243 if(d->pix_max>0 && (
244 (d->bytes_per_sample==1 && d->pix_max<24) ||
245 (d->bytes_per_sample==2 && d->pix_max<24*256)))
247 // If pix_max is to be believed, this image likely needs its brightness
248 // adjusted.
249 de_warn(c, "This image might need special processing (not supported).");
252 img = de_bitmap_create(c, d->width, d->height, (int)d->num_channels);
253 if(d->bytes_per_sample==2) {
254 imglo = de_bitmap_create(c, d->width, d->height, (int)d->num_channels);
257 d->rowspan = d->width * d->bytes_per_sample;
258 d->num_scanlines = d->height * d->num_channels;
259 d->total_unc_size = d->num_scanlines * d->rowspan;
261 if(d->storage_fmt==0) { // Uncompressed
262 sgiimage_decode_image(c, d, c->infile, 512, img, imglo);
264 else {
265 unc_pixels = dbuf_create_membuf(c, d->total_unc_size, 0);
266 dbuf_enable_wbuffer(unc_pixels);
267 sgiimage_decompress_rle(c, d, unc_pixels);
268 sgiimage_decode_image(c, d, unc_pixels, 0, img, imglo);
271 // Remove the alpha channel if it seems bad
272 if(!imglo) {
273 de_bitmap_optimize_alpha(img, 0x4 | 0x2);
276 de_bitmap16_write_to_file_finfo(img, imglo, NULL, DE_CREATEFLAG_FLIP_IMAGE |
277 DE_CREATEFLAG_OPT_IMAGE);
279 done:
280 dbuf_close(unc_pixels);
281 de_bitmap_destroy(img);
282 de_bitmap_destroy(imglo);
285 static void de_run_sgiimage(deark *c, de_module_params *mparams)
287 struct sgiimage_ctx *d = NULL;
288 i64 pos;
289 UI min_dim_count_expected;
290 int need_errmsg = 0;
292 d = de_malloc(c, sizeof(struct sgiimage_ctx));
293 d->input_encoding = de_get_input_encoding(c, NULL, DE_ENCODING_ASCII);
295 pos = 2;
296 d->storage_fmt = de_getbyte_p(&pos);
297 de_dbg(c, "compression: %u (%s)", (UI)d->storage_fmt,
298 get_cmprtype_name(d->storage_fmt));
300 d->bytes_per_sample = (UI)de_getbyte_p(&pos);
301 de_dbg(c, "bytes/sample: %u", d->bytes_per_sample);
303 d->dimension_count = (UI)de_getu16be_p(&pos);
304 de_dbg(c, "dimension count: %u", d->dimension_count);
306 d->width = de_getu16be_p(&pos);
307 de_dbg(c, "x-size: %"I64_FMT, d->width);
308 d->height = de_getu16be_p(&pos);
309 de_dbg(c, "y-size: %"I64_FMT, d->height);
310 d->num_channels = de_getu16be_p(&pos);
311 de_dbg(c, "z-size: %u", (UI)d->num_channels);
313 d->pix_min = (u32)de_getu32be_p(&pos);
314 d->pix_max = (u32)de_getu32be_p(&pos);
315 de_dbg(c, "pix min, max: %u, %u", (UI)d->pix_min, (UI)d->pix_max);
316 // TODO?: Support normalizing the image brightness/contrast.
317 // Unfortunately, the spec is ambiguous about the meaning of these fields,
318 // and there's no simple logic that would always do the right thing.
320 pos += 4; // unused
322 d->name = ucstring_create(c);
323 dbuf_read_to_ucstring(c->infile, pos, 80, d->name, DE_CONVFLAG_STOP_AT_NUL,
324 d->input_encoding);
325 de_dbg(c, "name: \"%s\"", ucstring_getpsz_d(d->name));
326 pos += 80;
328 d->colormap_id = (u32)de_getu32be_p(&pos);
329 de_dbg(c, "colormap code: %u (%s)", (UI)d->colormap_id,
330 get_cmi_name(d->colormap_id));
332 if(d->storage_fmt>1) {
333 need_errmsg = 1;
334 goto done;
337 if(d->colormap_id!=CMI_NORMAL) {
338 // TODO: Support other image types?
339 need_errmsg = 1;
340 goto done;
343 if(d->bytes_per_sample!=1 && d->bytes_per_sample!=2) {
344 need_errmsg = 1;
345 goto done;
348 // We're hoping that unused fields will be set to 0 or 1.
349 if(d->width==0) d->width = 1;
350 if(d->height==0) d->height = 1;
351 if(d->num_channels==0) d->num_channels = 1;
353 if(d->num_channels>1) min_dim_count_expected = 3;
354 else if(d->height>1) min_dim_count_expected = 2;
355 else min_dim_count_expected = 1;
357 if(d->dimension_count<min_dim_count_expected || d->dimension_count>3) {
358 de_warn(c, "Likely bad dimension count (is %u, assuming it should be %u)",
359 d->dimension_count, min_dim_count_expected);
361 // Note that, other than for the above warning, we ignore dimension_count.
362 // The x-size, y-size, z-size fields seem to be more reliable than it is.
364 if(d->num_channels<1 || d->num_channels>4) {
365 need_errmsg = 1;
366 goto done;
369 do_sgiimage_image(c, d);
371 done:
372 if(need_errmsg) {
373 de_err(c, "This type of SGI image is not supported");
375 if(d) {
376 ucstring_destroy(d->name);
377 de_free(c, d);
381 static int de_identify_sgiimage(deark *c)
383 UI n;
385 if(c->infile->len<513) return 0;
387 n = (UI)de_getu16be(0); // MAGIC
388 if(n!=474) return 0;
390 n = (UI)de_getbyte(2); // "STORAGE"
391 if(n>1) return 0;
393 n = (UI)de_getbyte(3); // "BPC"
394 if(n<1 || n>2) return 0;
396 n = (UI)de_getu16be(4); // "DIMENSION"
397 // Only 1-3 are legal, but we allow 0-4 because this field is confusing
398 // and underspecified.
399 if(n>4) return 0;
401 n = (UI)de_getu16be(10); // "ZSIZE"
402 // Allow 0 because this field may be unused for some image types.
403 if(n>4) return 0;
405 return 60;
408 void de_module_sgiimage(deark *c, struct deark_module_info *mi)
410 mi->id = "sgiimage";
411 mi->desc = "SGI image";
412 mi->run_fn = de_run_sgiimage;
413 mi->identify_fn = de_identify_sgiimage;