1 module utga; 2 3 import std.stdio; 4 import std.algorithm; 5 import std.exception; 6 import core.stdc.string; 7 8 9 struct TGAHeader { 10 align (1): 11 ubyte idlength; 12 ubyte colormaptype; 13 ubyte datatypecode; 14 ushort colormaporigin; 15 ushort colormaplength; 16 ubyte colormapdepth; 17 ushort x_origin; 18 ushort y_origin; 19 ushort width; 20 ushort height; 21 ubyte bitsperpixel; 22 ubyte imagedescriptor; 23 } 24 25 struct TGAColor { 26 ubyte[4] bgra; 27 ubyte bytespp = 1; 28 29 this(ubyte R, ubyte G, ubyte B, ubyte A=255) 30 { 31 bytespp = 4; 32 bgra[0] = B; 33 bgra[1] = G; 34 bgra[2] = R; 35 bgra[3] = A; 36 } 37 38 this(ubyte v) 39 { 40 bgra[0] = v; 41 } 42 43 this(ubyte[] p) 44 { 45 bytespp = cast(ubyte)p.length; 46 for (int i=0; i<cast(int)p.length; i++) { 47 bgra[i] = p[i]; 48 } 49 } 50 51 ubyte opIndex(int i) 52 { 53 return bgra[i]; 54 } 55 56 TGAColor opBinary(string op)(float intensity) if (op == "*") 57 { 58 TGAColor res = this; 59 intensity = ( intensity > 1f ? 1f : (intensity < 0f ? 0f : intensity)); 60 for (int i=0; i<4; i++) res.bgra[i] = cast(ubyte)(bgra[i] * intensity); 61 return res; 62 } 63 } 64 65 class TGAImage { 66 protected: 67 ubyte[] _data; 68 ushort _width; 69 ushort _height; 70 ubyte _bytespp; 71 72 bool load_rle_data(File input) 73 { 74 ulong pixelcount = _width*_height; 75 ulong currentpixel = 0; 76 ulong currentbyte = 0; 77 TGAColor colorbuffer; 78 do { 79 ubyte chunkheader = 0; 80 input.rawRead((&chunkheader)[0..1]); 81 if (chunkheader<128) { 82 chunkheader++; 83 for (int i=0; i<chunkheader; i++) { 84 input.rawRead(colorbuffer.bgra[0.._bytespp]); 85 for (int t=0; t<_bytespp; t++) 86 _data[currentbyte++] = colorbuffer.bgra[t]; 87 currentpixel++; 88 if (currentpixel>pixelcount) 89 return false; 90 } 91 } 92 else { 93 chunkheader -= 127; 94 input.rawRead(colorbuffer.bgra[0.._bytespp]); 95 for (int i=0; i<chunkheader; i++) { 96 for (int t=0; t<_bytespp; t++) 97 _data[currentbyte++] = colorbuffer.bgra[t]; 98 currentpixel++; 99 if (currentpixel>pixelcount) 100 return false; 101 } 102 } 103 } while (currentpixel < pixelcount); 104 return true; 105 } 106 107 bool unload_rle_data(File output) 108 { 109 enum ubyte max_chunk_length = 128; 110 ulong npixels = _width*_height; 111 ulong curpix = 0; 112 while (curpix<npixels) { 113 ulong chunkstart = curpix*_bytespp; 114 ulong curbyte = curpix*_bytespp; 115 ubyte run_length = 1; 116 bool raw = true; 117 while (curpix+run_length<npixels && run_length<max_chunk_length) { 118 bool succ_eq = true; 119 for (int t=0; succ_eq && t<_bytespp; t++) 120 succ_eq = (_data[curbyte+t]==_data[curbyte+t+_bytespp]); 121 curbyte +=_bytespp; 122 if (1==run_length) { 123 raw = !succ_eq; 124 } 125 if (raw && succ_eq) { 126 run_length--; 127 break; 128 } 129 if (!raw && !succ_eq) { 130 break; 131 } 132 run_length++; 133 } 134 curpix += run_length; 135 auto tmp = cast(ubyte)(raw?run_length-1:run_length+127); 136 output.rawWrite((&tmp)[0..1]); 137 output.rawWrite(_data[chunkstart..(chunkstart+(raw?run_length*_bytespp:_bytespp))]); 138 } 139 return true; 140 } 141 142 public: 143 enum Format { 144 GRAYSCALE=1, 145 RGB=3, 146 RGBA=4 147 } 148 149 this() {} 150 151 this(ushort w, ushort h, ubyte bpp) 152 { 153 _width = w; 154 _height = h; 155 _bytespp = bpp; 156 _data.length = _width*_height*_bytespp; 157 } 158 159 this(const TGAImage img) 160 { 161 _width = img._width; 162 _height = img._height; 163 _bytespp = img._bytespp; 164 _data = img._data.dup; 165 } 166 167 bool read_tga_file(const string filename) 168 { 169 if (_data) _data = []; 170 auto input = File(filename, "rb"); 171 TGAHeader header; 172 input.rawRead((&header)[0..1]); 173 _width = header.width; 174 _height = header.height; 175 _bytespp = header.bitsperpixel >> 3; 176 enforce(0<_width && 0<_height && (Format.GRAYSCALE==_bytespp || Format.RGB==_bytespp || Format.RGBA==_bytespp)); 177 _data.length = _bytespp*_width*_height; 178 if (3==header.datatypecode || 2==header.datatypecode) { 179 input.rawRead(_data); 180 } 181 else if (10==header.datatypecode || 11==header.datatypecode) { 182 if (!load_rle_data(input)) { 183 input.close; 184 return false; 185 } 186 } 187 else { 188 input.close; 189 return false; 190 } 191 if (!(header.imagedescriptor & 0x20)) { 192 flip_vertically(); 193 } 194 if (header.imagedescriptor & 0x10) { 195 flip_horizontally(); 196 } 197 input.close; 198 return true; 199 } 200 201 bool write_tga_file(const string filename, bool rle=true) 202 { 203 ubyte[4] developer_area_ref = [0, 0, 0, 0]; 204 ubyte[4] extension_area_ref = [0, 0, 0, 0]; 205 ubyte[18] footer = ['T','R','U','E','V','I','S','I','O','N','-','X','F','I','L','E','.','\0']; 206 auto output = File(filename, "wb"); 207 TGAHeader header; 208 header.bitsperpixel = cast(ubyte)(_bytespp<<3); 209 header.width = _width; 210 header.height = _height; 211 header.datatypecode = (_bytespp==Format.GRAYSCALE?(rle?11:3):(rle?10:2)); 212 header.imagedescriptor = 0x20; 213 output.rawWrite((&header)[0..1]); 214 if (!rle) { 215 output.rawWrite(_data); 216 } 217 else { 218 if (!unload_rle_data(output)) 219 return false; 220 } 221 output.rawWrite(developer_area_ref); 222 output.rawWrite(extension_area_ref); 223 output.rawWrite(footer); 224 output.close; 225 return true; 226 } 227 228 bool flip_horizontally() 229 { 230 if (!_data) return false; 231 int half = _width >> 1; 232 for (int i=0; i<half; i++) { 233 for (int j=0; j<_height; j++) { 234 TGAColor c1 = get(i, j); 235 TGAColor c2 = get(_width-1-i, j); 236 set(i, j, c2); 237 set(_width-1-i, j, c1); 238 } 239 } 240 return true; 241 } 242 243 bool flip_vertically() 244 { 245 if (!_data) return false; 246 ulong bytes_per_line = _width*_bytespp; 247 ubyte[] line; line.length = bytes_per_line; 248 int half = _height >> 1; 249 for (int j=0; j<half; j++) { 250 ulong l1 = j*bytes_per_line; 251 ulong l2 = (_height-1-j)*bytes_per_line; 252 swapRanges(_data[l1..l1+bytes_per_line], _data[l2..l2+bytes_per_line]); 253 } 254 return true; 255 } 256 257 bool scale(ushort w, ushort h) 258 { 259 if (w<=0 || h<=0 || !_data) return false; 260 ubyte[] tdata; tdata.length = w*h*_bytespp; 261 int nscanline = 0; 262 int oscanline = 0; 263 int erry = 0; 264 ulong nlinebytes = w*_bytespp; 265 ulong olinebytes = _width*_bytespp; 266 for (int j=0; j<_height; j++) { 267 int errx = _width-w; 268 int nx = -cast(int)_bytespp; 269 int ox = -cast(int)_bytespp; 270 for (int i=0; i<_width; i++) { 271 ox += _bytespp; 272 errx += w; 273 while (errx>=cast(int)_width) { 274 errx -= _width; 275 nx += _bytespp; 276 tdata[nscanline+nx..nscanline+nx+_bytespp] = _data[oscanline+ox.._bytespp+oscanline+ox]; 277 } 278 } 279 erry += h; 280 oscanline += olinebytes; 281 while (erry>=cast(int)_height) { 282 if (erry>=cast(int)_height<<1) 283 tdata[nscanline+nlinebytes..nlinebytes+nscanline+nlinebytes] = tdata[nscanline..nscanline+nlinebytes]; 284 erry -= _height; 285 nscanline += nlinebytes; 286 } 287 } 288 _data = []; 289 _data = tdata; 290 _width = w; 291 _height = h; 292 return true; 293 } 294 295 TGAColor get(int x, int y) 296 { 297 if (!_data || x<0 || y<0 || x>=_width || y>=_height) 298 return TGAColor(); 299 return TGAColor(_data[(x+y*_width)*_bytespp..(x+y*_width)*_bytespp+_bytespp]); 300 } 301 302 bool set(int x, int y, TGAColor c) 303 { 304 if (!_data || x<0 || y<0 || x>=_width || y>=_height) 305 return false; 306 memcpy(_data.ptr+(x+y*_width)*_bytespp, c.bgra.ptr, _bytespp); 307 return true; 308 } 309 310 bool set(int x, int y, const TGAColor c) 311 { 312 if (!_data || x<0 || y<0 || x>=_width || y>=_height) 313 return false; 314 memcpy(_data.ptr+(x+y*_width)*_bytespp, c.bgra.ptr, _bytespp); 315 return true; 316 } 317 318 TGAImage opAssing(const TGAImage img) 319 { 320 if (this != img) { 321 if (_data) _data = []; 322 _width = img._width; 323 _height = img._height; 324 _bytespp = img._bytespp; 325 _data = img._data.dup; 326 } 327 return this; 328 } 329 330 @property 331 auto width() 332 { 333 return _width; 334 } 335 336 @property 337 auto height() 338 { 339 return _height; 340 } 341 342 @property 343 auto bytespp() { 344 return _bytespp; 345 } 346 347 @property 348 auto data() { 349 return _data; 350 } 351 352 @property 353 void clear() { 354 _data = []; 355 _data.length = _width*_height*_bytespp; 356 } 357 }