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 }