1 module PixelPerfectEngine.system.file;
2 /*
3  * Copyright (C) 2015-2017, by Laszlo Szeremi under the Boost license.
4  *
5  * Pixel Perfect Engine, file module
6  */
7 
8 import std.file;
9 import std.path;
10 import std.stdio;
11 import std.conv : to;
12 import core.stdc..string : memcpy;
13 import PixelPerfectEngine.system.etc;
14 import PixelPerfectEngine.system.exc;
15 
16 import PixelPerfectEngine.graphics.bitmap;
17 import PixelPerfectEngine.graphics.raster;
18 import PixelPerfectEngine.graphics.fontsets;
19 
20 import PixelPerfectEngine.extbmp.extbmp;
21 
22 import dimage.tga;
23 import dimage.png;
24 
25 import bindbc.sdl.mixer;
26 
27 /**
28  * Loads a bitmap from disk.
29  * Currently supported formats: *.tga, *.png
30  */
31 public T loadBitmapFromFile(T)(string filename)
32 		if(T.stringof == Bitmap4Bit.stringof || T.stringof == Bitmap8Bit.stringof || T.stringof == Bitmap16Bit.stringof
33 		|| T.stringof == Bitmap32Bit.stringof){
34 	switch(extension(filename)){
35 		case ".tga", ".TGA":
36 			TGA imageFile = TGA.load(File(filename));
37 			if(!imageFile.getHeader.topOrigin){
38 				imageFile.flipVertical;
39 			}
40 			static if(T.stringof == Bitmap4Bit.stringof){
41 				if(imageFile.getBitdepth != 4)
42 					throw new BitmapFormatException("Bitdepth mismatch exception!");
43 				return new Bitmap4Bit(imageFile.getImageData, imageFile.width, imageFile.height);
44 			}else static if(T.stringof == Bitmap8Bit.stringof){
45 				if(imageFile.getBitdepth != 8)
46 					throw new BitmapFormatException("Bitdepth mismatch exception!");
47 				return new Bitmap8Bit(imageFile.getImageData, imageFile.width, imageFile.height);
48 			}else static if(T.stringof == Bitmap16Bit.stringof){
49 				if(imageFile.getBitdepth != 16)
50 					throw new BitmapFormatException("Bitdepth mismatch exception!");
51 				return new Bitmap16Bit(imageFile.getImageData, imageFile.width, imageFile.height);
52 			}else static if(T.stringof == Bitmap32Bit.stringof){
53 				if(imageFile.getBitdepth != 32)
54 					throw new BitmapFormatException("Bitdepth mismatch exception!");
55 				return new Bitmap32Bit(imageFile.getImageData, imageFile.width, imageFile.height);
56 			}
57 			break;
58 		case ".png", ".PNG":
59 			PNG imageFile = PNG.load(File(filename));
60 			static if(T.stringof == Bitmap8Bit.stringof){
61 				if(imageFile.getBitdepth != 8)
62 					throw new BitmapFormatException("Bitdepth mismatch exception!");
63 				return new Bitmap8Bit(imageFile.getImageData, imageFile.width, imageFile.height);
64 			}else static if(T.stringof == Bitmap32Bit.stringof){
65 				if(imageFile.getBitdepth != 32)
66 					throw new BitmapFormatException("Bitdepth mismatch exception!");
67 				return new Bitmap32Bit(imageFile.getImageData, imageFile.width, imageFile.height);
68 			}
69 			break;
70 		default:
71 			throw new Exception("Unsupported file format!");
72 	}
73 }
74 /**
75  * Loads a bitmap sheet from file.
76  * This one doesn't require TGA devarea extensions.
77  */
78 public T[] loadBitmapSheetFromFile(T)(string filename, int x, int y)
79 		if(T.stringof == Bitmap4Bit.stringof || T.stringof == Bitmap8Bit.stringof || T.stringof == Bitmap16Bit.stringof
80 		|| T.stringof == Bitmap32Bit.stringof){
81 	T source = loadBitmapFromFile(filename);
82 	if(source.width % x == 0 && source.height % y == 0){
83 
84 	}else throw new Exception("Requested size cannot be divided by input file's sizes!");
85 	T[] output;
86 	static if (T.stringof == Bitmap4Bit.stringof)
87 		const size_t length = x / 2, pitch = output.width / 2;
88 	else static if (T.stringof == Bitmap8Bit.stringof)
89 		const size_t length = x, pitch = output.width;
90 	else static if (T.stringof == Bitmap16Bit.stringof)
91 		const size_t length = x * 2, pitch = output.width * 2;
92 	else static if (T.stringof == Bitmap32Bit.stringof)
93 		const size_t length = x * 4, pitch = output.width * 4;
94 	const size_t pitch0 = pitch * y;
95 	for (int mY ; mY < source.height / y ; mY++){
96 		for (int mX ; mX < source.width / x ; mX++){
97 			T next = new T(x, y);
98 			for (int lY ; lY < y ; lY++){
99 				memcpy(next.getPtr + (lY * length), source.getPtr + (pitch * lY) + (pitch * mY) + (x * mX), length);
100 			}
101 		}
102 	}
103 	return output;
104 }
105 /**
106  * Loads a palette from a file.
107  */
108 public Color[] loadPaletteFromFile(string filename){
109 	File f = File(filename);
110 	switch(extension(filename)){
111 		case ".tga", ".TGA":
112 			TGA imageFile = TGA.load(f);
113 			return cast(Color[])(cast(void[])imageFile.getPaletteData);
114 		case ".png", ".PNG":
115 			PNG imageFile = PNG.load(f);
116 			return cast(Color[])(cast(void[])imageFile.getPaletteData);
117 		default:
118 			throw new Exception("Unsupported file format!");
119 	}
120 }
121 /**
122  * Gets a bitmap from the XMP file.
123  * DEPRECATED! Recommended to use *.tga with devarea extensions or even *.png files.
124  */
125 T loadBitmapFromXMP(T)(ExtendibleBitmap xmp, string ID){
126 	static if(T.stringof == Bitmap4Bit.stringof || T.stringof == Bitmap8Bit.stringof){
127 		T result = new T(cast(ubyte[])xmp.getBitmap(ID),xmp.getXsize(ID),xmp.getYsize(ID),null);
128 		return result;
129 	}else static if(T.stringof == Bitmap16Bit.stringof){
130 		T result;// = new T(cast(ushort[])xmp.getBitmap(ID),xmp.getXsize(ID),xmp.getYsize(ID));
131 		switch(xmp.bitdepth[xmp.searchForID(ID)]){
132 			case "16bit":
133 				result = new T(cast(ushort[])xmp.getBitmap(ID),xmp.getXsize(ID),xmp.getYsize(ID));
134 				break;
135 			case "8bit":
136 				ushort[] subresult;
137 				ubyte[] input = cast(ubyte[])xmp.getBitmap(ID);
138 				subresult.length = input.length;
139 				for(int i ; i < subresult.length ; i++){
140 					subresult[i] = input[i];
141 				}
142 				result = new T(subresult,xmp.getXsize(ID),xmp.getYsize(ID));
143 				break;
144 			case "4bit":
145 				ushort[] subresult;
146 				ubyte[] input = cast(ubyte[])xmp.getBitmap(ID);
147 				subresult.length = input.length;
148 				for(int i ; i < subresult.length ; i++){
149 					if(i & 1)
150 						subresult[i] = input[i>>1]>>4;
151 					else
152 						subresult[i] = input[i>>1]&0b0000_1111;
153 				}
154 				result = new T(subresult,xmp.getXsize(ID),xmp.getYsize(ID));
155 				break;
156 			/*case "1bit":
157 
158 				break;*/
159 			default:
160 				throw new FileAccessException("Bitdepth error!");
161 		}
162 
163 		return result;
164 	}else static if(T.stringof == Bitmap32Bit.stringof){
165 		T result = new T(cast(Color[])xmp.getBitmap(ID),xmp.getXsize(ID),xmp.getYsize(ID));
166 		return result;
167 	}else static if(T.stringof == ABitmap.stringof){
168 
169 		switch(xmp.bitdepth[xmp.searchForID(ID)]){
170 			case "4bit":
171 				return new Bitmap4Bit(cast(ubyte[])xmp.getBitmap(ID),xmp.getXsize(ID),xmp.getYsize(ID));
172 			case "8bit":
173 				return new Bitmap8Bit(cast(ubyte[])xmp.getBitmap(ID),xmp.getXsize(ID),xmp.getYsize(ID));
174 			case "16bit":
175 				return new Bitmap16Bit(cast(ushort[])xmp.getBitmap(ID),xmp.getXsize(ID),xmp.getYsize(ID));
176 			case "32bit":
177 				return new Bitmap32Bit(cast(Color[])xmp.getBitmap(ID),xmp.getXsize(ID),xmp.getYsize(ID));
178 			default:
179 				return null;
180 
181 		}
182 
183 	}else static assert("Template argument \'" ~ T.stringof ~ "\' not supported in function \'T loadBitmapFromXMP(T)(ExtendibleBitmap xmp, string ID)\'");
184 }
185 /**
186  * Loads a palette from an XMP file.
187  * Deprecated!
188  */
189 public void loadPaletteFromXMP(ExtendibleBitmap xmp, string ID, Raster target, int offset = 0){
190 	target.palette = cast(Color[])xmp.getPalette(ID);
191 	//writeln(target.palette);
192 	/*target.setupPalette(0);
193 	int max = (palette.length / 3);
194 	for(int i ; i < max ; i++){
195 		target.addColor(palette[(i * 3)], palette[(i * 3) + 1], palette[(i * 3) + 2]);
196 		//writeln(i);
197 	}*/
198 
199 }
200 /**
201  * Loads a fontset from an XMP file.
202  * Deprecated!
203  */
204 /+Fontset!Bitmap8Bit loadFontsetFromXMP(ExtendibleBitmap xmp, string fontName){
205 	Bitmap8Bit[wchar] characters;
206 	foreach(s;xmp.bitmapID){
207 		//writeln(parseHex(s[fontName.length..(s.length-1)]));
208 		//if(fontName == s[0..(fontName.length-1)]){
209 		characters[to!wchar(parseHex(s[fontName.length..s.length]))] = loadBitmapFromXMP!Bitmap8Bit(xmp,s);
210 	}
211 	foreach(c;characters){
212 		for(int y; y < c.height; y++){
213 			for(int x; x < c.width; x++){
214 				if(c.readPixel(x,y)){
215 					c.writePixel(x,y, ubyte.max);
216 				}
217 			}
218 		}
219 	}
220 	return new Fontset!Bitmap8Bit(fontName, characters['0'].height, characters);
221 }+/
222 /**
223  * Loads a *.wav file if SDL2 mixer is used
224  */
225 public Mix_Chunk* loadSoundFromFile(const char* filename){
226 	return Mix_LoadWAV(filename);
227 }
228 
229 File loadFileFromDisk(string filename){
230 	return File(filename, "r");
231 }
232 
233 /**
234  * Implements the RIFF serialization system
235  */
236 public struct RIFFHeader{
237 	char[4] data;
238 	uint length;
239 }