1 /* 2 Copyright (c) 2014-2021 Timur Gafarov, Roman Chistokhodov 3 4 Boost Software License - Version 1.0 - August 17th, 2003 5 6 Permission is hereby granted, free of charge, to any person or organization 7 obtaining a copy of the software and accompanying documentation covered by 8 this license (the "Software") to use, reproduce, display, distribute, 9 execute, and transmit the Software, and to prepare derivative works of the 10 Software, and to permit third-parties to whom the Software is furnished to 11 do so, all subject to the following: 12 13 The copyright notices in the Software and this entire statement, including 14 the above license grant, this restriction and the following disclaimer, 15 must be included in all copies of the Software, in whole or in part, and 16 all derivative works of the Software, unless such copies or derivative 17 works are solely in the form of machine-executable object code generated by 18 a source language processor. 19 20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 DEALINGS IN THE SOFTWARE. 27 */ 28 29 /** 30 * Decode JPEG images 31 * 32 * Copyright: Timur Gafarov, Roman Chistokhodov 2014-2021. 33 * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). 34 * Authors: Timur Gafarov, Roman Chistokhodov 35 */ 36 module dlib.image.io.tga; 37 38 import std.stdio; 39 import std.file; 40 import std.conv; 41 import dlib.core.memory; 42 import dlib.core.stream; 43 import dlib.core.compound; 44 import dlib.image.color; 45 import dlib.image.image; 46 import dlib.image.io; 47 import dlib.image.io.utils; 48 import dlib.filesystem.local; 49 50 // uncomment this to see debug messages: 51 //version = TGADebug; 52 53 struct TGAHeader 54 { 55 ubyte idLength; 56 ubyte type; 57 ubyte encoding; 58 short colmapStart; 59 short colmapLen; 60 ubyte colmapBits; 61 short xstart; 62 short ystart; 63 short width; 64 short height; 65 ubyte bpp; 66 ubyte descriptor; 67 } 68 69 enum TGAEncoding : ubyte { 70 Indexed = 1, 71 RGB = 2, 72 Grey = 3, 73 RLE_Indexed = 9, 74 RLE_RGB = 10, 75 RLE_Grey = 11 76 }; 77 78 enum TgaOrigin : ubyte 79 { 80 Left = 0x00, 81 Right = 0x10, 82 Lower = 0x00, 83 Upper = 0x20 84 } 85 86 class TGALoadException: ImageLoadException 87 { 88 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) 89 { 90 super(msg, file, line, next); 91 } 92 } 93 94 /** 95 * Load PNG from file using local FileSystem. 96 * Causes GC allocation 97 */ 98 SuperImage loadTGA(string filename) 99 { 100 InputStream input = openForInput(filename); 101 102 try 103 { 104 return loadTGA(input); 105 } 106 catch (TGALoadException ex) 107 { 108 throw new Exception("'" ~ filename ~ "' :" ~ ex.msg, ex.file, ex.line, ex.next); 109 } 110 finally 111 { 112 input.close(); 113 } 114 } 115 116 /** 117 * Load TGA from stream using default image factory. 118 * Causes GC allocation 119 */ 120 SuperImage loadTGA(InputStream istrm) 121 { 122 Compound!(SuperImage, string) res = 123 loadTGA(istrm, defaultImageFactory); 124 if (res[0] is null) 125 throw new TGALoadException(res[1]); 126 else 127 return res[0]; 128 } 129 130 /** 131 * Load TGA from stream using specified image factory. 132 * GC-free 133 */ 134 Compound!(SuperImage, string) loadTGA( 135 InputStream istrm, 136 SuperImageFactory imgFac) 137 { 138 SuperImage img = null; 139 140 Compound!(SuperImage, string) error(string errorMsg) 141 { 142 if (img) 143 { 144 img.free(); 145 img = null; 146 } 147 return compound(img, errorMsg); 148 } 149 150 TGAHeader readHeader() 151 { 152 TGAHeader hdr = readStruct!TGAHeader(istrm); 153 version(TGADebug) 154 { 155 writefln("idLength = %s", hdr.idLength); 156 writefln("type = %s", hdr.type); 157 /* 158 * Encoding flag: 159 * 1 = Raw indexed image 160 * 2 = Raw RGB 161 * 3 = Raw greyscale 162 * 9 = RLE indexed 163 * 10 = RLE RGB 164 * 11 = RLE greyscale 165 * 32 & 33 = Other compression, indexed 166 */ 167 writefln("encoding = %s", hdr.encoding); 168 169 writefln("colmapStart = %s", hdr.colmapStart); 170 writefln("colmapLen = %s", hdr.colmapLen); 171 writefln("colmapBits = %s", hdr.colmapBits); 172 writefln("xstart = %s", hdr.xstart); 173 writefln("ystart = %s", hdr.ystart); 174 writefln("width = %s", hdr.width); 175 writefln("height = %s", hdr.height); 176 writefln("bpp = %s", hdr.bpp); 177 writefln("descriptor = %s", hdr.descriptor); 178 writeln("-------------------"); 179 } 180 return hdr; 181 } 182 183 SuperImage readRawRGB(ref TGAHeader hdr) 184 { 185 uint channels = hdr.bpp / 8; 186 SuperImage res = imgFac.createImage(hdr.width, hdr.height, channels, 8); 187 188 if (hdr.descriptor & TgaOrigin.Upper) { 189 istrm.fillArray(res.data); 190 } else { 191 foreach(i; 0..hdr.height) { 192 istrm.fillArray(res.data[channels * hdr.width * (hdr.height-i-1)..channels * hdr.width * (hdr.height-i)]); 193 } 194 } 195 196 const ubyte alphaBits = cast(ubyte)(hdr.descriptor & 0xf); 197 version(TGADebug) { 198 writefln("Alpha bits: %s", alphaBits); 199 } 200 201 if (channels == 4) { 202 for (size_t i=0; i<res.data.length; i += channels) { 203 auto alphaIndex = i+3; 204 res.data[alphaIndex] = cast(ubyte)((res.data[alphaIndex] & ((1 << alphaBits)-1 )) << (8 - alphaBits)); 205 } 206 } 207 208 return res; 209 } 210 211 SuperImage readRLERGB(ref TGAHeader hdr) 212 { 213 uint channels = hdr.bpp / 8; 214 uint imageSize = hdr.width * hdr.height * channels; 215 SuperImage res = imgFac.createImage(hdr.width, hdr.height, channels, 8); 216 217 // Calculate offset to image data 218 uint dataOffset = 18 + hdr.idLength; 219 220 // Add palette offset for indexed images 221 if (hdr.type == 1) 222 dataOffset += 768; 223 224 // Read compressed data 225 // TODO: take scanline order into account (bottom-up or top-down) 226 ubyte[] data = New!(ubyte[])(cast(uint)istrm.size - dataOffset); 227 istrm.fillArray(data); 228 229 uint ii = 0; 230 uint i = 0; 231 while (ii < imageSize) 232 { 233 ubyte b = data[i]; 234 i++; 235 236 if (b & 0x80) // Run length chunk 237 { 238 // Get run length 239 uint runLength = b - 127; 240 241 // Repeat the next pixel runLength times 242 for (uint j = 0; j < runLength; j++) 243 { 244 foreach(pIndex; 0..channels) 245 res.data[ii + pIndex] = data[i + pIndex]; 246 247 ii += channels; 248 } 249 250 i += channels; 251 } 252 else // Raw chunk 253 { 254 // Get run length 255 uint runLength = b + 1; 256 257 // Write the next runLength pixels directly 258 for (uint j = 0; j < runLength; j++) 259 { 260 foreach(pIndex; 0..channels) 261 res.data[ii + pIndex] = data[i + pIndex]; 262 263 ii += channels; 264 i += channels; 265 } 266 } 267 } 268 269 Delete(data); 270 271 return res; 272 } 273 274 auto hdr = readHeader(); 275 276 if (hdr.idLength) 277 { 278 char[] id = New!(char[])(hdr.idLength); 279 istrm.fillArray(id); 280 281 version(TGADebug) 282 { 283 writefln("id = %s", id); 284 } 285 286 Delete(id); 287 } 288 289 if (hdr.encoding == TGAEncoding.RGB) { 290 img = readRawRGB(hdr); 291 } else if (hdr.encoding == TGAEncoding.RLE_RGB) { 292 img = readRLERGB(hdr); 293 } else { 294 return error("loadTGA error: only RGB images are supported"); 295 } 296 297 img.swapRGB(); 298 299 return compound(img, ""); 300 } 301 302 void swapRGB(SuperImage img) 303 { 304 foreach(x; 0..img.width) { 305 foreach(y; 0..img.height) 306 { 307 img[x, y] = Color4f(img[x, y].bgra); 308 } 309 } 310 } 311 312 /** 313 * Save TGA to file using local FileSystem. 314 * Causes GC allocation 315 */ 316 void saveTGA(SuperImage img, string filename) 317 { 318 OutputStream output = openForOutput(filename); 319 Compound!(bool, string) res = saveTGA(img, output); 320 output.close(); 321 322 if (!res[0]) 323 throw new TGALoadException(res[1]); 324 } 325 326 /** 327 * Save TGA to stream. 328 * GC-free 329 */ 330 Compound!(bool, string) saveTGA(SuperImage img, OutputStream output) 331 { 332 Compound!(bool, string) error(string errorMsg) 333 { 334 return compound(false, errorMsg); 335 } 336 337 enum ubyte[12] tgaStart = [0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0]; 338 output.writeArray(tgaStart); 339 output.writeLE(cast(ushort)img.width); 340 output.writeLE(cast(ushort)img.height); 341 342 const bool hasAlpha = img.channels == 4; 343 344 if (img.channels == 3) { 345 output.writeLE(cast(ubyte)24); 346 output.writeLE(cast(ubyte)TgaOrigin.Upper); 347 } else if (img.channels == 4) { 348 output.writeLE(cast(ubyte)32); 349 output.writeLE(cast(ubyte)0x28); 350 } else { 351 return error("saveTGA error: unsupported number of channels"); 352 } 353 354 foreach(y; 0..img.height) { 355 foreach(x; 0..img.width) { 356 ubyte[4] rgb; 357 ColorRGBA color = img[x, y].convert(8); 358 rgb[0] = cast(ubyte)color[2]; 359 rgb[1] = cast(ubyte)color[1]; 360 rgb[2] = cast(ubyte)color[0]; 361 362 if (hasAlpha) { 363 rgb[3] = cast(ubyte)(color[3]); 364 output.writeArray(rgb[]); 365 } else { 366 output.writeArray(rgb[0..3]); 367 } 368 } 369 } 370 371 return compound(true, ""); 372 }