The OpenD Programming Language

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 }