1 /// DirectDraw Surfaces (DDS files) 2 module nwnlibd.bitmap; 3 4 import std.stdint; 5 import std.exception; 6 import std.algorithm; 7 import std.conv; 8 import std.traits; 9 import std.string: format; 10 import std.range: chunks; 11 debug import std.stdio; 12 13 import nwnlibd.parseutils; 14 15 alias BitmapGrayscale = Bitmap!(ubyte[1]); 16 alias BitmapGrayscaleA = Bitmap!(ubyte[2]); 17 alias BitmapRGB = Bitmap!(ubyte[3]); 18 alias BitmapRGBA = Bitmap!(ubyte[4]); 19 20 /// 2D bitmap. Each pixel is coded with a fixed number of bytes. 21 /// 22 /// Pixel sizes: 23 /// ubyte[1] => grayscale 24 /// ubyte[2] => grayscale + alpha 25 /// ubyte[3] => RGB 26 /// ubyte[4] => RGBA 27 struct Bitmap(Pixel = ubyte[4]) { 28 static assert(isStaticArray!Pixel || isIntegral!Pixel, "Bitmap can only contain a static array or a single integers"); 29 30 this(size_t width, size_t height){ 31 this.width = width; 32 this.height = height; 33 pixels.length = width * height; 34 } 35 36 /// Access a given pixel with Bitmap[x, y] 37 ref inout(Pixel) opIndex(in size_t x, in size_t y) inout { 38 return pixels[y * width + x]; 39 } 40 41 ///// Rotate the image by increments of 90° counter clockwise 42 //void rotate(int rot) { 43 // rot = ((abs(rot / 4) + 1) * 4 + rot) % 4; 44 45 // auto newPixels = new Pixel[pixels.length]; 46 47 // final switch(rot){ 48 // case 0: 49 // return; 50 // case 1: 51 // foreach(y ; 0 .. height) 52 // foreach(x ; 0 .. width) 53 // newPixels[y * width + x] = this[] 54 55 56 // } 57 58 //} 59 60 /// Serialize to a BMP file. Alpha data is lost. 61 ubyte[] toBMP(){ 62 import std.outbuffer; 63 auto buf = new OutBuffer(); 64 65 //header 66 buf.write(cast(char[2])"BM"); 67 buf.write(cast(uint32_t)0);//will be filled later 68 buf.write(cast(uint16_t)0); 69 buf.write(cast(uint16_t)0); 70 buf.write(cast(uint32_t)(54)); 71 assert(buf.offset == 14); 72 73 //DIB header 74 buf.write(cast(uint32_t)40); 75 buf.write(cast(int32_t)width); 76 buf.write(cast(int32_t)height);//height 77 buf.write(cast(uint16_t)1); 78 buf.write(cast(uint16_t)24);//bits per pixel 79 buf.write(cast(uint32_t)0); 80 buf.write(cast(uint32_t)0);//size of the pixel array, 0 = auto 81 buf.write(cast(uint32_t)500);//dpi 82 buf.write(cast(uint32_t)500);//dpi 83 buf.write(cast(uint32_t)(0));//colors in color palette 84 buf.write(cast(uint32_t)0); 85 assert(buf.offset == 54); 86 87 ubyte[] padding; 88 padding.length = ((width * 3 + 3) / 4) * 4 - width * 3; 89 padding[] = 0; 90 91 foreach_reverse(y ; 0 .. height){ 92 foreach(x ; 0 .. width){ 93 static if(isStaticArray!Pixel){ 94 static if (Pixel.sizeof == 1 || Pixel.sizeof == 2) 95 buf.write([this[x, y][0], this[x, y][0], this[x, y][0]]); 96 else static if(Pixel.sizeof >= 3) 97 buf.write(this[x, y][0 .. 3]); 98 } 99 else 100 buf.write(this[x, y]); 101 } 102 buf.write(padding); 103 } 104 105 // re-write file size 106 const oldOffset = buf.offset; 107 buf.offset = 2; 108 buf.write(cast(uint32_t)oldOffset); 109 buf.offset = oldOffset; 110 111 return buf.toBytes(); 112 } 113 114 /// Serialize to a PNG file 115 ubyte[] toPNG(){ 116 import std.zlib: compress, crc32; 117 import std.outbuffer: OutBuffer; 118 import std.bitmanip; 119 //import std.digest.crc: crc32Of; 120 121 auto buf = new OutBuffer(); 122 123 void writeChunk(T...)(char[4] type, T data){ 124 template ChunkLen(T...){ 125 uint32_t ChunkLen(T data){ 126 uint32_t len = 0; 127 static foreach(d ; data){ 128 static if(isDynamicArray!(typeof(d))) 129 len += ForeachType!(typeof(d)).sizeof * d.length; 130 else 131 len += d.sizeof; 132 } 133 return len; 134 } 135 } 136 buf.write(ChunkLen(data).nativeToBigEndian); 137 const crcStart = buf.offset; 138 buf.write(type); 139 static foreach(d ; data) 140 buf.write(d); 141 buf.write(crc32(0, buf.toBytes()[crcStart .. buf.offset]).nativeToBigEndian); 142 } 143 144 buf.write("\x89PNG\r\n\x1A\n"); 145 146 static if(isStaticArray!Pixel) 147 ubyte depth = ForeachType!Pixel.sizeof * 8; 148 else 149 ubyte depth = 8; 150 ubyte colorType; 151 switch(Pixel.sizeof){ 152 case 1: colorType = 0b0000; break; // Grayscale 153 case 2: colorType = 0b0100; break; // Grayscale + A 154 case 3: colorType = 0b0010; break; // RGB 155 case 4: colorType = 0b0110; break; // RGBA 156 default: assert(0, format!"Cannot convert %d-bits colors to PNG"(Pixel.sizeof * 8)); 157 } 158 159 160 writeChunk("IHDR", 161 width.to!uint32_t.nativeToBigEndian, 162 height.to!uint32_t.nativeToBigEndian, 163 depth,// depth 164 cast(ubyte)colorType,// color type 165 cast(ubyte)0,// compression 166 cast(ubyte)0,// filter 167 cast(ubyte)0,// interlace 168 ); 169 170 171 auto uncompIdat = new OutBuffer(); 172 foreach(y ; 0 .. height){ 173 uncompIdat.write(cast(ubyte)0); 174 uncompIdat.write(cast(ubyte[])(pixels[y * width .. (y + 1) * width])); 175 } 176 177 writeChunk("IDAT", 178 uncompIdat.toBytes().compress(0) 179 ); 180 181 182 writeChunk("IEND"); 183 184 return buf.toBytes(); 185 } 186 187 188 size_t width, height; 189 Pixel[] pixels; 190 } 191 192 unittest{ 193 import std.file; 194 auto bmp = BitmapRGBA(8, 8); 195 196 ubyte[4] GetColor(size_t i){ 197 switch(i % 8){ 198 case 0: return [255, 0, 0, 255]; 199 case 1: return [255, 255, 0, 255]; 200 case 2: return [0, 255, 0, 255]; 201 case 3: return [0, 255, 255, 255]; 202 case 4: return [0, 0, 255, 255]; 203 case 5: return [255, 0, 255, 255]; 204 case 6: return [0, 0, 0, 255]; 205 case 7: return [255, 255, 255, 255]; 206 default: assert(0); 207 } 208 } 209 210 foreach(i ; 0 .. 8){ 211 bmp[i, i] = GetColor(i); 212 bmp[i, 0] = GetColor(i); 213 } 214 215 std.file.write("test.png", bmp.toPNG()); 216 }