1 /* 2 * Copyright (C) 2015-2017, by Laszlo Szeremi under the Boost license. 3 * 4 * Pixel Perfect Engine, extbmp module 5 */ 6 7 module PixelPerfectEngine.extbmp.extbmp; 8 9 import std.xml; 10 import std.bitmanip; 11 import std.stdio; 12 import std.zlib; 13 import std.conv; 14 15 public import PixelPerfectEngine.extbmp.animation; 16 17 public class ExtendibleBitmap{ 18 19 20 public AnimationData[string] animData; 21 private void[] rawData, rawData0; 22 private string filename; 23 private string[string] metaData; 24 private ReplaceData[string] dataReplacer; 25 //private PaletteData[string] palettes; 26 private int[string] paletteOffset, paletteLenght; 27 private int headerLenght; 28 private uint flags; 29 public string[] bitmapID, bitdepth, format, paletteMode; 30 private int[] offset, iX, iY, length; 31 //private ushort[string] paletteOffset; 32 this(){ 33 34 } 35 this(void[] data){ 36 rawData = data; 37 flags = *cast(uint*)rawData.ptr; 38 headerLenght = *cast(int*)rawData.ptr + 4; 39 if((flags & ExtBMPFlags.CompressionMethodNull) == ExtBMPFlags.CompressionMethodNull){ 40 headerLoad(); 41 }else if((flags & ExtBMPFlags.CompressionMethodZLIB) == ExtBMPFlags.CompressionMethodZLIB){ 42 rawData0 = uncompress(rawData[8..rawData.length]); 43 rawData.length = 8; 44 rawData ~= rawData0; 45 rawData0.length = 0; 46 headerLoad(); 47 } 48 rawData0 = rawData[(9 + headerLenght)..rawData.length]; 49 rawData.length = 0; 50 } 51 this(string filename){ 52 this.filename = filename; 53 loadFile(); 54 } 55 public void setFileName(string s){ 56 filename = s; 57 } 58 public void loadFile(){ 59 writeln(filename); 60 try{ 61 rawData = std.file.read(filename); 62 flags = *cast(uint*)rawData.ptr; 63 headerLenght = *cast(int*)(rawData.ptr + 4); 64 //if((flags & ExtBMPFlags.CompressionMethodNull) == ExtBMPFlags.CompressionMethodNull){ 65 headerLoad(); 66 /*}else if((flags & ExtBMPFlags.CompressionMethodZLIB) == ExtBMPFlags.CompressionMethodZLIB){ 67 rawData0 = uncompress(rawData[8..rawData.length-1]); 68 rawData.length = 8; 69 rawData ~= rawData0; 70 rawData0.length = 0; 71 headerLoad(); 72 }*/ 73 //writeln(headerLenght,',',rawData.length); 74 if(rawData.length > 8 + headerLenght){ 75 rawData0 = rawData[8 + headerLenght..rawData.length]; 76 } 77 rawData.length = 0; 78 //writeln(cast(string)rawData0); 79 }catch(Exception e){ 80 writeln(e.toString); 81 } 82 } 83 public void saveFile(){ 84 saveFile(filename); 85 } 86 public void saveFile(string f){ 87 //writeln(f); 88 try{ 89 rawData.length=8; 90 *cast(uint*)rawData.ptr = flags; 91 headerSave(); 92 //headerLenght = rawData.length-8; 93 *cast(int*)(rawData.ptr+4) = headerLenght; 94 rawData ~= rawData0; 95 //File file = File(f, "w"); 96 //file.rawWrite(rawData); 97 98 std.file.write(f, rawData); 99 }catch(Exception e){ 100 writeln(e.toString); 101 } 102 rawData.length = 0; 103 } 104 private void headerLoad(){ 105 string s = cast(string)rawData[8..8 + headerLenght]; 106 //writeln(s); 107 Document d = new Document(s); 108 foreach(Element e1; d.elements){ 109 if(e1.tag.name == "MetaData"){ 110 //writeln("MetaData found"); 111 foreach(Element e2; e1.elements){ 112 metaData[e2.tag.name] = e2.text; 113 } 114 }else if(e1.tag.name == "Bitmap"){ 115 //writeln("Bitmap found"); 116 bitmapID ~= e1.tag.attr["ID"]; 117 offset ~= to!int(e1.tag.attr["offset"]); 118 iX ~= to!int(e1.tag.attr["sizeX"]); 119 iY ~= to!int(e1.tag.attr["sizeY"]); 120 length ~= to!int(e1.tag.attr["length"]); 121 bitdepth ~= e1.tag.attr["bitDepth"]; 122 format ~= e1.tag.attr.get("format",""); 123 paletteMode ~= e1.tag.attr.get("paletteMode",""); 124 if(e1.tag.attr.get("format","") == "upconv"){ 125 dataReplacer[e1.tag.attr["ID"]] = new ReplaceData(); 126 foreach(Element e2; e1.elements){ 127 if(e1.tag.name == "ColorSwap"){ 128 dataReplacer[e1.tag.attr["ID"]].addReplaceAttr(to!ubyte(e2.tag.attr["from"]), to!ushort(e2.tag.attr["to"])); 129 } 130 } 131 } 132 }else if(e1.tag.name == "Palette"){ 133 //writeln("Palette found"); 134 //palettes[e1.tag.attr["ID"]] = PaletteData(); 135 paletteLenght[e1.tag.attr["ID"]] = to!int(e1.tag.attr["length"]); 136 //palettes[e1.tag.attr["ID"]].format = e1.tag.attr.get("format",""); 137 paletteOffset[e1.tag.attr["ID"]] = to!int(e1.tag.attr["offset"]); 138 }else if(e1.tag.name == "AnimData"){ 139 animData[e1.tag.attr["ID"]] = AnimationData(); 140 foreach(Element e2; e1.elements){ 141 animData[e1.tag.attr["ID"]].addFrame(e2.tag.attr["ID"], to!int(e2.tag.attr["length"])); 142 } 143 } 144 } 145 } 146 private void headerSave(){ 147 auto doc = new Document(new Tag("HEADER")); 148 auto e0 = new Element("MetaData"); 149 foreach(string s; metaData.byKey()){ 150 e0 ~= new Element(s, metaData[s]); 151 } 152 153 doc ~= e0; 154 for(int i; i < bitmapID.length; i++){ 155 auto e1 = new Element("Bitmap"); 156 e1.tag.attr["ID"] = bitmapID[i]; 157 e1.tag.attr["offset"] = to!string(offset[i]); 158 e1.tag.attr["sizeX"] = to!string(iX[i]); 159 e1.tag.attr["sizeY"] = to!string(iY[i]); 160 e1.tag.attr["bitDepth"] = bitdepth[i]; 161 e1.tag.attr["length"] = to!string(length[i]); 162 if(format[i] != ""){ 163 e1.tag.attr["format"] = format[i]; 164 } 165 /*if(paletteMode[i] != ""){ 166 e1.tag.attr["paletteMode"] = paletteMode[i]; 167 }*/ 168 if(dataReplacer.get(bitmapID[i], null) !is null){ 169 for(int j; j < dataReplacer[bitmapID[i]].src.length; j++){ 170 auto e2 = new Element("ColorSwap"); 171 e2.tag.attr["from"] = to!string(dataReplacer[bitmapID[i]].src[j]); 172 e2.tag.attr["to"] = to!string(dataReplacer[bitmapID[i]].dest[j]); 173 e1 ~= e2; 174 } 175 } 176 doc ~= e1; 177 } 178 179 foreach(string s; paletteLenght.byKey()){ 180 auto e1 = new Element("Palette"); 181 e1.tag.attr["ID"] = s; 182 e1.tag.attr["length"] = to!string(paletteLenght[s]); 183 e1.tag.attr["offset"] = to!string(paletteOffset[s]); 184 /*if(palettes[s].format != "") 185 e1.tag.attr["format"] = palettes[s].format;*/ 186 doc ~= e1; 187 } 188 189 foreach(string s; animData.byKey()){ 190 auto e1 = new Element("AnimData"); 191 e1.tag.attr["ID"] = s; 192 for(int i; i < animData[s].duration.length; i++){ 193 auto e2 = new Element("Frame"); 194 e2.tag.attr["ID"] = animData[s].ID[i]; 195 e2.tag.attr["length"] = to!string(animData[s].duration[i]); 196 e1 ~= e2; 197 } 198 doc ~= e1; 199 } 200 string h = doc.toString(); 201 headerLenght = h.length; 202 //writeln(h); 203 writeln(headerLenght); 204 rawData ~= cast(void[])h; 205 } 206 private int searchForID(string ID){ 207 for(int i; i < bitmapID.length; i++){ 208 if(bitmapID[i] == ID){ 209 return i; 210 } 211 } 212 return -1; 213 } 214 public string[] getIDs(){ 215 return bitmapID; 216 } 217 /*public string[] getIDs(string s){}*/ 218 public void addBitmap(void[] data, int x, int y, string bitDepth, string ID, string format = ""){ 219 int o = rawData0.length; 220 rawData0 ~= data; 221 offset ~= o; 222 iX ~= x; 223 iY ~= y; 224 bitmapID ~= ID; 225 bitdepth ~= bitDepth; 226 length ~= data.length; 227 this.format ~= format; 228 } 229 public void addBitmap(ushort[] data, int x, int y, string bitDepth, string ID, string format = ""){ 230 int o = rawData0.length; 231 rawData0 ~= cast(void[])data; 232 offset ~= o; 233 iX ~= x; 234 iY ~= y; 235 bitmapID ~= ID; 236 bitdepth ~= bitDepth; 237 length ~= data.length * 2; 238 this.format ~= format; 239 } 240 public void addBitmap(ubyte[] data, int x, int y, string bitDepth, string ID, string format = "", ReplaceData rd = null){ 241 int o = rawData0.length; 242 rawData0 ~= cast(void[])data; 243 offset ~= o; 244 iX ~= x; 245 iY ~= y; 246 bitmapID ~= ID; 247 bitdepth ~= bitDepth; 248 length ~= data.length; 249 this.format ~= format; 250 251 } 252 public void addPalette(void[] data, string ID){ 253 if(paletteLenght.get(ID, -1)==-1){ 254 paletteOffset[ID] = rawData0.length; 255 rawData0 ~= data; 256 paletteLenght[ID] = data.length; 257 }else{ 258 259 } 260 writeln(cast(ubyte[])data); 261 } 262 public void removePalette(string ID){ 263 264 } 265 public void[] getBitmap(string ID){ 266 int pitch; 267 int n = searchForID(ID); 268 switch(bitdepth[n]){ 269 case "1bit": pitch = 1; break; 270 case "8bit": pitch = 8; break; 271 case "16bit": pitch = 16; break; 272 case "32bit": pitch = 32; break; 273 default: break; 274 } 275 276 int l = iX[n]*iY[n]*(pitch/8); 277 if(pitch == 1){ 278 BitArray ba = BitArray(rawData0[offset[n]..offset[n]+l], l); 279 return cast(void[])ba; 280 } 281 return rawData0[offset[n]..offset[n]+l]; 282 } 283 public void[] getBitmapRaw(int n){ 284 int pitch; 285 //int n = searchForID(ID); 286 switch(bitdepth[n]){ 287 case "1bit": pitch = 1; break; 288 case "8bit": pitch = 8; break; 289 case "16bit": pitch = 16; break; 290 case "32bit": pitch = 32; break; 291 default: break; 292 } 293 294 int l = iX[n]*iY[n]*(pitch/8); 295 if(pitch == 1){ 296 BitArray ba = BitArray(rawData0[offset[n]..offset[n]+l], l); 297 return cast(void[])ba; 298 } 299 return rawData0[offset[n]..offset[n]+l]; 300 } 301 public ubyte[] getBitmap(int n){ 302 int pitch; 303 //int n = searchForID(ID); 304 /*switch(bitdepth[n]){ 305 case "1bit": pitch = 1; break; 306 case "8bit": pitch = 8; break; 307 case "16bit": pitch = 16; break; 308 case "32bit": pitch = 32; break; 309 default: break; 310 }*/ 311 312 //int l = (iX[n]*iY[n]*pitch)/8; 313 /*if(pitch == 1){ 314 BitArray ba = BitArray(rawData0[offset[n]..offset[n]+l], l); 315 return cast(void[])ba; 316 }*/ 317 writeln(offset[n],',',offset[n]+length[n]); 318 return cast(ubyte[])(rawData0[offset[n]..offset[n]+length[n]]); 319 } 320 public ubyte[] get8bitBitmap(string ID){ 321 int n = searchForID(ID); 322 //int l = iX[n]*iY[n]; 323 return cast(ubyte[])rawData0[offset[n]..offset[n]+length[n]]; 324 } 325 public ushort[] get16bitBitmap(string ID){ 326 int n = searchForID(ID); 327 //int l = iX[n]*iY[n]; 328 ushort[] d; 329 if(bitdepth[n] == "16bit"){ 330 331 }else{ 332 if(dataReplacer.get(ID,null) is null){ 333 for(int i ; i < length[n]; i++){ 334 d ~= *cast(ubyte*)(rawData0.ptr + offset[n] + i); 335 } 336 }else{ 337 d = dataReplacer[ID].decodeBitmap(cast(ubyte[])rawData0[offset[n]..offset[n]+length[n]]); 338 } 339 } 340 return d; 341 } 342 public void[] getPalette(string ID){ 343 if(paletteLenght.get(ID, -1) == -1){ 344 ID = "default"; 345 } 346 return rawData0[paletteOffset[ID]..(paletteOffset[ID]+paletteLenght[ID])]; 347 } 348 public string getPaletteMode(string ID){ 349 return paletteMode[searchForID(ID)]; 350 } 351 public int getXsize(string ID){ 352 return iX[searchForID(ID)]; 353 } 354 public int getXsize(int i){ 355 return iX[i]; 356 } 357 public int getYsize(string ID){ 358 return iY[searchForID(ID)]; 359 } 360 public int getYsize(int i){ 361 return iY[i]; 362 } 363 public string getBitDepth(string ID){ 364 return bitdepth[searchForID(ID)]; 365 } 366 public string getFormat(string ID){ 367 return format[searchForID(ID)]; 368 } 369 public bool isEmpty(){ 370 return (bitmapID.length == 0); 371 } 372 } 373 374 /*public struct PaletteData{ 375 ubyte[] data; 376 string format; 377 int length; 378 379 }*/ 380 381 public class ReplaceData{ 382 ubyte[] src; 383 ushort[] dest; 384 this(){ 385 386 } 387 void addReplaceAttr(ubyte f, ushort t){ 388 this.src ~= f; 389 this.dest ~= t; 390 } 391 ushort[] decodeBitmap(ubyte[] data){ 392 ushort[] result; 393 result.length = data.length; 394 for(int i; i < data.length; i++){ 395 result[i] = lookupForDecoding(data[i]); 396 } 397 return result; 398 } 399 ubyte[] encodeBitmap(ushort[] data){ 400 ubyte[] result; 401 result.length = data.length; 402 for(int i; i < data.length; i++){ 403 result[i] = lookupForEncoding(data[i]); 404 } 405 return result; 406 } 407 private ushort lookupForDecoding(ubyte b){ 408 for(int i; i < src.length; i++){ 409 if(src[i] == b){ 410 return dest[i]; 411 } 412 } 413 return b; 414 } 415 private ubyte lookupForEncoding(ushort s){ 416 for(int i; i < src.length; i++){ 417 if(dest[i] == s){ 418 return src[i]; 419 } 420 } 421 return to!ubyte(s); 422 } 423 } 424 425 public enum ExtBMPFlags : uint{ 426 CompressionMethodNull = 1, 427 CompressionMethodZLIB = 2, 428 LongHeader = 16, 429 LongFile = 32 430 /*ZLIBCompressionLevel0 = 16, 431 ZLIBCompressionLevel1 = 32, 432 ZLIBCompressionLevel2 = 48, 433 ZLIBCompressionLevel3 = 64, 434 ZLIBCompressionLevel4 = 80, 435 ZLIBCompressionLevel5 = 96, 436 ZLIBCompressionLevel6 = 112, 437 ZLIBCompressionLevel7 = 128, 438 ZLIBCompressionLevel8 = 144, 439 ZLIBCompressionLevel9 = 160,*/ 440 }