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 }