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 }