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 bindbc.sdl.mixer;
34 
35 /** 
36  * Pads a scanline to be on size_t's bounds.
37  * Params:
38  *   scanline = The scanline to be padded.
39  * Returns: the padded scanline
40  */
41 package size_t[] padScanLine(ubyte[] scanline) @safe {
42 	const int extra = size_t.sizeof - (scanline.length % size_t.sizeof);
43 	if (extra)
44 		scanline.length = scanline.length + extra;
45 	return reinterpretCast!size_t(scanline);
46 }
47 /**
48  * Loads a bitmap slice from an image.
49  * Ideal for loading sprite sheets.
50  */
51 public T loadBitmapSliceFromImage(T)(Image img, int x, int y, int w, int h) {
52 	T src = loadBitmapFromImage!T(img);
53 	T result = src.window(x, y, x + w, y + h);
54 	return result;
55 }
56 /**
57  * Loads an Image from a File or VFile.
58  * Automatically detects format from file extension.
59  */
60 public Image loadImage(F = File)(F file) @trusted{
61 	switch(extension(file.name)){
62 		case ".tga", ".TGA":
63 			TGA imageFile = TGA.load!(F, true, true)(file);
64 			if(!imageFile.getHeader.topOrigin){
65 				imageFile.flipVertical;
66 			}
67 			return imageFile;
68 		case ".png", ".PNG":
69 			PNG imageFile = PNG.load!F(file);
70 			return imageFile;
71 		case ".bmp", ".BMP":
72 			BMP imageFile = BMP.load!F(file);
73 			return imageFile;
74 		default:
75 			throw new Exception("Unsupported file format!");
76 	}
77 }
78 /**
79  * Loads a bitmap from Image.
80  */
81 public T loadBitmapFromImage(T)(Image img) @trusted
82 		if (is (T == Bitmap1Bit) || is(T == Bitmap2Bit) || is(T == Bitmap4Bit) || is(T == Bitmap8Bit) || 
83 		is(T == Bitmap16Bit) || is(T == Bitmap32Bit)) {
84 	// Later we might want to detect image type from classinfo, until then let's rely on similarities between types
85 	static if(is(T == Bitmap1Bit)){
86 		if (img.getBitdepth != 1)
87 			throw new BitmapFormatException("Bitdepth mismatch exception!");
88 		ubyte[] data = img.imageData.raw;
89 		size_t[] newData;
90 		const size_t pitch = data.length / img.imageData.height;
91 		for (int i ; i < img.imageData.height ; i++) {
92 			newData ~= padScanLine(data[pitch * i..pitch * (i + 1)]);
93 		}
94 		return new Bitmap1Bit(newData, img.imageData.width, img.imageData.height);
95 	} else static if(is(T == Bitmap2Bit)){
96 		if (img.getBitdepth == 2)
97 			return new Bitmap2Bit(img.imageData.raw, img.width, img.height);
98 		else if (img.getBitdepth < 2)
99 			return new Bitmap2Bit(img.imageData.convTo(PixelFormat.Indexed2Bit).raw, img.width, img.height);
100 		else
101 			throw new BitmapFormatException("Bitdepth mismatch exception!");	
102 	}else static if(is(T == Bitmap4Bit)){
103 		if (img.getBitdepth == 4)
104 			return new Bitmap4Bit(img.imageData.raw, img.width, img.height);
105 		else if (img.getBitdepth < 4)
106 			return new Bitmap4Bit(img.imageData.convTo(PixelFormat.Indexed4Bit).raw, img.width, img.height);
107 		else
108 			throw new BitmapFormatException("Bitdepth mismatch exception!");	
109 
110 	}else static if(is(T == Bitmap8Bit)){
111 		if (img.getBitdepth == 8)
112 			return new Bitmap8Bit(img.imageData.raw, img.width, img.height);
113 		else if (img.getBitdepth < 8)
114 			return new Bitmap8Bit(img.imageData.convTo(PixelFormat.Indexed8Bit).raw, img.width, img.height);
115 		else
116 			throw new BitmapFormatException("Bitdepth mismatch exception!");
117 
118 	}else static if(is(T == Bitmap16Bit)){
119 		if (img.getBitdepth == 16)
120 			return new Bitmap16Bit(reinterpretCast!ushort(img.imageData.raw), img.width, img.height);
121 		else if (img.getBitdepth < 16)
122 			return new Bitmap16Bit(reinterpretCast!ushort(img.imageData.convTo(PixelFormat.Indexed16Bit).raw), 
123 					img.width, img.height);
124 		else
125 			throw new BitmapFormatException("Bitdepth mismatch exception!");
126 		
127 	}else static if(is(T == Bitmap32Bit)){
128 		return new Bitmap32Bit(reinterpretCast!Color(img.imageData.convTo(PixelFormat.ARGB8888 | PixelFormat.BigEndian).raw), 
129 				img.width, img.height);
130 
131 	}
132 
133 }
134 //TODO: Make collision model loader for 1 bit bitmaps
135 /**
136  * Loads a bitmap from disk.
137  * Currently supported formats: *.tga, *.png, *.bmp
138  */
139 public T loadBitmapFromFile(T)(string filename)
140 		if (is (T == Bitmap1Bit) || is(T == Bitmap2Bit) || is(T == Bitmap4Bit) || is(T == Bitmap8Bit) || 
141 		is(T == Bitmap16Bit) || is(T == Bitmap32Bit)) {
142 	File f = File(filename);
143 	return loadBitmapFromImage!T(loadImage(f));
144 }
145 /**
146  * Loads a bitmap sheet from file.
147  * This one doesn't require TGA devarea extensions.
148  */
149 public T[] loadBitmapSheetFromFile(T)(string filename, int x, int y)
150 		if (is (T == Bitmap1Bit) || is(T == Bitmap2Bit) || is(T == Bitmap4Bit) || is(T == Bitmap8Bit) || 
151 		is(T == Bitmap16Bit) || is(T == Bitmap32Bit)) {
152 	//T source = loadBitmapFromFile!T(filename);
153 	return loadBitmapSheetFromImage!T(loadImage(File(filename)), x, y);
154 }
155 /**
156  * Creates a bitmap sheet from an image file.
157  * This one doesn't require embedded data.
158  */
159 public T[] loadBitmapSheetFromImage(T)(Image img, int x, int y)
160 		if (is (T == Bitmap1Bit) || is(T == Bitmap2Bit) || is(T == Bitmap4Bit) || is(T == Bitmap8Bit) || 
161 		is(T == Bitmap16Bit) || is(T == Bitmap32Bit)) {
162 	T source = loadBitmapFromImage!T(img);
163 	if (source.width % x == 0 && source.height % y == 0) {
164 		T[] output;
165 		static if (is(T == Bitmap1Bit))
166 			const size_t length = x / 8, length0 = x / 8, pitch = source.width / 8;
167 		else static if (is(T == Bitmap2Bit))
168 			const size_t length = x / 4, length0 = x / 4, pitch = source.width / 4;
169 		else static if (is(T == Bitmap4Bit))
170 			const size_t length = x / 2, length0 = x / 2, pitch = source.width / 2;
171 		else static if (is(T == Bitmap8Bit))
172 			const size_t length = x, length0 = x, pitch = source.width;
173 		else static if (is(T == Bitmap16Bit))
174 			const size_t length = x, length0 = x * 2, pitch = source.width;
175 		else static if (is(T == Bitmap32Bit))
176 			const size_t length = x, length0 = x * 4, pitch = source.width;
177 		const size_t pitch0 = pitch * y;
178 		output.reserve(source.height / y * source.width / x);
179 		for (int mY ; mY < source.height / y ; mY++){
180 			for (int mX ; mX < source.width / x ; mX++){
181 				T next = new T(x, y);
182 				for (int lY ; lY < y ; lY++){
183 					memcpy(next.getPtr + (lY * length), source.getPtr + (pitch * lY) + (pitch0 * mY) + (length * mX), length0);
184 				}
185 				output ~= next;
186 			}
187 		}
188 		return output;
189 	} else throw new Exception("Requested size cannot be divided by input file's sizes!");
190 }
191 /**
192  * Loads a palette from a file.
193  */
194 public Color[] loadPaletteFromFile(string filename) {
195 	File f = File(filename);
196 	switch(extension(filename)){
197 		case ".tga", ".TGA":
198 			TGA imageFile = TGA.load(f);
199 			return loadPaletteFromImage(imageFile);
200 		case ".png", ".PNG":
201 			PNG imageFile = PNG.load(f);
202 			return loadPaletteFromImage(imageFile);
203 		case ".bmp", ".BMP":
204 			BMP imageFile = BMP.load(f);
205 			return loadPaletteFromImage(imageFile);
206 		default:
207 			throw new Exception("Unsupported file format!");
208 	}
209 }
210 /**
211  * Loads a palette from image.
212  */
213 public Color[] loadPaletteFromImage (Image img) {
214 	Color[] palette;
215 	IPalette sourcePalette = img.palette.convTo(PixelFormat.ARGB8888 | PixelFormat.BigEndian);
216 	palette = reinterpretCast!Color(sourcePalette.raw);
217 
218 	assert(palette.length == sourcePalette.length, "Palette lenght import mismatch!");
219 	if(!(img.palette.paletteFormat & PixelFormat.ValidAlpha)){
220 		palette[0].a = 0x0;
221 		for(int i = 1; i < palette.length; i++) {
222 			palette[i].a = 0xFF;
223 		}
224 	}
225 	return palette;
226 }
227 
228 /**
229  * Loads a *.wav file if SDL2 mixer is used
230  * Deprecated, use pixelperfect.audio instead!
231  */
232 public deprecated Mix_Chunk* loadSoundFromFile(const char* filename){
233 	return Mix_LoadWAV(filename);
234 }
235 
236 File loadFileFromDisk(string filename){
237 	return File(filename, "r");
238 }
239 
240 MIDI loadMidiFile(F)(F source) {
241 	ubyte[] src;
242 	src.length = cast(size_t)source.size;
243 	src = source.rawRead(src);
244 	MIDIReader!(ubyte[]) reader = MIDIReader!(ubyte[])(src);
245 	return reader.readMIDI();
246 }
247 
248 /**
249  * Implements the RIFF serialization system
250  */
251 public struct RIFFHeader{
252 	char[4] data;
253 	uint length;
254 }