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 import dimage.bmp;
26 
27 //import bitleveld.reinterpret;
28 
29 import vfile;
30 
31 import mididi;
32 
33 import sdl_mixer;
34 import std.utf;
35 
36 /** 
37  * Pads a scanline to be on size_t's bounds.
38  * Params:
39  *   scanline = The scanline to be padded.
40  * Returns: the padded scanline
41  */
42 package size_t[] padScanLine(ubyte[] scanline) @safe {
43 	const int extra = size_t.sizeof - (scanline.length % size_t.sizeof);
44 	if (extra)
45 		scanline.length = scanline.length + extra;
46 	return reinterpretCast!size_t(scanline);
47 }
48 /**
49  * Loads a bitmap slice from an image.
50  * Ideal for loading sprite sheets.
51  */
52 public T loadBitmapSliceFromImage(T)(Image img, int x, int y, int w, int h) {
53 	synchronized{T src = loadBitmapFromImage!T(img), result;
54 	result = src.window(x, y, x + w - 1, y + h - 1);
55 	return result;}
56 }
57 /**
58  * Loads an Image from a File or VFile.
59  * Automatically detects format from file extension.
60  */
61 public Image loadImage(F = File)(F file) @trusted{
62 	switch(extension(file.name)){
63 		case ".tga", ".TGA":
64 			TGA imageFile = TGA.load!(F, true, true)(file);
65 			if(!imageFile.getHeader.topOrigin){
66 				imageFile.flipVertical;
67 			}
68 			return imageFile;
69 		case ".png", ".PNG":
70 			PNG imageFile = PNG.load!F(file);
71 			return imageFile;
72 		case ".bmp", ".BMP":
73 			BMP imageFile = BMP.load!F(file);
74 			return imageFile;
75 		default:
76 			throw new Exception("Unsupported file format!");
77 	}
78 }
79 /**
80  * Loads a bitmap from Image.
81  */
82 public T loadBitmapFromImage(T)(Image img) @trusted
83 		if (is (T == Bitmap1Bit) || is(T == Bitmap2Bit) || is(T == Bitmap4Bit) || is(T == Bitmap8Bit) || 
84 		is(T == Bitmap16Bit) || is(T == Bitmap32Bit)) {
85 	// Later we might want to detect image type from classinfo, until then let's rely on similarities between types
86 	static if(is(T == Bitmap1Bit)){
87 		if (img.getBitdepth != 1)
88 			throw new BitmapFormatException("Bitdepth mismatch exception!");
89 		ubyte[] data = img.imageData.raw;
90 		size_t[] newData;
91 		const size_t pitch = data.length / img.imageData.height;
92 		for (int i ; i < img.imageData.height ; i++) {
93 			newData ~= padScanLine(data[pitch * i..pitch * (i + 1)]);
94 		}
95 		return new Bitmap1Bit(newData, img.imageData.width, img.imageData.height);
96 	} else static if(is(T == Bitmap2Bit)){
97 		if (img.getBitdepth == 2)
98 			return new Bitmap2Bit(img.imageData.raw, img.width, img.height);
99 		else if (img.getBitdepth < 2)
100 			return new Bitmap2Bit(img.imageData.convTo(PixelFormat.Indexed2Bit).raw, img.width, img.height);
101 		else
102 			throw new BitmapFormatException("Bitdepth mismatch exception!");	
103 	}else static if(is(T == Bitmap4Bit)){
104 		if (img.getBitdepth == 4)
105 			return new Bitmap4Bit(img.imageData.raw, img.width, img.height);
106 		else if (img.getBitdepth < 4)
107 			return new Bitmap4Bit(img.imageData.convTo(PixelFormat.Indexed4Bit).raw, img.width, img.height);
108 		else
109 			throw new BitmapFormatException("Bitdepth mismatch exception!");	
110 
111 	}else static if(is(T == Bitmap8Bit)){
112 		if (img.getBitdepth == 8)
113 			return new Bitmap8Bit(img.imageData.raw, img.width, img.height);
114 		else if (img.getBitdepth < 8)
115 			return new Bitmap8Bit(img.imageData.convTo(PixelFormat.Indexed8Bit).raw, img.width, img.height);
116 		else
117 			throw new BitmapFormatException("Bitdepth mismatch exception!");
118 
119 	}else static if(is(T == Bitmap16Bit)){
120 		if (img.getBitdepth == 16)
121 			return new Bitmap16Bit(reinterpretCast!ushort(img.imageData.raw), img.width, img.height);
122 		else if (img.getBitdepth < 16)
123 			return new Bitmap16Bit(reinterpretCast!ushort(img.imageData.convTo(PixelFormat.Indexed16Bit).raw), 
124 					img.width, img.height);
125 		else
126 			throw new BitmapFormatException("Bitdepth mismatch exception!");
127 		
128 	}else static if(is(T == Bitmap32Bit)){
129 		return new Bitmap32Bit(reinterpretCast!Color(img.imageData.convTo(PixelFormat.ARGB8888 | PixelFormat.BigEndian).raw), 
130 				img.width, img.height);
131 
132 	}
133 
134 }
135 //TODO: Make collision model loader for 1 bit bitmaps
136 /**
137  * Loads a bitmap from disk.
138  * Currently supported formats: *.tga, *.png, *.bmp
139  */
140 public T loadBitmapFromFile(T)(string filename)
141 		if (is (T == Bitmap1Bit) || is(T == Bitmap2Bit) || is(T == Bitmap4Bit) || is(T == Bitmap8Bit) || 
142 		is(T == Bitmap16Bit) || is(T == Bitmap32Bit)) {
143 	File f = File(filename);
144 	return loadBitmapFromImage!T(loadImage(f));
145 }
146 /**
147  * Loads a bitmap sheet from file.
148  * This one doesn't require TGA devarea extensions.
149  */
150 public T[] loadBitmapSheetFromFile(T)(string filename, int x, int y)
151 		if (is (T == Bitmap1Bit) || is(T == Bitmap2Bit) || is(T == Bitmap4Bit) || is(T == Bitmap8Bit) || 
152 		is(T == Bitmap16Bit) || is(T == Bitmap32Bit)) {
153 	//T source = loadBitmapFromFile!T(filename);
154 	return loadBitmapSheetFromImage!T(loadImage(File(filename)), x, y);
155 }
156 /**
157  * Creates a bitmap sheet from an image file.
158  * This one doesn't require embedded data.
159  */
160 public T[] loadBitmapSheetFromImage(T)(Image img, int x, int y)
161 		if (is (T == Bitmap1Bit) || is(T == Bitmap2Bit) || is(T == Bitmap4Bit) || is(T == Bitmap8Bit) || 
162 		is(T == Bitmap16Bit) || is(T == Bitmap32Bit)) {
163 	T source = loadBitmapFromImage!T(img);
164 	if (source.width % x == 0 && source.height % y == 0) {
165 		T[] output;
166 		static if (is(T == Bitmap1Bit))
167 			const size_t length = x / 8, length0 = x / 8, pitch = source.width / 8;
168 		else static if (is(T == Bitmap2Bit))
169 			const size_t length = x / 4, length0 = x / 4, pitch = source.width / 4;
170 		else static if (is(T == Bitmap4Bit))
171 			const size_t length = x / 2, length0 = x / 2, pitch = source.width / 2;
172 		else static if (is(T == Bitmap8Bit))
173 			const size_t length = x, length0 = x, pitch = source.width;
174 		else static if (is(T == Bitmap16Bit))
175 			const size_t length = x, length0 = x * 2, pitch = source.width;
176 		else static if (is(T == Bitmap32Bit))
177 			const size_t length = x, length0 = x * 4, pitch = source.width;
178 		const size_t pitch0 = pitch * y;
179 		output.reserve(source.height / y * source.width / x);
180 		for (int mY ; mY < source.height / y ; mY++){
181 			for (int mX ; mX < source.width / x ; mX++){
182 				T next = new T(x, y);
183 				for (int lY ; lY < y ; lY++){
184 					memcpy(next.getPtr + (lY * length), source.getPtr + (pitch * lY) + (pitch0 * mY) + (length * mX), length0);
185 				}
186 				output ~= next;
187 			}
188 		}
189 		return output;
190 	} else throw new Exception("Requested size cannot be divided by input file's sizes!");
191 }
192 /**
193  * Loads a palette from a file.
194  */
195 public Color[] loadPaletteFromFile(string filename) {
196 	File f = File(filename);
197 	switch(extension(filename)){
198 		case ".tga", ".TGA":
199 			TGA imageFile = TGA.load(f);
200 			return loadPaletteFromImage(imageFile);
201 		case ".png", ".PNG":
202 			PNG imageFile = PNG.load(f);
203 			return loadPaletteFromImage(imageFile);
204 		case ".bmp", ".BMP":
205 			BMP imageFile = BMP.load(f);
206 			return loadPaletteFromImage(imageFile);
207 		default:
208 			throw new Exception("Unsupported file format!");
209 	}
210 }
211 /**
212  * Loads a palette from image.
213  */
214 public Color[] loadPaletteFromImage (Image img) {
215 	Color[] palette;
216 	IPalette sourcePalette = img.palette.convTo(PixelFormat.ARGB8888 | PixelFormat.BigEndian);
217 	palette = reinterpretCast!Color(sourcePalette.raw);
218 
219 	assert(palette.length == sourcePalette.length, "Palette lenght import mismatch!");
220 	if(!(img.palette.paletteFormat & PixelFormat.ValidAlpha)){
221 		palette[0].a = 0x0;
222 		for(int i = 1; i < palette.length; i++) {
223 			palette[i].a = 0xFF;
224 		}
225 	}
226 	return palette;
227 }
228 package immutable string pathRoot;
229 shared static this () {
230 	import std.path;
231 	import std.file : exists;
232 	string path;
233 	version (Posix) {
234 		import core.sys.posix.unistd : getcwd;
235 		import std.string : fromStringz;
236 		{
237 			char[2048] _path;
238 			getcwd(_path.ptr, _path.length);
239 			path = fromStringz(_path).idup;
240 		}
241 	} else version (Windows) {
242 		import core.sys.windows.windows : GetCurrentDirectoryW, DWORD;
243 		import std.string : fromStringz;
244 		import std.utf : toUTF8;
245 		{
246 			wchar[2048] _path;
247 			GetCurrentDirectoryW(cast(DWORD)_path.length, _path.ptr);
248 			path = toUTF8(fromStringz(_path.ptr).idup);
249 		}
250 	} else static assert (0, "Please contact me about implementation!");
251 	if (exists(buildNormalizedPath(path, "../system/")))
252 		pathRoot = "..";
253 	else if (exists(buildNormalizedPath(path, "./system/")))
254 		pathRoot = ".";
255 	else {
256 		debug assert(0, "Folder /system/ does not exist! Check your development environment and the documentation for info.");
257 		else assert(0, "Folder /system/ does not exist! Please reinstall the software or contact the developer if that does 
258 				not solve the issue.");
259 		}
260 }
261 /** 
262  * 
263  * Params:
264  *   country = 
265  *   language = 
266  *   filename = 
267  * Returns: 
268  */
269 public string getPathToLocalizationFile (string country, string language, string filename) @safe pure nothrow {
270 	if (filename[0] == '.') filename = filename[1..$];
271 	return pathRoot ~ "/local/" ~ country ~ "-" ~ language ~ "." ~ filename;
272 }
273 ///NOTE: probably redo it for 1.0.0, or make a system that bypasses it.
274 public string getPathToAsset (string path) @safe pure nothrow {
275 	return pathRoot ~ "/" ~ path;
276 }
277 /**
278  * Implements the RIFF serialization system
279  */
280 public struct RIFFHeader{
281 	char[4] data;
282 	uint length;
283 }