1 /** 2 BMP encoder. 3 Encoder based upon imageformats dub package. 4 5 Copyright: Copyright Tero Hänninen 2016-2022 (encoder) 6 Copyright Guillaume Piolat 2024 7 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 8 */ 9 module gamut.codecs.bmpenc; 10 11 import core.stdc.stdlib: malloc, free; 12 import gamut.io; 13 import gamut.image; 14 import gamut.scanline; 15 16 @nogc nothrow: 17 18 enum CMP_RGB = 0; 19 enum CMP_BITS = 3; 20 21 22 // Writes RGB or RGBA data. 23 bool write_bmp(ref const(Image) image, 24 IOStream *io, 25 IOHandle handle, 26 int w, 27 int h, 28 int tgt_chans) 29 { 30 assert(w >= 1 && w < 32768); 31 enum int DIB_SIZE = 108; 32 const int tgt_linesize = w * tgt_chans; 33 const int pad = 3 - ((tgt_linesize - 1) & 3); 34 assert(pad >= 0 && pad < 4); 35 const int idat_offset = 14 + DIB_SIZE; 36 const size_t filesize = idat_offset + (cast(size_t) h) * (tgt_linesize + pad); 37 if (filesize > 0xffff_ffff) 38 return false; // image too large (cannot happend for now with rgb8 and rgba8). 39 40 ubyte* _scanBuffer = null; 41 size_t _scanLen; 42 _scanBuffer = cast(ubyte*) malloc(tgt_linesize + pad); 43 _scanLen = tgt_linesize + pad; 44 scope(exit) free(_scanBuffer); 45 46 ubyte[14 + DIB_SIZE] hdr; 47 hdr[0] = 0x42; 48 hdr[1] = 0x4d; 49 hdr[2..6] = nativeToLittleEndian_uint(cast(uint) filesize); 50 hdr[6..10] = 0; // reserved 51 hdr[10..14] = nativeToLittleEndian_uint(cast(uint) idat_offset); // offset of pixel data 52 hdr[14..18] = nativeToLittleEndian_uint(cast(uint) DIB_SIZE); // dib header size 53 hdr[18..22] = nativeToLittleEndian_uint(w); 54 hdr[22..26] = nativeToLittleEndian_uint(h); // positive -> bottom-up 55 hdr[26..28] = nativeToLittleEndian_ushort(1); // planes 56 hdr[28..30] = nativeToLittleEndian_ushort(cast(ushort)(tgt_chans * 8)); // bits per pixel 57 hdr[30..34] = nativeToLittleEndian_uint((tgt_chans == 3) ? CMP_RGB : CMP_BITS); 58 hdr[34..54] = 0; // rest of dib v1 59 if (tgt_chans == 3) 60 { 61 hdr[54..70] = 0; // dib v2 and v3 62 } 63 else 64 { 65 static immutable ubyte[16] b = [ 66 0, 0, 0xff, 0, 67 0, 0xff, 0, 0, 68 0xff, 0, 0, 0, 69 0, 0, 0, 0xff 70 ]; 71 hdr[54..70] = b; 72 } 73 static immutable ubyte[4] BGRs = ['B', 'G', 'R', 's']; 74 hdr[70..74] = BGRs; 75 hdr[74..122] = 0; 76 77 // write header 78 if (1 != io.write(hdr.ptr, hdr.length, 1, handle)) 79 { 80 return false; 81 } 82 83 scanlineConversionFunction_t cvtFun = tgt_chans == 3 ? &scanline_convert_rgb8_to_bgr8 84 : &scanline_convert_rgba8_to_bgra8; 85 ubyte* outScan = _scanBuffer; 86 for (int y = 0; y < h; ++y) 87 { 88 const(ubyte)* inScan = cast(const(ubyte)*) image.scanptr(h - 1 - y); 89 cvtFun(inScan, outScan, w, null); // convert to BGR or BGRA 90 size_t written = io.write(_scanBuffer, _scanLen, 1, handle); 91 if (1 != written) 92 { 93 return false; 94 } 95 } 96 return true; 97 } 98 99 private: 100 101 ubyte[2] nativeToLittleEndian_ushort(short s) pure @safe 102 { 103 ubyte[2] r; 104 r[0] = (s & 0xff); 105 r[1] = (s >> 8) & 0xff; 106 return r; 107 } 108 109 ubyte[4] nativeToLittleEndian_uint(int s) pure @safe 110 { 111 ubyte[4] r; 112 r[0] = s & 0xff; 113 r[1] = (s >> 8) & 0xff; 114 r[2] = (s >> 16) & 0xff; 115 r[3] = (s >> 24) & 0xff; 116 return r; 117 }