1 /* 2 * dimage - tga.d 3 * by Laszlo Szeremi 4 * 5 * Copyright under Boost Software License. 6 */ 7 8 module dimage.tga; 9 10 import std.bitmanip; 11 static import std.stdio; 12 13 public import dimage.base; 14 import dimage.util; 15 import bitleveld.datatypes; 16 17 //import core.stdc.string; 18 import std.conv : to; 19 20 /** 21 * Implements the Truevision Graphics bitmap file format (*.tga) with some extra capabilities at the cost 22 * of making them unusable in applications not implementing these features: 23 * 24 * * Capability of storing 1, 2, and 4 bit indexed images. 25 * * External color map support. 26 * * More coming soon such as more advanced compression methods. 27 * 28 * Accessing developer area is fully implemented, accessing extension area is partly implemented. 29 */ 30 public class TGA : Image, ImageMetadata{ 31 /** 32 * Implements Truevision Graphics bitmap header. 33 */ 34 public struct Header { 35 align(1) : 36 /** 37 * Defines the type of the color map. 38 */ 39 public enum ColorMapType{ 40 NoColorMapPresent = 0, 41 ColorMapPresent = 1, 42 /** 43 * In this case, the palette is stored in a *.pal file, colorMapLength specifies the lenght of the filename, and the usual colorMap field instead stores the filename. 44 */ 45 ExtColorMap = 128, 46 } 47 /** 48 * Defines the type of the image. 49 */ 50 enum ImageType : ubyte { 51 NoData = 0, 52 UncompressedMapped = 1, 53 UncompressedTrueColor = 2, 54 UncompressedGrayscale = 3, 55 RLEMapped = 9, /// RLE in 8 or 16 bit chunks 56 RLETrueColor = 10, 57 RLEGrayscale = 11, 58 /** 59 * RLE optimized for 4 bit bitmaps. Added by me. Also works with 2 and 1 bit bitmaps 60 * Packet layout: 61 * bits 0 - 3: index needs to written 62 * bits 4 - 7: repeated occurence of indexes + 1 (1-16) 63 */ 64 RLE4BitMapped = 12, 65 /** 66 * RLE optimized for 1 bit bitmaps. Added by me. 67 * Packet layout: 68 * Every odd numbered byte: n of zeros (0-255) 69 * Every even numbered byte: n of ones (0-255) 70 */ 71 RLE1BitMapped = 13, 72 /** 73 * Mapped image with Huffman-Delta-RLE compression. 74 * I can't find any info on how this works, so I currently just leaving it as a placeholder 75 */ 76 HDRLEMapped = 32, 77 /** 78 * Mapped image with Huffman-Delta-RLE compression with 4-pass quadtree-type process. 79 * I can't find any info on how this works, so I currently just leaving it as a placeholder 80 */ 81 HDRLEMappedQT = 33 82 } 83 ubyte idLength; /// length in bytes 84 ubyte colorMapType; /// See ColorMapType enumerator 85 ubyte imageType; /// See ImageType enumerator 86 ushort colorMapOffset; /// index of first actual map entry 87 ushort colorMapLength; /// number of total entries (incl. skipped) 88 ubyte colorMapDepth; /// bits per pixel (entry) 89 ushort xOrigin; /// X origin of the image on the screen 90 ushort yOrigin; /// Y origin of the image on the screen 91 ushort width; /// Image width 92 ushort height; /// Image height 93 ubyte pixelDepth; /// bits per pixel 94 //imageDescriptor: 95 mixin(bitfields!( 96 ubyte, "alphaChannelBits", 4, 97 bool , "rightSideOrigin", 1, 98 bool , "topOrigin", 1, 99 ubyte, "interleaving", 2, 100 )); 101 ///Sets the colormapdepth of the image 102 public void setColorMapDepth(uint format) @safe pure { 103 switch (format) { 104 case PixelFormat.Grayscale8Bit: 105 colorMapDepth = 8; 106 break; 107 case PixelFormat.RGBX5551, PixelFormat.RGBA5551: 108 colorMapDepth = 16; 109 break; 110 /+case PixelFormat.RGB565: 111 colorMapDepth = 16; 112 break;+/ 113 case PixelFormat.RGB888: 114 colorMapDepth = 24; 115 break; 116 case PixelFormat.RGBX8888, PixelFormat.RGBA8888: 117 colorMapDepth = 32; 118 break; 119 default: throw new ImageFormatException("Palette format not supported!"); 120 } 121 } 122 ///Sets the pixeldepth of the image 123 public void setImageType(uint format, bool isRLE) @safe pure { 124 switch (format) { 125 case PixelFormat.Grayscale8Bit: 126 pixelDepth = 8; 127 if (isRLE) imageType = ImageType.RLEGrayscale; 128 else imageType = ImageType.UncompressedGrayscale; 129 break; 130 case PixelFormat.Indexed8Bit: 131 pixelDepth = 8; 132 if (isRLE) imageType = ImageType.RLEMapped; 133 else imageType = ImageType.UncompressedMapped; 134 break; 135 case PixelFormat.Indexed16Bit: 136 pixelDepth = 16; 137 if (isRLE) imageType = ImageType.RLEMapped; 138 else imageType = ImageType.UncompressedMapped; 139 break; 140 case PixelFormat.Indexed4Bit: 141 pixelDepth = 4; 142 if (isRLE) imageType = ImageType.RLE4BitMapped; 143 else imageType = ImageType.UncompressedMapped; 144 break; 145 case PixelFormat.Indexed2Bit: 146 pixelDepth = 2; 147 if (isRLE) imageType = ImageType.RLEMapped; 148 else imageType = ImageType.UncompressedMapped; 149 break; 150 case PixelFormat.Indexed1Bit: 151 pixelDepth = 1; 152 if (isRLE) imageType = ImageType.RLE1BitMapped; 153 else imageType = ImageType.UncompressedMapped; 154 break; 155 case PixelFormat.RGBA5551, PixelFormat.RGBX5551: 156 pixelDepth = 16; 157 alphaChannelBits = 1; 158 if (isRLE) imageType = ImageType.RLETrueColor; 159 else imageType = ImageType.UncompressedTrueColor; 160 break; 161 case PixelFormat.RGB888: 162 pixelDepth = 24; 163 if (isRLE) imageType = ImageType.RLETrueColor; 164 else imageType = ImageType.UncompressedTrueColor; 165 break; 166 case PixelFormat.RGBX8888, PixelFormat.RGBA8888: 167 pixelDepth = 32; 168 alphaChannelBits = 8; 169 if (isRLE) imageType = ImageType.RLETrueColor; 170 else imageType = ImageType.UncompressedTrueColor; 171 break; 172 default: throw new ImageFormatException("Palette format not supported!"); 173 } 174 } 175 public string toString(){ 176 import std.conv : to; 177 return 178 "idLength:" ~ to!string(idLength) ~ "\n" ~ 179 "colorMapType:" ~ to!string(colorMapType) ~ "\n" ~ 180 "imageType:" ~ to!string(imageType) ~ "\n" ~ 181 "colorMapOffset:" ~ to!string(colorMapOffset) ~ "\n" ~ 182 "colorMapLength:" ~ to!string(colorMapLength) ~ "\n" ~ 183 "colorMapDepth:" ~ to!string(colorMapDepth) ~ "\n" ~ 184 "xOrigin:" ~ to!string(xOrigin) ~ "\n" ~ 185 "yOrigin:" ~ to!string(yOrigin) ~ "\n" ~ 186 "width:" ~ to!string(width) ~ "\n" ~ 187 "height:" ~ to!string(height) ~ "\n" ~ 188 "pixelDepth:" ~ to!string(pixelDepth); 189 190 } 191 } 192 /** 193 * Implements Truevision Graphics bitmap footer, which is used to indicate the locations of extra fields. 194 */ 195 struct Footer { 196 align(1) : 197 uint extensionAreaOffset; /// offset of the extensionArea, zero if doesn't exist 198 uint developerAreaOffset; /// offset of the developerArea, zero if doesn't exist 199 char[16] signature = "TRUEVISION-XFILE"; /// if equals with "TRUEVISION-XFILE", it's the new format 200 char reserved = '.'; /// should be always a dot 201 ubyte terminator; /// terminates the file, always null 202 ///Returns true if it's a valid TGA footer 203 @property bool isValid(){ 204 return signature == "TRUEVISION-XFILE" && reserved == '.' && terminator == 0; 205 } 206 } 207 /** 208 * Contains extended data, mostly metadata. 209 */ 210 struct ExtArea{ 211 /** 212 * Stores attributes about the alpha channel. 213 */ 214 enum Attributes : ubyte{ 215 NoAlpha = 0, 216 UndefinedAlphaCanBeIgnored = 1, 217 UndefinedAlphaMustBePreserved = 2, 218 UsefulAlpha = 4, 219 PreMultipliedAlpha = 5 220 } 221 align(1) : 222 ushort size = cast(ushort)ExtArea.sizeof; ///size of this field (should be ExtArea.sizeof) 223 char[41] authorName; ///Name of the author 224 char[324] authorComments; ///Stores author comments 225 /** 226 * Stores the datetime in the following format 227 * 0: Year 228 * 1: Month 229 * 2: Day 230 * 3: Hour 231 * 4: Minute 232 * 5: Second 233 */ 234 ushort[6] dateTimeStamp; 235 char[41] jobName; ///Name of the job 236 /** 237 * Time of the job in the following format: 238 * 0: Hours 239 * 1: Minutes 240 * 2: Seconds 241 */ 242 ushort[3] jobTime; 243 char[41] softwareID; ///Stores the name of the software 244 ushort softwareVersNum; ///Stores the version of the software in a decimal system in the following format: 000.0.0 245 char softwareVersChar; ///Stores the version of the software 246 ubyte[4] keyColor; ///Key color, mostly used for greenscreening and similar thing 247 ushort pixelWidth; ///Pixel width ratio 248 ushort pixelHeight; ///Pixel height ratio 249 ushort gammaNumerator; ///Gamma correction 250 ushort gammaDenominator; ///Gamma correction 251 uint colorCorrectionOffset; ///Color correction field. The library cannot process this information. 252 uint postageStampOffset; ///Thumbnail image offset 253 uint scanlineOffset; ///Fast access to scanline offsets. The library can create it and load it, but doesn't use it since it decompresses RLE images upon load 254 ubyte attributes; ///Information on the alpha channel 255 } 256 /** 257 * Identifies the embedded data. 258 */ 259 struct DevAreaTag{ 260 ushort reserved; /// number of tags in the beginning 261 /** 262 * Identifier tag of the developer area field. 263 * Supposedly the range of 32768 - 65535 is reserved by Truevision, however there are no information on whenever it was 264 * used by them or not. 265 */ 266 ushort tag; 267 uint offset; /// offset into file 268 uint fieldSize; /// field size in bytes 269 } 270 /** 271 * Represents embedded data within the developer area 272 */ 273 struct DevArea{ 274 ubyte[] data; 275 /** 276 * Returns the data as a certain type (preferrably struct) if available 277 */ 278 T get(T)(){ 279 if(T.sizeof == data.length){ 280 return cast(T)(cast(void[])data); 281 } 282 } 283 } 284 protected Header header; 285 protected Footer footer; 286 protected char[] imageID; 287 protected ExtArea[] extensionArea; 288 protected DevAreaTag[] developerAreaTags; 289 protected DevArea[] developerArea; 290 protected size_t pitch; 291 public uint[] scanlineTable; ///stores offset to scanlines 292 public ubyte[] postageStampImage; ///byte 0 is width, byte 1 is height 293 public ushort[] colorCorrectionTable; ///color correction table 294 ///Empty CTOR for loading 295 protected this() @nogc @safe pure nothrow { 296 297 } 298 /** 299 * Creates a TGA file without a footer. 300 * Params: 301 * header = The header to be used for the image. Must match the values of _imageData and such. 302 * _imageData = The imagedata to be used for the imagefile. 303 * _palette = Palette data if any. 304 * imageID = Image name. 305 */ 306 public this(Header header, IImageData _imageData, IPalette _palette = null, char[] imageID = null) @safe { 307 this.header = header; 308 this._imageData = _imageData; 309 this._palette = _palette; 310 this.imageID = imageID; 311 } 312 /** 313 * Creates a TGA file safely with automatically generated header. 314 * This constructor is the recommended one for general use. 315 * Params: 316 * _imageData = The imagedata to be used for the imagefile. Automatically adjusts the header format to this. 317 * _palette = Palette data if any. 318 * imageID = Image name. 319 * isRLE = If true, then RLE compression will be used. 320 */ 321 public this(IImageData _imageData, IPalette _palette = null, char[] imageID = null, bool isRLE = false) @safe { 322 this._imageData = _imageData; 323 this._palette = _palette; 324 this.imageID = imageID; 325 header.width = cast(ushort)_imageData.width; 326 header.height = cast(ushort)_imageData.height; 327 if(_palette) { 328 header.colorMapLength = cast(ushort)this._palette.length; 329 header.colorMapDepth = this._palette.bitDepth; 330 header.colorMapType = Header.ColorMapType.ColorMapPresent; 331 } 332 header.setImageType(_imageData.pixelFormat, isRLE); 333 } 334 /** 335 * Creates a TGA file with a footer. 336 * Params: 337 * header = The header to be used for the image. Must match the values of _imageData and such. 338 * footer = The footer to be used for the image. 339 * _imageData = The imagedata to be used for the imagefile. 340 * _palette = Palette data if any. 341 * imageID = Image name. 342 */ 343 public this(Header header, Footer footer, IImageData _imageData, IPalette _palette = null, char[] imageID = null) @safe { 344 this.header = header; 345 this.footer = footer; 346 this._imageData = _imageData; 347 this._palette = _palette; 348 this.imageID = imageID; 349 } 350 /** 351 * Loads a Truevision TARGA file and creates a TGA object. 352 * FILE can be either std.stdio's file, my own implementation of a virtual file (see ziltoid1991/vfile), or any compatible solution. 353 */ 354 public static TGA load(FILE = std.stdio.File, bool loadDevArea = true, bool loadExtArea = true)(ref FILE file){ 355 import std.stdio; 356 TGA result = new TGA(); 357 ubyte[] loadRLEImageData(const ref Header header) { 358 size_t target = header.width * header.height; 359 const size_t bytedepth = header.pixelDepth >= 8 ? (header.pixelDepth / 8) : 1; 360 target >>= header.pixelDepth < 8 ? 1 : 0; 361 target >>= header.pixelDepth < 4 ? 1 : 0; 362 target >>= header.pixelDepth < 2 ? 1 : 0; 363 ubyte[] result, dataBuffer; 364 result.reserve(header.pixelDepth >= 8 ? target * bytedepth : target); 365 switch(header.pixelDepth) { 366 case 15, 16: 367 dataBuffer.length = 3; 368 break; 369 case 24: 370 dataBuffer.length = 4; 371 break; 372 case 32: 373 dataBuffer.length = 5; 374 break; 375 default: //all indexed type having less than 8bits of depth use the same method of RLE as 8bit ones 376 dataBuffer.length = 2; 377 break; 378 } 379 while(result.length < target * bytedepth){ 380 file.rawRead(dataBuffer); 381 if(dataBuffer[0] & 0b1000_0000){//RLE block 382 dataBuffer[0] &= 0b0111_1111; 383 dataBuffer[0]++; 384 while(dataBuffer[0]){ 385 result ~= dataBuffer[1..$]; 386 dataBuffer[0]--; 387 //target--; 388 } 389 //result ~= rleBlock; 390 }else{//literal block 391 dataBuffer[0] &= 0b0111_1111; 392 //dataBuffer[0]--; 393 ubyte[] literalBlock; 394 literalBlock.length = (dataBuffer[0] * bytedepth); 395 if(literalBlock.length)file.rawRead(literalBlock); 396 result ~= dataBuffer[1..$] ~ literalBlock; 397 } 398 } 399 //std.stdio.writeln(result.length, ";", (header.width * header.height * bytedepth)); 400 assert(result.length == (header.width * header.height * bytedepth), "RLE length mismatch error!"); 401 return result; 402 } 403 ubyte[] readBuffer; 404 readBuffer.length = Header.sizeof; 405 file.rawRead(readBuffer); 406 result.header = reinterpretGet!Header(readBuffer); 407 result.imageID.length = result.header.idLength; 408 if (result.imageID.length) file.rawRead(result.imageID); 409 ubyte[] palette; 410 palette.length = result.header.colorMapLength * (result.getPaletteBitdepth / 8); 411 if (palette.length) { 412 file.rawRead(palette); 413 switch (result.getPalettePixelFormat) { 414 case PixelFormat.Grayscale8Bit: 415 result._palette = new Palette!ubyte(palette, PixelFormat.Grayscale8Bit, 8); 416 break; 417 case PixelFormat.RGBA5551: 418 result._palette = new Palette!RGBA5551(reinterpretCast!RGBA5551(palette), PixelFormat.RGBA5551, 16); 419 break; 420 case PixelFormat.RGB888: 421 result._palette = new Palette!RGB888(reinterpretCast!RGB888(palette), PixelFormat.RGB888, 24); 422 break; 423 case PixelFormat.ARGB8888: 424 result._palette = new Palette!ARGB8888(reinterpretCast!ARGB8888(palette), PixelFormat.ARGB8888, 32); 425 break; 426 default: throw new ImageFileException("Unknown palette type!"); 427 } 428 } 429 ubyte[] image; 430 if (result.header.imageType >= Header.ImageType.RLEMapped && result.header.imageType <= Header.ImageType.RLEGrayscale) { 431 image = loadRLEImageData(result.header); 432 } else { 433 image.length = (result.header.width * result.header.height * result.header.pixelDepth) / 8; 434 if(image.length) file.rawRead(image); 435 } 436 if (image.length) { 437 switch (result.getPixelFormat) { 438 case PixelFormat.Grayscale8Bit: 439 result._imageData = new ImageData!ubyte(image, result.width, result.height, PixelFormat.Grayscale8Bit, 8); 440 break; 441 case PixelFormat.RGBA5551: 442 result._imageData = new ImageData!RGBA5551(reinterpretCast!RGBA5551(image), result.width, result.height, 443 PixelFormat.RGBA5551, 16); 444 break; 445 case PixelFormat.RGB888: 446 result._imageData = new ImageData!RGB888(reinterpretCast!RGB888(image), result.width, result.height, 447 PixelFormat.RGB888, 24); 448 break; 449 case PixelFormat.ARGB8888: 450 result._imageData = new ImageData!ARGB8888(reinterpretCast!ARGB8888(image), result.width, result.height, 451 PixelFormat.ARGB8888, 32); 452 break; 453 case PixelFormat.Indexed8Bit: 454 result._imageData = new IndexedImageData!ubyte(image, result._palette, result.width, result.height); 455 break; 456 case PixelFormat.Indexed16Bit: 457 result._imageData = new IndexedImageData!ushort(reinterpretCast!ushort(image), result._palette, result.width, 458 result.height); 459 break; 460 default: throw new ImageFileException("Unknown Image type!"); 461 } 462 } 463 static if(loadExtArea || loadDevArea){ 464 readBuffer.length = Footer.sizeof; 465 file.seek(cast(int)Footer.sizeof * -1, SEEK_END); 466 file.rawRead(readBuffer); 467 result.footer = reinterpretGet!Footer(readBuffer); 468 //TGA result = new TGA(result.header, footerLoad, image, palette, imageIDLoad); 469 if(result.footer.isValid){ 470 static if(loadDevArea){ 471 if(result.footer.developerAreaOffset){ 472 file.seek(result.footer.developerAreaOffset); 473 result.developerAreaTags.length = 1; 474 file.rawRead(result.developerAreaTags); 475 result.developerAreaTags.length = result.developerAreaTags[0].reserved; 476 file.rawRead(result.developerAreaTags[1..$]); 477 result.developerArea.reserve = result.developerAreaTags[0].reserved; 478 ubyte[] dataBuffer; 479 foreach(tag; result.developerAreaTags){ 480 file.seek(tag.offset); 481 dataBuffer.length = tag.fieldSize; 482 file.rawRead(dataBuffer); 483 result.developerArea ~= DevArea(dataBuffer.dup); 484 } 485 } 486 } 487 static if(loadExtArea){ 488 if(result.footer.extensionAreaOffset){ 489 file.seek(result.footer.extensionAreaOffset); 490 result.extensionArea.length = 1; 491 file.rawRead(result.extensionArea); 492 if(result.extensionArea[0].postageStampOffset){ 493 file.seek(result.extensionArea[0].postageStampOffset); 494 result.postageStampImage.length = 2; 495 file.rawRead(result.postageStampImage); 496 result.postageStampImage.length = 2 + result.postageStampImage[0] * result.postageStampImage[1]; 497 file.rawRead(result.postageStampImage[2..$]); 498 } 499 if(result.extensionArea[0].colorCorrectionOffset){ 500 result.colorCorrectionTable.length = 1024; 501 file.seek(result.extensionArea[0].colorCorrectionOffset); 502 file.rawRead(result.colorCorrectionTable); 503 } 504 if(result.extensionArea[0].scanlineOffset){ 505 result.scanlineTable.length = result.header.height; 506 file.seek(result.extensionArea[0].scanlineOffset); 507 file.rawRead(result.scanlineTable); 508 } 509 } 510 } 511 } 512 return result; 513 }else{ 514 //TGA result = new TGA(result.header, image, palette, imageIDLoad); 515 return result; 516 } 517 518 } 519 /** 520 * Saves the current TGA object into a Truevision TARGA file. 521 * If ignoreScanlineBounds is true, then the compression methods will ignore the scanline bounds, this disables per-line accessing, but enhances compression 522 * rates by a margin in exchange. If false, then it'll generate a scanline table. 523 */ 524 public void save(FILE = std.stdio.File, bool saveDevArea = false, bool saveExtArea = false, 525 bool ignoreScanlineBounds = false)(ref FILE file){ 526 import std.stdio; 527 ubyte[] imageBuffer; 528 if(_imageData) imageBuffer = _imageData.raw; 529 void compressRLE(){ //Help!!! I haven't used templates for this and I can't debug!!! 530 static if(!ignoreScanlineBounds){ 531 const uint maxScanlineLength = header.width; 532 scanlineTable.length = 0; 533 scanlineTable.reserve(height); 534 } 535 version(unittest) uint pixelCount; 536 ubyte[] writeBuff; 537 static if(!ignoreScanlineBounds) 538 uint currScanlineLength; 539 switch(header.pixelDepth){ 540 case 16: 541 ushort* src = cast(ushort*)(cast(void*)imageBuffer.ptr); 542 const ushort* dest = src + (imageBuffer.length / 2); 543 writeBuff.length = 257; 544 ushort* writeBuff0 = cast(ushort*)(cast(void*)writeBuff.ptr + 1); 545 while(src < dest){ 546 ushort* currBlockBegin = src, currBlockEnd = src; 547 if(currBlockBegin[0] == currBlockBegin[1]){ //RLE block 548 ubyte blockLength; 549 //while(src < dest && currBlockEnd[0] == currBlockEnd[1]){ 550 do{ 551 src++; 552 currBlockEnd++; 553 blockLength++; 554 static if(!ignoreScanlineBounds){ 555 currScanlineLength++; 556 if(currScanlineLength == maxScanlineLength){ 557 currScanlineLength = 0; 558 scanlineTable ~= cast(uint)file.tell; 559 break; 560 } 561 } 562 if(blockLength == 128) 563 break; 564 }while(src < dest && currBlockBegin[0] == currBlockEnd[0]); 565 version(unittest){ 566 import std.conv : to; 567 pixelCount += blockLength; 568 assert(pixelCount <= header.width * header.height, "Required size: " ~ to!string(header.width * header.height) 569 ~ " Current size:" ~ to!string(pixelCount)); 570 } 571 blockLength--; 572 blockLength |= 0b1000_0000; 573 writeBuff[0] = blockLength; 574 writeBuff0[0] = currBlockBegin[0]; 575 file.rawWrite(writeBuff[0..3]); 576 }else{ //literal block 577 ubyte blockLength; 578 579 //while(src < dest && currBlockEnd[0] != currBlockEnd[1]){ 580 do{ 581 writeBuff0[blockLength] = currBlockEnd[0]; 582 src++; 583 currBlockEnd++; 584 blockLength++; 585 static if(!ignoreScanlineBounds){ 586 currScanlineLength++; 587 if(currScanlineLength == maxScanlineLength){ 588 currScanlineLength = 0; 589 break; 590 } 591 } 592 if(blockLength == 128) 593 break; 594 //blockLength++; 595 }while(src < dest && currBlockBegin[0] == currBlockEnd[0]); 596 //writeBuff[1] = currBlockEnd[0]; 597 version(unittest){ 598 import std.conv : to; 599 pixelCount += blockLength; 600 //std.stdio.writeln(pixelCount); 601 assert(pixelCount <= header.width * header.height, "Required size: " ~ to!string(header.width * header.height) 602 ~ " Current size:" ~ to!string(pixelCount)); 603 } 604 blockLength--; 605 writeBuff[0] = blockLength; 606 file.rawWrite(writeBuff[0..((blockLength * 2) + 3)]); 607 } 608 } 609 break; 610 case 24: 611 RGB888* src = cast(RGB888*)(cast(void*)imageBuffer.ptr); 612 const RGB888* dest = src + (imageBuffer.length / 3); 613 writeBuff.length = 1; 614 RGB888[] writeBuff0; 615 writeBuff0.length = 128; 616 while(src < dest){ 617 RGB888* currBlockBegin = src, currBlockEnd = src; 618 if(currBlockBegin[0] == currBlockBegin[1]){ //RLE block 619 ubyte blockLength; 620 //while(src < dest && currBlockEnd[0] == currBlockEnd[1]){ 621 do{ 622 src++; 623 currBlockEnd++; 624 blockLength++; 625 static if(!ignoreScanlineBounds){ 626 currScanlineLength++; 627 if(currScanlineLength == maxScanlineLength){ 628 currScanlineLength = 0; 629 scanlineTable ~= cast(uint)file.tell; 630 break; 631 } 632 } 633 if(blockLength == 128) 634 break; 635 }while(src < dest && currBlockBegin[0] == currBlockEnd[0]); 636 version(unittest){ 637 import std.conv : to; 638 pixelCount += blockLength; 639 assert(pixelCount <= header.width * header.height, "Required size: " ~ to!string(header.width * header.height) 640 ~ " Current size:" ~ to!string(pixelCount)); 641 } 642 blockLength--; 643 blockLength |= 0b1000_0000; 644 writeBuff[0] = blockLength; 645 writeBuff0[0] = currBlockBegin[0]; 646 file.rawWrite(writeBuff[0..1]); 647 file.rawWrite(writeBuff0[0..1]); 648 }else{ //literal block 649 ubyte blockLength; 650 651 //while(src < dest && currBlockEnd[0] != currBlockEnd[1]){ 652 do{ 653 writeBuff0[blockLength] = currBlockEnd[0]; 654 src++; 655 currBlockEnd++; 656 blockLength++; 657 static if(!ignoreScanlineBounds){ 658 currScanlineLength++; 659 if(currScanlineLength == maxScanlineLength){ 660 currScanlineLength = 0; 661 break; 662 } 663 } 664 if(blockLength == 128) 665 break; 666 //blockLength++; 667 }while(src < dest && currBlockBegin[0] == currBlockEnd[0]); 668 //writeBuff[1] = currBlockEnd[0]; 669 version(unittest){ 670 import std.conv : to; 671 pixelCount += blockLength; 672 assert(pixelCount <= header.width * header.height, "Required size: " ~ to!string(header.width * header.height) 673 ~ " Current size:" ~ to!string(pixelCount)); 674 } 675 blockLength--; 676 writeBuff[0] = blockLength; 677 file.rawWrite(writeBuff[0..1]); 678 file.rawWrite(writeBuff0[0..(blockLength + 1)]); 679 } 680 } 681 break; 682 case 32: 683 uint* src = cast(uint*)(cast(void*)imageBuffer.ptr); 684 const uint* dest = src + (imageBuffer.length / 4); 685 writeBuff.length = 1; 686 uint[] writeBuff0; 687 writeBuff0.length = 128; 688 while(src < dest){ 689 uint* currBlockBegin = src, currBlockEnd = src; 690 if(currBlockBegin[0] == currBlockBegin[1]){ //RLE block 691 ubyte blockLength; 692 //while(src < dest && currBlockEnd[0] == currBlockEnd[1]){ 693 do{ 694 src++; 695 currBlockEnd++; 696 blockLength++; 697 static if(!ignoreScanlineBounds){ 698 currScanlineLength++; 699 if(currScanlineLength == maxScanlineLength){ 700 currScanlineLength = 0; 701 scanlineTable ~= cast(uint)file.tell; 702 break; 703 } 704 } 705 if(blockLength == 128) 706 break; 707 }while(src < dest && currBlockBegin[0] == currBlockEnd[0]); 708 version(unittest){ 709 import std.conv : to; 710 pixelCount += blockLength; 711 assert(pixelCount <= header.width * header.height, "Required size: " ~ to!string(header.width * header.height) 712 ~ " Current size:" ~ to!string(pixelCount)); 713 } 714 blockLength--; 715 blockLength |= 0b1000_0000; 716 writeBuff[0] = blockLength; 717 writeBuff0[0] = currBlockBegin[0]; 718 file.rawWrite(writeBuff[0..1]); 719 file.rawWrite(writeBuff0[0..1]); 720 }else{ //literal block 721 ubyte blockLength; 722 723 //while(src < dest && currBlockEnd[0] != currBlockEnd[1]){ 724 do{ 725 writeBuff0[blockLength] = currBlockEnd[0]; 726 src++; 727 currBlockEnd++; 728 blockLength++; 729 static if(!ignoreScanlineBounds){ 730 currScanlineLength++; 731 if(currScanlineLength == maxScanlineLength){ 732 currScanlineLength = 0; 733 break; 734 } 735 } 736 if(blockLength == 128) 737 break; 738 //blockLength++; 739 }while(src < dest && currBlockBegin[0] == currBlockEnd[0]); 740 //writeBuff[1] = currBlockEnd[0]; 741 version(unittest){ 742 import std.conv : to; 743 pixelCount += blockLength; 744 assert(pixelCount <= header.width * header.height, "Required size: " ~ to!string(header.width * header.height) 745 ~ " Current size:" ~ to!string(pixelCount)); 746 } 747 blockLength--; 748 writeBuff[0] = blockLength; 749 file.rawWrite(writeBuff[0..1]); 750 file.rawWrite(writeBuff0[0..(blockLength + 1)]); 751 } 752 } 753 break; 754 default: 755 ubyte* src = imageBuffer.ptr; 756 const ubyte* dest = src + imageBuffer.length; 757 writeBuff.length = 129; 758 while(src < dest){ 759 ubyte* currBlockBegin = src, currBlockEnd = src; 760 if(currBlockBegin[0] == currBlockBegin[1]){ //RLE block 761 ubyte blockLength; 762 //while(src < dest && currBlockEnd[0] == currBlockEnd[1]){ 763 do{ 764 src++; 765 currBlockEnd++; 766 blockLength++; 767 static if(!ignoreScanlineBounds){ 768 currScanlineLength++; 769 if(currScanlineLength == maxScanlineLength){ 770 currScanlineLength = 0; 771 scanlineTable ~= cast(uint)file.tell; 772 break; 773 } 774 } 775 if(blockLength == 128) 776 break; 777 }while(src < dest && currBlockBegin[0] == currBlockEnd[0]); 778 version(unittest){ 779 import std.conv : to; 780 pixelCount += blockLength; 781 assert(pixelCount <= imageBuffer.length, "Required size: " ~ to!string(imageBuffer.length) 782 ~ " Current size:" ~ to!string(pixelCount)); 783 } 784 blockLength--; 785 blockLength |= 0b1000_0000; 786 writeBuff[0] = blockLength; 787 writeBuff[1] = currBlockBegin[0]; 788 file.rawWrite(writeBuff[0..2]); 789 }else{ //literal block 790 ubyte blockLength; 791 792 //while(src < dest && currBlockEnd[0] != currBlockEnd[1]){ 793 do{ 794 writeBuff[1 + blockLength] = currBlockEnd[0]; 795 src++; 796 currBlockEnd++; 797 blockLength++; 798 static if(!ignoreScanlineBounds){ 799 currScanlineLength++; 800 if(currScanlineLength == maxScanlineLength){ 801 currScanlineLength = 0; 802 break; 803 } 804 } 805 if(blockLength == 128) 806 break; 807 //blockLength++; 808 }while(src < dest && currBlockEnd[0] != currBlockEnd[1]); 809 //writeBuff[1] = currBlockEnd[0]; 810 version(unittest){ 811 import std.conv : to; 812 pixelCount += blockLength; 813 assert(pixelCount <= imageBuffer.length, "Required size: " ~ to!string(imageBuffer.length) 814 ~ " Current size:" ~ to!string(pixelCount)); 815 } 816 blockLength--; 817 writeBuff[0] = blockLength; 818 file.rawWrite(writeBuff[0..blockLength + 2]); 819 } 820 } 821 break; 822 } 823 version(unittest){ 824 import std.conv : to; 825 assert(pixelCount == header.width * header.height, "Required size: " ~ to!string(header.width * 826 header.height) ~ " Current size:" ~ to!string(pixelCount)); 827 } 828 } 829 //write most of the data into the file 830 file.rawWrite([header]); 831 file.rawWrite(imageID); 832 if (_palette) file.rawWrite(_palette.raw); 833 if(header.imageType >= Header.ImageType.RLEMapped && header.imageType <= Header.ImageType.RLEGrayscale){ 834 compressRLE(); 835 }else{ 836 file.rawWrite(_imageData.raw); 837 } 838 static if(saveDevArea){ 839 if(developerAreaTags.length){ 840 //make all tags valid 841 uint offset = cast(uint)file.tell; 842 footer.developerAreaOffset = offset; 843 offset += cast(uint)(developerAreaTags.length * DevAreaTag.sizeof); 844 developerAreaTags[0].reserved = cast(ushort)developerAreaTags.length; 845 for(int i; i < developerAreaTags.length; i++){ 846 developerAreaTags[i].offset = offset; 847 developerAreaTags[i].fieldSize = developerArea[i].data.length; 848 offset += developerArea[i].data.length; 849 } 850 file.rawWrite(developerAreaTags); 851 foreach(d; developerArea){ 852 file.rawWrite(d.data); 853 } 854 } 855 } 856 static if(saveExtArea){ 857 if(extensionArea.length){ 858 uint offset = cast(uint)file.tell; 859 footer.extensionAreaOffset = offset; 860 offset += cast(uint)ExtArea.sizeof; 861 if(colorCorrectionTable.length){ 862 extensionArea[0].colorCorrectionOffset = offset; 863 offset += cast(uint)(colorCorrectionTable.length * ushort.sizeof); 864 }else{ 865 extensionArea[0].colorCorrectionOffset = 0; 866 } 867 if(postageStampImage.length){ 868 extensionArea[0].postageStampOffset = offset; 869 offset += cast(uint)(postageStampImage.length * ubyte.sizeof); 870 }else{ 871 extensionArea[0].postageStampOffset = 0; 872 } 873 if(scanlineTable.length){ 874 extensionArea[0].scanlineOffset = offset; 875 //offset += cast(uint)(scanlineTable.length * uint.sizeof); 876 }else{ 877 extensionArea[0].scanlineOffset = 0; 878 } 879 file.rawWrite(extensionArea); 880 assert(file.tell == footer.extensionAreaOffset); 881 if (colorCorrectionTable.length){ 882 file.rawWrite(colorCorrectionTable); 883 assert(file.tell == extensionArea[0].colorCorrectionOffset); 884 } 885 if (postageStampImage.length){ 886 file.rawWrite(postageStampImage); 887 assert(file.tell == extensionArea[0].postageStampImage); 888 } 889 if (scanlineTable.length){ 890 file.rawWrite(scanlineTable); 891 assert(file.tell == extensionArea[0].scanlineTable); 892 } 893 } 894 } 895 static if(saveExtArea || saveDevArea){ 896 file.rawWrite([footer]); 897 } 898 } 899 override uint width() @nogc @safe @property const pure{ 900 return header.width; 901 } 902 override uint height() @nogc @safe @property const pure{ 903 return header.height; 904 } 905 override bool isIndexed() @nogc @safe @property const pure{ 906 return header.colorMapType != Header.ColorMapType.NoColorMapPresent; 907 } 908 override ubyte getBitdepth() @nogc @safe @property const pure{ 909 return header.pixelDepth; 910 } 911 override ubyte getPaletteBitdepth() @nogc @safe @property const pure { 912 return header.colorMapDepth; 913 } 914 override uint getPixelFormat() @nogc @safe @property const pure { 915 if (header.imageType == Header.ImageType.UncompressedMapped || header.imageType == Header.ImageType.RLEMapped || 916 header.imageType == Header.ImageType.RLE4BitMapped || header.imageType == Header.ImageType.RLE1BitMapped) { 917 switch (header.pixelDepth) { 918 case 1: return PixelFormat.Indexed1Bit; 919 case 2: return PixelFormat.Indexed2Bit; 920 case 4: return PixelFormat.Indexed4Bit; 921 case 8: return PixelFormat.Indexed8Bit; 922 case 16: return PixelFormat.Indexed16Bit; 923 default: return PixelFormat.Undefined; 924 } 925 } else if (header.imageType == Header.ImageType.RLEGrayscale || 926 header.imageType == Header.ImageType.UncompressedGrayscale) { 927 if (header.pixelDepth == 8) return PixelFormat.Grayscale8Bit; 928 else return PixelFormat.Undefined; 929 } else { //Must be truecolor 930 switch(header.pixelDepth){ 931 case 15, 16: 932 return PixelFormat.RGBA5551; 933 case 24: 934 return PixelFormat.RGB888; 935 case 32: 936 return PixelFormat.ARGB8888; 937 default: 938 return PixelFormat.Undefined; 939 } 940 } 941 } 942 override uint getPalettePixelFormat() @nogc @safe @property const pure{ 943 if(Header.ColorMapType.NoColorMapPresent){ 944 return PixelFormat.Undefined; 945 }else{ 946 switch(header.colorMapDepth){ 947 case 8: return PixelFormat.Grayscale8Bit; 948 case 15, 16: return PixelFormat.RGBA5551; 949 case 24: return PixelFormat.RGB888; 950 case 32: return PixelFormat.ARGB8888; 951 default: return PixelFormat.Undefined; 952 } 953 } 954 } 955 public string getID() @safe{ 956 return to!string(imageID); 957 } 958 public string getAuthor() @safe{ 959 if(extensionArea.length) 960 return extensionArea[0].authorName; 961 return null; 962 } 963 public string getComment() @safe{ 964 if(extensionArea.length) 965 return extensionArea[0].authorComments; 966 return null; 967 } 968 public string getJobName() @safe{ 969 if(extensionArea.length) 970 return extensionArea[0].jobName; 971 return null; 972 } 973 public string getSoftwareInfo() @safe{ 974 if(extensionArea.length) 975 return extensionArea[0].softwareID; 976 return null; 977 } 978 public string getSoftwareVersion() @safe{ 979 import std.conv : to; 980 if(extensionArea.length) 981 return to!string(extensionArea[0].softwareVersNum / 100) ~ "." ~ to!string((extensionArea[0].softwareVersNum % 100) 982 / 10) ~ "." ~ to!string(extensionArea[0].softwareVersNum % 10) ~ extensionArea[0].softwareVersChar; 983 return null; 984 } 985 public string getDescription() @safe pure { 986 return null; 987 } 988 public string getCopyright() @safe pure { 989 return null; 990 } 991 public string getSource() @safe pure { 992 return null; 993 } 994 public string getCreationTimeStr() @safe pure { 995 return null; 996 } 997 998 999 public string setID(string val) @safe{ 1000 if(val.length > 255) 1001 throw new Exception("ID is too long"); 1002 imageID = val.dup; 1003 header.idLength = cast(ubyte)val.length; 1004 return val; 1005 } 1006 public string setAuthor(string val) @safe{ 1007 if(val.length > 41) 1008 throw new Exception("Author name is too long"); 1009 if(extensionArea.length){ 1010 stringCpy(extensionArea[0].authorName, val); 1011 return val; 1012 } 1013 return null; 1014 } 1015 public string setComment(string val) @safe{ 1016 if(val.length > 324) 1017 throw new Exception("Comment is too long"); 1018 if(extensionArea.length){ 1019 stringCpy(extensionArea[0].authorComments, val); 1020 return val; 1021 } 1022 return null; 1023 } 1024 public string setJobName(string val) @safe{ 1025 if(val.length > 41) 1026 throw new Exception("Jobname is too long"); 1027 if(extensionArea.length){ 1028 stringCpy(extensionArea[0].jobName, val); 1029 return val; 1030 } 1031 return null; 1032 } 1033 public string setSoftwareInfo(string val) @safe{ 1034 if(val.length > 41) 1035 throw new Exception("SoftwareID is too long"); 1036 if(extensionArea.length){ 1037 stringCpy(extensionArea[0].softwareID, val); 1038 return val; 1039 } 1040 return null; 1041 } 1042 ///Format used: 0.0.0a 1043 public string setSoftwareVersion(string val) @safe{ 1044 if(extensionArea.length){ 1045 //separate first part with dot, then parse the number 1046 uint prelimiter; 1047 for( ; prelimiter < val.length ; prelimiter++){ 1048 if(val[prelimiter] == '.') 1049 break; 1050 } 1051 uint resultI = to!uint(val[0..prelimiter]); 1052 resultI *= 10; 1053 for( ; prelimiter < val.length ; prelimiter++){ 1054 if(val[prelimiter] == '.') 1055 break; 1056 } 1057 resultI += to!uint([val[prelimiter-1]]); 1058 resultI *= 10; 1059 if(val.length > prelimiter+1) 1060 resultI += to!uint([val[prelimiter+1]]); 1061 extensionArea[0].softwareVersNum = cast(ushort)resultI; 1062 if(val.length > prelimiter+2) 1063 extensionArea[0].softwareVersChar = val[prelimiter+2]; 1064 return val; 1065 } 1066 return null; 1067 } 1068 public string setDescription(string val) @safe pure { 1069 return null; 1070 } 1071 public string setSource(string val) @safe pure { 1072 return null; 1073 } 1074 public string setCopyright(string val) @safe pure { 1075 return null; 1076 } 1077 public string setCreationTime(string val) @safe pure { 1078 return null; 1079 } 1080 /** 1081 * Adds extension area for the file. 1082 */ 1083 public void createExtensionArea(){ 1084 extensionArea.length = 1; 1085 } 1086 /** 1087 * Returns the developer area info for the field. 1088 */ 1089 public DevAreaTag getDevAreaInfo(size_t n){ 1090 return developerAreaTags[n]; 1091 } 1092 /** 1093 * Returns the embedded field. 1094 */ 1095 public DevArea getEmbeddedData(size_t n){ 1096 return developerArea[n]; 1097 } 1098 /** 1099 * Creates an embedded field within the TGA file. 1100 */ 1101 public void addEmbeddedData(ushort ID, ubyte[] data){ 1102 developerAreaTags ~= DevAreaTag(0, ID, 0, cast(uint)data.length); 1103 developerArea ~= DevArea(data); 1104 } 1105 /** 1106 * Returns the image type. 1107 */ 1108 public ubyte getImageType() const @nogc @property @safe nothrow{ 1109 return header.imageType; 1110 } 1111 /** 1112 * Returns the header as a reference type. 1113 */ 1114 public ref Header getHeader() @nogc @safe nothrow{ 1115 return header; 1116 } 1117 /** 1118 * Flips the image on the vertical axis. Useful to set images to the correct top-left screen origin point. 1119 */ 1120 public override void flipVertical() @safe{ 1121 header.topOrigin = !header.topOrigin; 1122 super.flipVertical; 1123 } 1124 /** 1125 * Flips the image on the vertical axis. Useful to set images to the correct top-left screen origin point. 1126 */ 1127 public override void flipHorizontal() @safe{ 1128 header.rightSideOrigin = !header.rightSideOrigin; 1129 super.flipHorizontal; 1130 } 1131 ///Returns true if the image originates from the top 1132 public override bool topOrigin() @property @nogc @safe pure const { 1133 return header.topOrigin; 1134 } 1135 ///Returns true if the image originates from the right 1136 public override bool rightSideOrigin() @property @nogc @safe pure const { 1137 return header.rightSideOrigin; 1138 } 1139 } 1140 1141 unittest{ 1142 import std.conv : to; 1143 import vfile; 1144 assert(TGA.Header.sizeof == 18); 1145 //void[] tempStream; 1146 //test 8 bit RLE load for 8 bit greyscale and indexed 1147 { 1148 std.stdio.File greyscaleUncFile = std.stdio.File("test/tga/grey_8.tga"); 1149 std.stdio.writeln("Loading ", greyscaleUncFile.name); 1150 TGA greyscaleUnc = TGA.load(greyscaleUncFile); 1151 std.stdio.writeln("File `", greyscaleUncFile.name, "` successfully loaded"); 1152 std.stdio.File greyscaleRLEFile = std.stdio.File("test/tga/grey_8_rle.tga"); 1153 std.stdio.writeln("Loading ", greyscaleRLEFile.name); 1154 TGA greyscaleRLE = TGA.load(greyscaleRLEFile); 1155 std.stdio.writeln("File `", greyscaleRLEFile.name, "` successfully loaded"); 1156 compareImages(greyscaleUnc, greyscaleRLE); 1157 //store the uncompressed one as a VFile in the memory using RLE, then restore it and check if it's working. 1158 greyscaleUnc.getHeader.imageType = TGA.Header.ImageType.RLEGrayscale; 1159 VFile virtualFile;// = VFile(tempStream); 1160 //std.stdio.File virtualFile = std.stdio.File("test/tga/grey_8_rle_gen.tga", "wb"); 1161 greyscaleUnc.save!(VFile, false, false, true)(virtualFile); 1162 std.stdio.writeln("Save to virtual file was successful"); 1163 std.stdio.writeln(virtualFile.size); 1164 virtualFile.seek(0); 1165 greyscaleRLE = TGA.load!VFile(virtualFile); 1166 std.stdio.writeln("Load from virtual file was successful"); 1167 compareImages(greyscaleUnc, greyscaleRLE); 1168 } 1169 { 1170 std.stdio.File mappedUncFile = std.stdio.File("test/tga/mapped_8.tga"); 1171 std.stdio.writeln("Loading ", mappedUncFile.name); 1172 TGA mappedUnc = TGA.load(mappedUncFile); 1173 std.stdio.writeln("File `", mappedUncFile.name, "` successfully loaded"); 1174 std.stdio.File mappedRLEFile = std.stdio.File("test/tga/mapped_8_rle.tga"); 1175 std.stdio.writeln("Loading ", mappedRLEFile.name); 1176 TGA mappedRLE = TGA.load(mappedRLEFile); 1177 std.stdio.writeln("File `", mappedRLEFile.name, "` successfully loaded"); 1178 compareImages(mappedUnc, mappedRLE); 1179 //store the uncompressed one as a VFile in the memory using RLE, then restore it and check if it's working. 1180 mappedUnc.getHeader.imageType = TGA.Header.ImageType.RLEMapped; 1181 VFile virtualFile;// = VFile(tempStream); 1182 //std.stdio.File virtualFile = std.stdio.File("test/tga/grey_8_rle_gen.tga", "wb"); 1183 mappedUnc.save!(VFile, false, false, true)(virtualFile); 1184 std.stdio.writeln("Save to virtual file was successful"); 1185 std.stdio.writeln(virtualFile.size); 1186 virtualFile.seek(0); 1187 mappedRLE = TGA.load!VFile(virtualFile); 1188 std.stdio.writeln("Load from virtual file was successful"); 1189 compareImages(mappedUnc, mappedRLE); 1190 1191 { 1192 Palette!RGBA5551 p = cast(Palette!RGBA5551)mappedUnc.palette; 1193 assert(p.convTo(PixelFormat.ARGB8888).length == p.length, "Length mismatch!"); 1194 assert(p.convTo(PixelFormat.RGBA8888).length == p.length, "Length mismatch!"); 1195 //std.stdio.writeln(mappedUnc.getHeader); 1196 foreach_reverse (c ; p) { 1197 } 1198 foreach (c ; p) { 1199 } 1200 } 1201 } 1202 { 1203 std.stdio.File mappedUncFile = std.stdio.File("test/tga/concreteGUIE3.tga"); 1204 std.stdio.writeln("Loading ", mappedUncFile.name); 1205 TGA mappedUnc = TGA.load(mappedUncFile); 1206 std.stdio.writeln("File `", mappedUncFile.name, "` successfully loaded"); 1207 std.stdio.File mappedRLEFile = std.stdio.File("test/tga/concreteGUIE3_rle.tga"); 1208 std.stdio.writeln("Loading ", mappedRLEFile.name); 1209 TGA mappedRLE = TGA.load(mappedRLEFile); 1210 std.stdio.writeln("File `", mappedRLEFile.name, "` successfully loaded"); 1211 compareImages(mappedUnc, mappedRLE); 1212 //store the uncompressed one as a VFile in the memory using RLE, then restore it and check if it's working. 1213 mappedUnc.getHeader.imageType = TGA.Header.ImageType.RLEMapped; 1214 VFile virtualFile;// = VFile(tempStream); 1215 //std.stdio.File virtualFile = std.stdio.File("test/tga/grey_8_rle_gen.tga", "wb"); 1216 mappedUnc.save!(VFile, false, false, true)(virtualFile); 1217 std.stdio.writeln("Save to virtual file was successful"); 1218 std.stdio.writeln(virtualFile.size); 1219 virtualFile.seek(0); 1220 mappedRLE = TGA.load!VFile(virtualFile); 1221 std.stdio.writeln("Load from virtual file was successful"); 1222 compareImages(mappedUnc, mappedRLE); 1223 1224 { 1225 IPalette p = mappedUnc.palette; 1226 assert(p.convTo(PixelFormat.ARGB8888).length == p.length, "Length mismatch!"); 1227 assert(p.convTo(PixelFormat.RGBA8888).length == p.length, "Length mismatch!"); 1228 } 1229 } 1230 { 1231 std.stdio.File mappedUncFile = std.stdio.File("test/tga/sci-fi-tileset.tga"); 1232 std.stdio.writeln("Loading ", mappedUncFile.name); 1233 TGA mappedUnc = TGA.load(mappedUncFile); 1234 std.stdio.writeln("File `", mappedUncFile.name, "` successfully loaded"); 1235 { 1236 IPalette p = mappedUnc.palette; 1237 assert(p.convTo(PixelFormat.ARGB8888).length == p.length, "Length mismatch!"); 1238 assert(p.convTo(PixelFormat.RGBA8888).length == p.length, "Length mismatch!"); 1239 } 1240 } 1241 { 1242 std.stdio.File greyscaleUncFile = std.stdio.File("test/tga/truecolor_16.tga"); 1243 std.stdio.writeln("Loading ", greyscaleUncFile.name); 1244 TGA greyscaleUnc = TGA.load(greyscaleUncFile); 1245 std.stdio.writeln("File `", greyscaleUncFile.name, "` successfully loaded"); 1246 std.stdio.File greyscaleRLEFile = std.stdio.File("test/tga/truecolor_16_rle.tga"); 1247 std.stdio.writeln("Loading ", greyscaleRLEFile.name); 1248 TGA greyscaleRLE = TGA.load(greyscaleRLEFile); 1249 std.stdio.writeln("File `", greyscaleRLEFile.name, "` successfully loaded"); 1250 compareImages(greyscaleUnc, greyscaleRLE); 1251 //store the uncompressed one as a VFile in the memory using RLE, then restore it and check if it's working. 1252 greyscaleUnc.getHeader.imageType = TGA.Header.ImageType.RLETrueColor; 1253 VFile virtualFile;// = VFile(tempStream); 1254 //std.stdio.File virtualFile = std.stdio.File("test/tga/grey_8_rle_gen.tga", "wb"); 1255 greyscaleUnc.save!(VFile, false, false, true)(virtualFile); 1256 std.stdio.writeln("Save to virtual file was successful"); 1257 std.stdio.writeln(virtualFile.size); 1258 virtualFile.seek(0); 1259 greyscaleRLE = TGA.load!VFile(virtualFile); 1260 std.stdio.writeln("Load from virtual file was successful"); 1261 compareImages(greyscaleUnc, greyscaleRLE); 1262 //Test upconversion and handling of 16 bit images 1263 ImageData!RGBA5551 imageData = cast(ImageData!RGBA5551)greyscaleUnc.imageData; 1264 TGA upconvToRGB888 = new TGA(imageData.convTo(PixelFormat.RGB888), null, null, true); 1265 1266 std.stdio.File output = std.stdio.File("test/tga/test.tga", "wb"); 1267 upconvToRGB888.save(output); 1268 } 1269 { 1270 std.stdio.File greyscaleUncFile = std.stdio.File("test/tga/truecolor_24.tga"); 1271 std.stdio.writeln("Loading ", greyscaleUncFile.name); 1272 TGA greyscaleUnc = TGA.load(greyscaleUncFile); 1273 std.stdio.writeln("File `", greyscaleUncFile.name, "` successfully loaded"); 1274 std.stdio.File greyscaleRLEFile = std.stdio.File("test/tga/truecolor_24_rle.tga"); 1275 std.stdio.writeln("Loading ", greyscaleRLEFile.name); 1276 TGA greyscaleRLE = TGA.load(greyscaleRLEFile); 1277 std.stdio.writeln("File `", greyscaleRLEFile.name, "` successfully loaded"); 1278 compareImages(greyscaleUnc, greyscaleRLE); 1279 //store the uncompressed one as a VFile in the memory using RLE, then restore it and check if it's working. 1280 greyscaleUnc.getHeader.imageType = TGA.Header.ImageType.RLETrueColor; 1281 VFile virtualFile;// = VFile(tempStream); 1282 //std.stdio.File virtualFile = std.stdio.File("test/tga/grey_8_rle_gen.tga", "wb"); 1283 greyscaleUnc.save!(VFile, false, false, true)(virtualFile); 1284 std.stdio.writeln("Save to virtual file was successful"); 1285 std.stdio.writeln(virtualFile.size); 1286 virtualFile.seek(0); 1287 greyscaleRLE = TGA.load!VFile(virtualFile); 1288 std.stdio.writeln("Load from virtual file was successful"); 1289 compareImages(greyscaleUnc, greyscaleRLE); 1290 } 1291 { 1292 std.stdio.File greyscaleUncFile = std.stdio.File("test/tga/truecolor_32.tga"); 1293 std.stdio.writeln("Loading ", greyscaleUncFile.name); 1294 TGA greyscaleUnc = TGA.load(greyscaleUncFile); 1295 std.stdio.writeln("File `", greyscaleUncFile.name, "` successfully loaded"); 1296 std.stdio.File greyscaleRLEFile = std.stdio.File("test/tga/truecolor_32_rle.tga"); 1297 std.stdio.writeln("Loading ", greyscaleRLEFile.name); 1298 TGA greyscaleRLE = TGA.load(greyscaleRLEFile); 1299 std.stdio.writeln("File `", greyscaleRLEFile.name, "` successfully loaded"); 1300 compareImages(greyscaleUnc, greyscaleRLE); 1301 //store the uncompressed one as a VFile in the memory using RLE, then restore it and check if it's working. 1302 greyscaleUnc.getHeader.imageType = TGA.Header.ImageType.RLETrueColor; 1303 VFile virtualFile;// = VFile(tempStream); 1304 //std.stdio.File virtualFile = std.stdio.File("test/tga/grey_8_rle_gen.tga", "wb"); 1305 greyscaleUnc.save!(VFile, false, false, true)(virtualFile); 1306 std.stdio.writeln("Save to virtual file was successful"); 1307 std.stdio.writeln(virtualFile.size); 1308 virtualFile.seek(0); 1309 greyscaleRLE = TGA.load!VFile(virtualFile); 1310 std.stdio.writeln("Load from virtual file was successful"); 1311 compareImages(greyscaleUnc, greyscaleRLE); 1312 } 1313 }