1 // Copyright 2019 Tero Hänninen. All rights reserved.
2 // SPDX-License-Identifier: BSD-2-Clause
3 module imagefmt.bmp;
4 
5 import imagefmt;
6 
7 @nogc nothrow package:
8 
9 struct BMPHeader {
10     int w;              // can be negative
11     int h;              // can be negative
12     int planes;         // only checked, not otherwise used...
13     int bitspp;
14     uint dataoff;
15     uint alphamask;     // alpha; from dibv3
16     uint rmask;         // red
17     uint gmask;         // green
18     uint bmask;         // blue
19     uint compress;
20     uint palettelen;
21     uint dibsize;
22     ubyte dibv;         // dib header version
23 }
24 
25 int abs(int x) { return x >= 0 ? x : -x; }
26 
27 bool detect_bmp(Reader* rc)
28 {
29     bool result;
30     if (read_u8(rc) != 'B' || read_u8(rc) != 'M') {
31         result = false;
32     } else {
33         skip(rc, 12);
34         const uint ds = read_u32le(rc);
35         result = ((ds == 12 || ds == 40 || ds == 52 ||
36                    ds == 56 || ds == 108 || ds == 124) && !rc.fail);
37     }
38     reset2start(rc);
39     return result;
40 }
41 
42 IFInfo read_bmp_info(Reader* rc)
43 {
44     BMPHeader head;
45     IFInfo info;
46     info.e = read_bmp_header(rc, head);
47     if (info.e) return info;
48     info.w = abs(head.w);
49     info.h = abs(head.h);
50     info.c = (head.dibv >= 3 && head.alphamask != 0 && head.bitspp == 32) ? 4 : 3;
51     return info;
52 }
53 
54 ubyte read_bmp_header(Reader* rc, out BMPHeader head)
55 {
56     ubyte b = read_u8(rc);
57     ubyte m = read_u8(rc);
58 
59     skip(rc, 8);    // filesize (4) + reserved bytes
60     head.dataoff = read_u32le(rc);
61     head.dibsize = read_u32le(rc);
62 
63     if (rc.fail)
64         return ERROR.stream;
65 
66     if (b != 'B' || m != 'M')
67         return ERROR.data;
68 
69     switch (head.dibsize) {
70         case 12: head.dibv = 0; break;
71         case 40: head.dibv = 1; break;
72         case 52: head.dibv = 2; break;
73         case 56: head.dibv = 3; break;
74         case 108: head.dibv = 4; break;
75         case 124: head.dibv = 5; break;
76         default: return ERROR.unsupp;
77     }
78 
79     if (head.dibsize <= 12) {
80         head.w      = read_u16le(rc);
81         head.h      = read_u16le(rc);
82         head.planes = read_u16le(rc);
83         head.bitspp = read_u16le(rc);
84     } else {
85         head.w      = cast(int) read_u32le(rc);
86         head.h      = cast(int) read_u32le(rc);
87         head.planes = read_u16le(rc);
88         head.bitspp = read_u16le(rc);
89     }
90 
91     if (head.dibsize >= 40) {
92         head.compress = read_u32le(rc);
93         skip(rc, 4 * 3); // image data size + pixels per meter x & y
94         head.palettelen = read_u32le(rc);
95         skip(rc, 4);    // important color count
96     }
97 
98     if (head.dibsize >= 52) {
99         head.rmask = read_u32le(rc);
100         head.gmask = read_u32le(rc);
101         head.bmask = read_u32le(rc);
102     }
103 
104     if (head.dibsize >= 56)
105         head.alphamask = read_u32le(rc);
106 
107     if (head.dibsize >= 108)
108         skip(rc, 4 + 36 + 4*3); // color space type + endpoints + rgb-gamma
109 
110     if (head.dibsize >= 124)
111         skip(rc, 8);    // icc profile data + size
112 
113     if (rc.fail)
114         return ERROR.stream;
115 
116     if (head.w == int.min || head.h == int.min)
117         return ERROR.data;  // make abs simple
118 
119     return 0;
120 }
121 
122 enum CMP_RGB  = 0;
123 enum CMP_BITS = 3;
124 
125 ubyte read_bmp(Reader* rc, out IFImage image, in int reqchans, in int reqbpc)
126 {
127     if (cast(uint) reqchans > 4)
128         return ERROR.arg;
129     const ubyte tbpc = cast(ubyte) (reqbpc ? reqbpc : 8);
130     if (tbpc != 8 && tbpc != 16)
131         return ERROR.unsupp;
132     BMPHeader head;
133     if (ubyte e = read_bmp_header(rc, head))
134         return e;
135     if (head.w < 1 || head.h == 0)
136         return ERROR.dim;
137     if (head.dataoff < (14 + head.dibsize) || head.dataoff > 0xffffff)
138         return ERROR.data;    // that upper limit is arbitrary --^
139     if (head.planes != 1)
140         return ERROR.unsupp;
141 
142     int bytes_pp    = 1;
143     bool paletted   = true;
144     int palettelen  = 256;
145     bool rgb_masked = false;
146     int pe_bytes_pp = 3;
147 
148     if (head.dibv >= 1) {
149         if (head.palettelen > 256)
150             return ERROR.dim;
151         if (head.bitspp <= 8 && (head.palettelen == 0 || head.compress != CMP_RGB))
152             return ERROR.unsupp;
153         if (head.compress != CMP_RGB && head.compress != CMP_BITS)
154             return ERROR.unsupp;
155 
156         switch (head.bitspp) {
157             case 8  : bytes_pp = 1; paletted = true; break;
158             case 24 : bytes_pp = 3; paletted = false; break;
159             case 32 : bytes_pp = 4; paletted = false; break;
160             default: return ERROR.unsupp;
161         }
162 
163         palettelen = head.palettelen;
164         rgb_masked = head.compress == CMP_BITS;
165         pe_bytes_pp = 4;
166     }
167 
168     int redi = 2;
169     int grei = 1;
170     int blui = 0;
171 
172     if (rgb_masked) {
173         if (head.dibv < 2)
174             return ERROR.data;
175         if (mask2idx(head.rmask, redi)
176          || mask2idx(head.gmask, grei)
177          || mask2idx(head.bmask, blui))
178             return ERROR.unsupp;
179     }
180 
181     bool alphamasked = false;
182     int alphai = 0;
183 
184     if (bytes_pp == 4 && head.dibv >= 3 && head.alphamask != 0) {
185         alphamasked = true;
186         if (mask2idx(head.alphamask, alphai))
187             return ERROR.unsupp;
188     }
189 
190     const int tchans = reqchans > 0 ? reqchans
191                                     : alphamasked ? CHANS.rgba
192                                                   : CHANS.rgb;
193 
194     // note: this does not directly match cinfile, see alphamasked
195     const int sfmt = paletted && pe_bytes_pp == 3 ? CHANS.bgr
196                                                   : CHANS.bgra;
197 
198     auto convert = cast(conv8) getconv(sfmt, tchans, 8);
199 
200     const int slinesz = head.w * bytes_pp;    // without padding
201     const int tlinesz = head.w * tchans;
202     const int srcpad  = 3 - ((slinesz-1) % 4);
203     const int height  = abs(head.h);
204     const bool flip   = (head.h < 0) ^ (VERTICAL_ORIENTATION_READ == 1);
205     const int tstride   = flip ? -tlinesz               : tlinesz;
206     const int ti_start  = flip ? (height-1) * tlinesz   : 0;
207     const uint ti_limit = height * tlinesz;     // unsigned for a reason
208 
209     ubyte e;
210 
211     if (cast(ulong) head.w * height * tchans > MAXIMUM_IMAGE_SIZE)
212         return ERROR.bigimg;
213 
214     ubyte[] result       = new_buffer(head.w * height * tchans, e);
215     if (e) return e;
216     ubyte[] sline        = null;
217     ubyte[] xline        = null;  // intermediate buffer
218     ubyte[] palette      = null;
219     ubyte[] workbuf      = new_buffer(slinesz + srcpad + head.w * 4, e);
220     if (e) goto failure;
221     sline                = workbuf[0 .. slinesz + srcpad];
222     xline                = workbuf[sline.length .. sline.length + head.w * 4];
223 
224     if (paletted) {
225         palette = new_buffer(palettelen * pe_bytes_pp, e);
226         if (e) goto failure;
227         read_block(rc, palette[0..$]);
228     }
229 
230     skipto(rc, head.dataoff);
231 
232     if (rc.fail) {
233         e = ERROR.stream;
234         goto failure;
235     }
236 
237     if (!paletted) {
238         for (int ti = ti_start; cast(uint) ti < ti_limit; ti += tstride) {
239             read_block(rc, sline[0..$]);
240             for (size_t si, di;   si < slinesz;   si+=bytes_pp, di+=4) {
241                 xline[di + 0] = sline[si + blui];
242                 xline[di + 1] = sline[si + grei];
243                 xline[di + 2] = sline[si + redi];
244                 xline[di + 3] = alphamasked ? sline[si + alphai]
245                                             : 255;
246             }
247             convert(xline[0..$], result[ti .. ti + tlinesz]);
248         }
249     } else {
250         const int ps = pe_bytes_pp;
251         for (int ti = ti_start; cast(uint) ti < ti_limit; ti += tstride) {
252             read_block(rc, sline[0..$]);
253             int di = 0;
254             foreach (idx; sline[0 .. slinesz]) {
255                 if (idx > palettelen) {
256                     e = ERROR.data;
257                     goto failure;
258                 }
259                 const int i = idx * ps;
260                 xline[di + 0] = palette[i + 0];
261                 xline[di + 1] = palette[i + 1];
262                 xline[di + 2] = palette[i + 2];
263                 if (ps == 4)
264                     xline[di + 3] = 255;
265                 di += ps;
266             }
267             convert(xline[0..$], result[ti .. ti + tlinesz]);
268         }
269     }
270 
271     if (rc.fail) goto failure;
272 finish:
273     _free(workbuf.ptr);
274     _free(palette.ptr);
275     image.w = head.w;
276     image.h = abs(head.h);
277     image.c = cast(ubyte) tchans;
278     image.cinfile = head.dibv >= 3 && head.alphamask != 0 && head.bitspp == 32
279                   ? 4 : 3;
280     image.bpc = tbpc;
281     if (tbpc == 8) {
282         image.buf8 = result;
283     } else if (result) {
284         image.buf16 = bpc8to16(result);
285         if (!image.buf16.ptr && !e)
286             e = ERROR.oom;
287     }
288     return e;
289 failure:
290     _free(result.ptr);
291     result = null;
292     goto finish;
293 }
294 
295 bool mask2idx(in uint mask, out int index)
296 {
297     switch (mask) {
298         case 0xff00_0000: index = 3; return false;
299         case 0x00ff_0000: index = 2; return false;
300         case 0x0000_ff00: index = 1; return false;
301         case 0x0000_00ff: index = 0; return false;
302         default: return true;
303     }
304 }
305 
306 // Note: will only write RGB and RGBA images.
307 ubyte write_bmp(Writer* wc, int w, int h, in ubyte[] buf, int reqchans)
308 {
309     if (w < 1 || h < 1 || w > 0x7fff || h > 0x7fff)
310         return ERROR.dim;
311     const int schans = cast(int) (buf.length / w / h);
312     if (schans < 1 || schans > 4 || schans * w * h != buf.length)
313         return ERROR.dim;
314     if (reqchans != 0 && reqchans != 3 && reqchans != 4)
315         return ERROR.unsupp;
316 
317     const int tchans = reqchans ? reqchans
318                                 : schans == 1 || schans == 3 ? 3 : 4;
319 
320     const uint dibsize = 108;
321     const uint tlinesz = cast(size_t) (w * tchans);
322     const uint pad = 3 - ((tlinesz-1) % 4);
323     const uint idat_offset = 14 + dibsize;       // bmp file header + dib header
324     const size_t filesize = idat_offset + cast(size_t) h * (tlinesz + pad);
325     if (filesize > 0xffff_ffff)
326         return ERROR.bigimg;
327     const ubyte[64] zeros = 0;
328 
329     write_u8(wc, 'B');
330     write_u8(wc, 'M');
331     write_u32le(wc, cast(uint) filesize);
332     write_u32le(wc, 0);     // reserved
333     write_u32le(wc, idat_offset);
334     write_u32le(wc, dibsize);
335     write_u32le(wc, w);
336     write_u32le(wc, h);     // positive -> bottom-up
337     write_u16le(wc, 1);     // planes
338     write_u16le(wc, cast(ushort) (tchans * 8));   // bitspp
339     write_u32le(wc, tchans == 3 ? CMP_RGB : CMP_BITS);
340     write_block(wc, zeros[0..20]);      // rest of dibv1
341     if (tchans == 3) {
342         write_block(wc, zeros[0..16]);  // dibv2 and dibv3
343     } else {
344         static immutable ubyte[16] masks = [
345             0, 0, 0xff, 0,
346             0, 0xff, 0, 0,
347             0xff, 0, 0, 0,
348             0, 0, 0, 0xff
349         ];
350         write_block(wc, masks[0..$]);
351     }
352     write_u8(wc, 'B');
353     write_u8(wc, 'G');
354     write_u8(wc, 'R');
355     write_u8(wc, 's');
356     write_block(wc, zeros[0..48]);
357 
358     if (wc.fail)
359         return ERROR.stream;
360 
361     auto convert =
362         cast(conv8) getconv(schans, tchans == 3 ? CHANS.bgr : CHANS.bgra, 8);
363 
364     int slinesz = w * schans;
365     int sstride = -slinesz * VERTICAL_ORIENTATION_WRITE;
366     int si      = (h-1) * slinesz * (VERTICAL_ORIENTATION_WRITE == 1);
367 
368     ubyte e;
369     ubyte[] tline = new_buffer(tlinesz + pad, e);
370 
371     if (e)
372         goto finish;
373 
374     foreach (_; 0..h) {
375         convert(buf[si .. si + slinesz], tline[0..tlinesz]);
376         write_block(wc, tline[0..$]);
377         si += sstride;
378     }
379 
380     if (wc.fail)
381         e = ERROR.stream;
382 
383 finish:
384     _free(tline.ptr);
385     return e;
386 }