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 }