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 }