1 module stb.image; 2 3 import 4 core.bitop, 5 std.file, 6 std.path, 7 std.conv, 8 std.math, 9 std.range, 10 std.string, 11 std.mmfile, 12 std.typecons, 13 std.bitmanip, 14 std.exception, 15 std.algorithm, 16 17 core.stdc.string, 18 etc.c.zlib, 19 20 stb.image.write, 21 stb.image.resize, 22 stb.image.binding; 23 24 25 struct Color 26 { 27 this(uint r, uint g, uint b, uint a = 0) 28 { 29 this.r = cast(ubyte)r; 30 this.g = cast(ubyte)g; 31 this.b = cast(ubyte)b; 32 this.a = cast(ubyte)a; 33 } 34 35 static fromInt(uint n) 36 { 37 n = n.bswap; 38 return *cast(Color*)&n; 39 } 40 41 const 42 { 43 auto opBinary(string op: `*`)(in Color c) 44 { 45 return Color( 46 r * c.r / 255, 47 g * c.g / 255, 48 b * c.b / 255, 49 a * c.a / 255 50 ); 51 } 52 53 auto opBinary(string op: `+`)(in Color c) 54 { 55 return Color( 56 min(r + c.r, 255), 57 min(g + c.g, 255), 58 min(b + c.b, 255), 59 min(a + c.a, 255) 60 ); 61 } 62 63 auto opBinary(string op: `^`)(in Color c) 64 { 65 // TODO: CHECK FORCORRECTNESS 66 auto od = 255 - c.a; 67 auto ra = c.a + a * od / 255; 68 69 // TODO: WORKAROUND 70 if(!ra) ra = 1; 71 72 return Color( 73 (c.r * c.a + r * a * od / 255) / ra, 74 (c.g * c.a + g * a * od / 255) / ra, 75 (c.b * c.a + b * a * od / 255) / ra, 76 ra 77 ); 78 } 79 80 bool isGray(ubyte n) 81 { 82 auto a = min(r, g, b), b = max(r, g, b); 83 return abs(a - b) < 30 && b < n; 84 } 85 86 bool compare(in Color c, ubyte d) 87 { 88 return abs(r - c.r) + abs(g - c.g) + abs(b - c.b) + abs(a - c.a) <= d * 4; 89 } 90 } 91 92 ref opOpAssign(string op)(in Color c) 93 { 94 return this = this.opBinary!op(c); 95 } 96 97 ubyte 98 r, 99 g, 100 b, 101 a; 102 } 103 104 static assert(Color.sizeof == 4); 105 106 enum 107 { 108 colorGray = Color(128, 128, 128, 200), 109 colorBlack = Color(0, 0, 0, 255), 110 colorWhite = Color(255, 255, 255, 255), 111 colorTransparent = Color.init, 112 } 113 114 enum 115 { 116 IM_BMP, 117 IM_TGA, 118 IM_PNG, 119 IM_JPG, 120 } 121 122 final class Image 123 { 124 this(string name) 125 { 126 auto f = new MmFile(name); 127 128 scope(exit) 129 { 130 f.destroy; 131 } 132 133 this(f[]); 134 } 135 136 this(in void[] data) 137 { 138 auto p = cast(Color*)stbi_load_from_memory(data.ptr, cast(uint)data.length, cast(int*)&_w, cast(int*)&_h, null, 4); 139 enforce(p, `unknown/corrupted image`); 140 141 _data = p[0.._w * _h].dup; 142 stbi_image_free(p); 143 } 144 145 this(uint w, uint h, const(void)[] data = null) 146 { 147 assert(w && h); 148 149 if(data.length) 150 { 151 _data = cast(Color[])data; 152 assert(_data.length == w * h); 153 } 154 else 155 { 156 _data = new Color[w * h]; 157 } 158 159 _w = w; 160 _h = h; 161 } 162 163 this(in Image im) 164 { 165 _w = im._w; 166 _h = im._h; 167 _data = im._data.dup; 168 } 169 170 auto blit(in Image im, uint x, uint y) 171 { 172 assert(x + im._w <= _w && y + im._h <= _h); 173 174 foreach(j; 0..im._h) 175 { 176 memcpy(&this[x, j + y], &im[0, j], im.w * 4); 177 } 178 179 return this; 180 } 181 182 const 183 { 184 auto dup() 185 { 186 return new Image(this); 187 } 188 189 void saveToFile(string name) 190 { 191 ubyte f; 192 193 switch(name.extension.stripLeft(`.`).toLower) 194 { 195 case `png`: 196 f = IM_PNG; 197 break; 198 case `bmp`: 199 f = IM_BMP; 200 break; 201 case `tga`: 202 f = IM_TGA; 203 break; 204 case `jpg`: 205 case `jpeg`: 206 f = IM_JPG; 207 break; 208 default: 209 throw new Exception(`unknown image extension`); 210 } 211 212 saveToFile(name, f); 213 } 214 215 void saveToFile(string name, ubyte format) 216 { 217 std.file.write(name, save(format)); 218 } 219 220 auto save(ubyte format) 221 { 222 bool res; 223 void[] data; 224 225 final switch(format) 226 { 227 case IM_BMP: 228 res = !!stbi_write_bmp_to_func(&writerCallback, &data, _w, _h, 4, _data.ptr); 229 break; 230 case IM_TGA: 231 res = !!stbi_write_tga_to_func(&writerCallback, &data, _w, _h, 4, _data.ptr); 232 break; 233 case IM_PNG: 234 res = !!stbi_write_png_to_func(&writerCallback, &data, _w, _h, 4, _data.ptr, 0); 235 break; 236 case IM_JPG: 237 //res = !!stbi_write_jpg_to_func(&writerCallback, &data, _w, _h, 4, _data.ptr, 95); 238 } 239 240 assert(res); 241 return data; 242 } 243 244 auto subImage(uint x, uint y, uint w, uint h) 245 { 246 assert(x + w <= _w && y + h <= _h); 247 auto res = new Image(w, h); 248 249 foreach(i; 0..w) 250 foreach(j; 0..h) 251 res[j][] = this[y + j][x..x + w][]; 252 253 return res; 254 } 255 256 auto resize(uint w, uint h) 257 { 258 auto res = new Image(w, h); 259 stbir_resize_uint8(cast(ubyte*)_data.ptr, _w, _h, 0, cast(ubyte*)res[].ptr, w, h, 0, 4); 260 return res; 261 } 262 263 /*auto rotate(float angle) 264 { 265 Image res; 266 if(angle > 85 && angle < 95) 267 { 268 res = new Image(_h, _w); 269 270 foreach(i; 0.._w) 271 foreach(j; 0.._h) 272 res[j, i] = this[i, j]; // TODO: IT'S -90, NOT 90 273 //res[j, i] = this[_w - i - 1, j]; 274 } 275 return res; 276 }*/ 277 } 278 279 void clean() 280 { 281 _data 282 .filter!((ref a) => a.compare(Color(255, 0, 255, 255), 10)) 283 .each!((ref a) => a.a = 0); 284 285 normalizeTransparentPixels; 286 } 287 288 void normalizeTransparentPixels() 289 { 290 const uint N = _w * _h; 291 292 auto opaque = new byte[N]; 293 auto loose = new bool[N]; 294 295 uint[] pending; 296 uint[] pendingNext; 297 298 static immutable byte[2][8] offsets = 299 [ 300 [ -1, -1 ], 301 [ 0, -1 ], 302 [ 1, -1 ], 303 [ -1, 0 ], 304 [ 1, 0 ], 305 [ -1, 1 ], 306 [ 0, 1 ], 307 [ 1, 1 ] 308 ]; 309 310 auto image = cast(ubyte[])_data; 311 312 for(uint i = 0, j = 3; i < N; i++, j += 4) 313 { 314 if(image[j] == 0) 315 { 316 bool isLoose = true; 317 318 int x = i % _w; 319 int y = i / _w; 320 321 for(int k = 0; k < 8; k++) 322 { 323 int s = offsets[k][0]; 324 int t = offsets[k][1]; 325 326 if(x + s >= 0 && x + s < _w && y + t >= 0 && y + t < _h) 327 { 328 uint index = j + 4 * (s + t * _w); 329 330 if(image[index] != 0) 331 { 332 isLoose = false; 333 break; 334 } 335 } 336 } 337 338 if(isLoose) 339 { 340 loose[i] = true; 341 } 342 else 343 { 344 pending ~= i; 345 } 346 } 347 else 348 { 349 opaque[i] = -1; 350 } 351 } 352 353 while(pending.length > 0) 354 { 355 pendingNext = null; 356 357 for(uint p = 0; p < pending.length; p++) 358 { 359 uint i = pending[p] * 4; 360 uint j = pending[p]; 361 362 int x = j % _w; 363 int y = j / _w; 364 365 int r = 0; 366 int g = 0; 367 int b = 0; 368 369 int count = 0; 370 371 for(uint k = 0; k < 8; k++) 372 { 373 int s = offsets[k][0]; 374 int t = offsets[k][1]; 375 376 if(x + s >= 0 && x + s < _w && y + t >= 0 && y + t < _h) 377 { 378 t *= _w; 379 380 if(opaque[j + s + t] & 1) 381 { 382 uint index = i + 4 * (s + t); 383 384 r += image[index + 0]; 385 g += image[index + 1]; 386 b += image[index + 2]; 387 388 count++; 389 } 390 } 391 } 392 393 if(count > 0) 394 { 395 image[i + 0] = cast(ubyte)(r / count); 396 image[i + 1] = cast(ubyte)(g / count); 397 image[i + 2] = cast(ubyte)(b / count); 398 399 opaque[j] = cast(byte)0xFE; 400 401 for(uint k = 0; k < 8; k++) 402 { 403 int s = offsets[k][0]; 404 int t = offsets[k][1]; 405 406 if(x + s >= 0 && x + s < _w && y + t >= 0 && y + t < _h) 407 { 408 uint index = j + s + t * _w; 409 410 if(loose[index]) 411 { 412 pendingNext ~= index; 413 loose[index] = false; 414 } 415 } 416 } 417 } 418 else 419 { 420 pendingNext ~= j; 421 } 422 } 423 424 if(pendingNext.length > 0) 425 { 426 for(uint p = 0; p < pending.length; p++) 427 { 428 opaque[pending[p]] >>= 1; 429 } 430 } 431 432 pending.swap(pendingNext); 433 } 434 } 435 436 auto flip() 437 { 438 foreach(j; 0.._h / 2) 439 foreach(i; 0.._w) 440 { 441 swap(this[i, j], this[i, _h - j - 1]); 442 } 443 444 return this; 445 } 446 447 auto mirror() 448 { 449 foreach(i; 0.._w / 2) 450 foreach(j; 0.._h) 451 { 452 swap(this[i, j], this[_w - i - 1, j]); 453 } 454 455 return this; 456 } 457 458 inout 459 { 460 auto toMipmaps() 461 { 462 inout(Image)[] res = [ this ]; 463 464 while(true) 465 { 466 auto im = res.back; 467 468 if(im.w == 1 && im.h == 1) 469 { 470 break; 471 } 472 473 auto 474 w = max(im.w / 2, 1), 475 h = max(im.h / 2, 1); 476 477 res ~= cast(inout(Image))resize(w, h); 478 } 479 480 return res; 481 } 482 483 ref opIndex(uint x, uint y) 484 { 485 return _data[y * _w + x]; 486 } 487 488 auto opIndex(uint y) 489 { 490 auto c = y * _w; 491 return _data[c..c + _w]; 492 } 493 494 auto opSlice() 495 { 496 return _data; 497 } 498 } 499 500 @property const 501 { 502 auto w() { return _w; } 503 auto h() { return _h; } 504 } 505 506 private: 507 static extern(C) void writerCallback(void* ptr, void* data, int len) 508 { 509 *cast(void[]*)ptr ~= data[0..len]; 510 } 511 512 Color[] _data; 513 514 uint 515 _w, 516 _h; 517 } 518 519 private extern(C): 520 521 ubyte* compress_for_stb_image_write(in ubyte* data, uint len, uint* resLen, int level) 522 { 523 import core.stdc.stdlib; 524 525 auto dst = compressBound(len); 526 auto res = cast(ubyte*)malloc(dst); 527 528 if(compress2(res, &dst, data, len, level) == Z_OK) 529 { 530 *resLen = cast(uint)dst; 531 return res; 532 } 533 534 free(res); 535 return null; 536 }