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 public import dimage.base;
23 import dimage.tga;
24 import dimage.png;
25 
26 import vfile;
27 
28 import bindbc.sdl.mixer;
29 
30 /**
31  * Loads an Image from a File or VFile.
32  * Automatically detects format from file extension.
33  */
34 public Image loadImage(F = File)(F file) @trusted{
35 	switch(extension(file.name)){
36 		case ".tga", ".TGA":
37 			TGA imageFile = TGA.load!(F, true, true)(file);
38 			if(!imageFile.getHeader.topOrigin){
39 				imageFile.flipVertical;
40 			}
41 			return imageFile;
42 		case ".png", ".PNG":
43 			PNG imageFile = PNG.load!File(file);
44 			return imageFile;
45 		default:
46 			throw new Exception("Unsupported file format!");
47 	}
48 }
49 /**
50  * Loads a bitmap from Image.
51  */
52 public T loadBitmapFromImage(T)(Image img) @trusted
53 		if(T.stringof == Bitmap4Bit.stringof || T.stringof == Bitmap8Bit.stringof || T.stringof == Bitmap16Bit.stringof
54 		|| T.stringof == Bitmap32Bit.stringof){
55 	// Later we might want to detect image type from classinfo, until then let's rely on similarities between types
56 	static if(T.stringof == Bitmap4Bit.stringof){
57 		if(img.getBitdepth != 4)
58 			throw new BitmapFormatException("Bitdepth mismatch exception!");
59 		return new Bitmap4Bit(img.getImageData, img.width, img.height);
60 	}else static if(T.stringof == Bitmap8Bit.stringof){
61 		if(img.getBitdepth != 8)
62 			throw new BitmapFormatException("Bitdepth mismatch exception!");
63 		return new Bitmap8Bit(img.getImageData, img.width, img.height);
64 	}else static if(T.stringof == Bitmap16Bit.stringof){
65 		if(img.getBitdepth != 16)
66 			throw new BitmapFormatException("Bitdepth mismatch exception!");
67 		return new Bitmap16Bit(reinterpretCast!ushort(img.getImageData), img.width, img.height);
68 	}else static if(T.stringof == Bitmap32Bit.stringof){
69 		if(img.getBitdepth != 32)
70 			throw new BitmapFormatException("Bitdepth mismatch exception!");
71 		return new Bitmap32Bit(reinterpretCast!Color(img.getImageData), img.width, img.height);
72 	}
73 
74 }
75 //TODO: Make collision model loader for 1 bit bitmaps
76 /**
77  * Loads a bitmap from disk.
78  * Currently supported formats: *.tga, *.png
79  */
80 public T loadBitmapFromFile(T)(string filename)
81 		if(T.stringof == Bitmap4Bit.stringof || T.stringof == Bitmap8Bit.stringof || T.stringof == Bitmap16Bit.stringof
82 		|| T.stringof == Bitmap32Bit.stringof){
83 	File f = File(filename);
84 	switch(extension(filename)){
85 		case ".tga", ".TGA":
86 			TGA imageFile = TGA.load!(File, false, false)(f);
87 			if(!imageFile.getHeader.topOrigin){
88 				imageFile.flipVertical;
89 			}
90 			static if(T.stringof == Bitmap4Bit.stringof){
91 				if(imageFile.getBitdepth != 4)
92 					throw new BitmapFormatException("Bitdepth mismatch exception!");
93 				return new Bitmap4Bit(imageFile.getImageData, imageFile.width, imageFile.height);
94 			}else static if(T.stringof == Bitmap8Bit.stringof){
95 				if(imageFile.getBitdepth != 8)
96 					throw new BitmapFormatException("Bitdepth mismatch exception!");
97 				return new Bitmap8Bit(imageFile.getImageData, imageFile.width, imageFile.height);
98 			}else static if(T.stringof == Bitmap16Bit.stringof){
99 				if(imageFile.getBitdepth != 16)
100 					throw new BitmapFormatException("Bitdepth mismatch exception!");
101 				return new Bitmap16Bit(imageFile.getImageData, imageFile.width, imageFile.height);
102 			}else static if(T.stringof == Bitmap32Bit.stringof){
103 				if(imageFile.getBitdepth != 32)
104 					throw new BitmapFormatException("Bitdepth mismatch exception!");
105 				return new Bitmap32Bit(imageFile.getImageData, imageFile.width, imageFile.height);
106 			}
107 		case ".png", ".PNG":
108 			PNG imageFile = PNG.load!File(f);
109 			static if(T.stringof == Bitmap8Bit.stringof){
110 				if(imageFile.getBitdepth != 8)
111 					throw new BitmapFormatException("Bitdepth mismatch exception!");
112 				return new Bitmap8Bit(imageFile.getImageData, imageFile.width, imageFile.height);
113 			}else static if(T.stringof == Bitmap32Bit.stringof){
114 				if(imageFile.getBitdepth != 32)
115 					throw new BitmapFormatException("Bitdepth mismatch exception!");
116 				return new Bitmap32Bit(imageFile.getImageData, imageFile.width, imageFile.height);
117 			}
118 		default:
119 			throw new Exception("Unsupported file format!");
120 	}
121 }
122 /**
123  * Loads a bitmap sheet from file.
124  * This one doesn't require TGA devarea extensions.
125  */
126 public T[] loadBitmapSheetFromFile(T)(string filename, int x, int y)
127 		if(T.stringof == Bitmap4Bit.stringof || T.stringof == Bitmap8Bit.stringof || T.stringof == Bitmap16Bit.stringof
128 		|| T.stringof == Bitmap32Bit.stringof) {
129 	T source = loadBitmapFromFile!T(filename);
130 	if(source.width % x == 0 && source.height % y == 0){
131 		T[] output;
132 		static if (T.stringof == Bitmap4Bit.stringof)
133 			const size_t length = x / 2, pitch = source.width / 2;
134 		else static if (T.stringof == Bitmap8Bit.stringof)
135 			const size_t length = x, pitch = source.width;
136 		else static if (T.stringof == Bitmap16Bit.stringof)
137 			const size_t length = x * 2, pitch = source.width * 2;
138 		else static if (T.stringof == Bitmap32Bit.stringof)
139 			const size_t length = x * 4, pitch = source.width * 4;
140 		const size_t pitch0 = pitch * y;
141 		for (int mY ; mY < source.height / y ; mY++){
142 			for (int mX ; mX < source.width / x ; mX++){
143 				T next = new T(x, y);
144 				for (int lY ; lY < y ; lY++){
145 					memcpy(next.getPtr + (lY * length), source.getPtr + (pitch * lY) + (pitch0 * mY) + (length * mX), length);
146 				}
147 				output ~= next;
148 			}
149 		}
150 		return output;
151 	}else throw new Exception("Requested size cannot be divided by input file's sizes!");
152 }
153 /**
154  * Creates a bitmap sheet from an image file.
155  * This one doesn't require embedded data.
156  */
157 public T[] loadBitmapSheetFromImage(T)(Image img, int x, int y)
158 		if(T.stringof == Bitmap4Bit.stringof || T.stringof == Bitmap8Bit.stringof || T.stringof == Bitmap16Bit.stringof
159 		|| T.stringof == Bitmap32Bit.stringof) {
160 	T source = loadBitmapFromImage!T(img);
161 	if(source.width % x == 0 && source.height % y == 0){
162 		T[] output;
163 		static if (T.stringof == Bitmap4Bit.stringof)
164 			const size_t length = x / 2, pitch = source.width / 2;
165 		else static if (T.stringof == Bitmap8Bit.stringof)
166 			const size_t length = x, pitch = source.width;
167 		else static if (T.stringof == Bitmap16Bit.stringof)
168 			const size_t length = x * 2, pitch = source.width * 2;
169 		else static if (T.stringof == Bitmap32Bit.stringof)
170 			const size_t length = x * 4, pitch = source.width * 4;
171 		const size_t pitch0 = pitch * y;
172 		for (int mY ; mY < source.height / y ; mY++){
173 			for (int mX ; mX < source.width / x ; mX++){
174 				T next = new T(x, y);
175 				for (int lY ; lY < y ; lY++){
176 					memcpy(next.getPtr + (lY * length), source.getPtr + (pitch * lY) + (pitch0 * mY) + (length * mX), length);
177 				}
178 				output ~= next;
179 			}
180 		}
181 		return output;
182 	}else throw new Exception("Requested size cannot be divided by input file's sizes!");
183 }
184 /**
185  * Loads a palette from a file.
186  */
187 public Color[] loadPaletteFromFile(string filename) {
188 	File f = File(filename);
189 	switch(extension(filename)){
190 		case ".tga", ".TGA":
191 			TGA imageFile = TGA.load(f);
192 			return loadPaletteFromImage(imageFile);
193 		case ".png", ".PNG":
194 			PNG imageFile = PNG.load(f);
195 			return loadPaletteFromImage(imageFile);
196 		default:
197 			throw new Exception("Unsupported file format!");
198 	}
199 }
200 /**
201  * Loads a palette from image.
202  */
203 public Color[] loadPaletteFromImage (Image img) {
204 	Color[] result;
205 	result.reserve(img.palette.length);
206 	for (ushort i ; i < img.palette.length ; i++) {
207 		auto origin = img.palette[i];
208 		result ~= Color(origin.a, origin.r, origin.g, origin.b);
209 	}
210 	return result;
211 }
212 /**
213  * Gets a bitmap from the XMP file.
214  * DEPRECATED! Recommended to use *.tga with devarea extensions or even *.png files.
215  */
216 deprecated T loadBitmapFromXMP(T)(ExtendibleBitmap xmp, string ID){
217 	static if(T.stringof == Bitmap4Bit.stringof || T.stringof == Bitmap8Bit.stringof){
218 		T result = new T(cast(ubyte[])xmp.getBitmap(ID),xmp.getXsize(ID),xmp.getYsize(ID),null);
219 		return result;
220 	}else static if(T.stringof == Bitmap16Bit.stringof){
221 		T result;// = new T(cast(ushort[])xmp.getBitmap(ID),xmp.getXsize(ID),xmp.getYsize(ID));
222 		switch(xmp.bitdepth[xmp.searchForID(ID)]){
223 			case "16bit":
224 				result = new T(cast(ushort[])xmp.getBitmap(ID),xmp.getXsize(ID),xmp.getYsize(ID));
225 				break;
226 			case "8bit":
227 				ushort[] subresult;
228 				ubyte[] input = cast(ubyte[])xmp.getBitmap(ID);
229 				subresult.length = input.length;
230 				for(int i ; i < subresult.length ; i++){
231 					subresult[i] = input[i];
232 				}
233 				result = new T(subresult,xmp.getXsize(ID),xmp.getYsize(ID));
234 				break;
235 			case "4bit":
236 				ushort[] subresult;
237 				ubyte[] input = cast(ubyte[])xmp.getBitmap(ID);
238 				subresult.length = input.length;
239 				for(int i ; i < subresult.length ; i++){
240 					if(i & 1)
241 						subresult[i] = input[i>>1]>>4;
242 					else
243 						subresult[i] = input[i>>1]&0b0000_1111;
244 				}
245 				result = new T(subresult,xmp.getXsize(ID),xmp.getYsize(ID));
246 				break;
247 			/*case "1bit":
248 
249 				break;*/
250 			default:
251 				throw new FileAccessException("Bitdepth error!");
252 		}
253 
254 		return result;
255 	}else static if(T.stringof == Bitmap32Bit.stringof){
256 		T result = new T(cast(Color[])xmp.getBitmap(ID),xmp.getXsize(ID),xmp.getYsize(ID));
257 		return result;
258 	}else static if(T.stringof == ABitmap.stringof){
259 
260 		switch(xmp.bitdepth[xmp.searchForID(ID)]){
261 			case "4bit":
262 				return new Bitmap4Bit(cast(ubyte[])xmp.getBitmap(ID),xmp.getXsize(ID),xmp.getYsize(ID));
263 			case "8bit":
264 				return new Bitmap8Bit(cast(ubyte[])xmp.getBitmap(ID),xmp.getXsize(ID),xmp.getYsize(ID));
265 			case "16bit":
266 				return new Bitmap16Bit(cast(ushort[])xmp.getBitmap(ID),xmp.getXsize(ID),xmp.getYsize(ID));
267 			case "32bit":
268 				return new Bitmap32Bit(cast(Color[])xmp.getBitmap(ID),xmp.getXsize(ID),xmp.getYsize(ID));
269 			default:
270 				return null;
271 
272 		}
273 
274 	}else static assert("Template argument \'" ~ T.stringof ~ "\' not supported in function \'T loadBitmapFromXMP(T)(ExtendibleBitmap xmp, string ID)\'");
275 }
276 /**
277  * Loads a palette from an XMP file.
278  * Deprecated!
279  */
280 public void loadPaletteFromXMP(ExtendibleBitmap xmp, string ID, Raster target, int offset = 0){
281 	target.palette = cast(Color[])xmp.getPalette(ID);
282 
283 
284 }
285 
286 /**
287  * Loads a *.wav file if SDL2 mixer is used
288  */
289 public Mix_Chunk* loadSoundFromFile(const char* filename){
290 	return Mix_LoadWAV(filename);
291 }
292 
293 File loadFileFromDisk(string filename){
294 	return File(filename, "r");
295 }
296 
297 /**
298  * Implements the RIFF serialization system
299  */
300 public struct RIFFHeader{
301 	char[4] data;
302 	uint length;
303 }