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 }