1 // Copyright 2019 Tero Hänninen. All rights reserved. 2 // SPDX-License-Identifier: BSD-2-Clause 3 module imagefmt; 4 5 import core.stdc.stdio; 6 import cstd = core.stdc.stdlib; 7 import imagefmt.bmp; 8 import imagefmt.tga; 9 import imagefmt.png; 10 import imagefmt.jpeg; 11 12 @nogc nothrow: 13 14 /// Basic image information. 15 struct IFInfo { 16 int w; /// width 17 int h; /// height 18 ubyte c; /// channels 19 ubyte e; /// error code or zero 20 } 21 22 /// Image returned from the read functions. Data is in buf8 or buf16. 23 struct IFImage { 24 int w; /// width 25 int h; /// height 26 ubyte c; /// channels in buf, 1 = y, 2 = ya, 3 = rgb, 4 = rgba 27 ubyte cinfile; /// channels found in file 28 ubyte bpc; /// bits per channel, 8 or 16 29 ubyte e; /// error code or zero 30 union { 31 ubyte[] buf8; /// 32 ushort[] buf16; /// 33 } 34 35 @nogc nothrow: 36 37 /// Frees the image data. 38 void free() { 39 _free(buf8.ptr); 40 buf8 = null; 41 } 42 } 43 44 /// Read interface. 45 struct Read { 46 void* stream; 47 /// returns number of bytes read; tries to read n bytes 48 int function(void* stream, ubyte* buf, int n) @nogc nothrow read; 49 /// returns 0 on success, -1 on error; 50 /// sets cursor to off(set) from current position 51 int function(void* stream, int off) @nogc nothrow seek; 52 } 53 54 /// Write interface. 55 struct Write { 56 void* stream; 57 /// returns the number of bytes written; tries to write all of buf. 58 int function(void* stream, ubyte[] buf) @nogc nothrow write; 59 /// returns 0 on success, -1 on error; forces a write of still unwritten data. 60 int function(void* stream) @nogc nothrow flush; 61 } 62 63 int fileread(void* st, ubyte* buf, int n) 64 { 65 return cast(int) fread(buf, 1, n, cast(FILE*) st); 66 } 67 68 int fileseek(void* st, int off) 69 { 70 return fseek(cast(FILE*) st, off, SEEK_CUR); 71 } 72 73 int filewrite(void* st, ubyte[] buf) 74 { 75 return cast(int) fwrite(buf.ptr, 1, buf.length, cast(FILE*) st); 76 } 77 78 int fileflush(void* st) 79 { 80 return fflush(cast(FILE*) st); 81 } 82 83 /// Maximum size for the result buffer the loader functions 84 /// don't reject with a "too large" error. 85 ulong MAXIMUM_IMAGE_SIZE = 0x7fff_ffff; 86 87 version(IF__CUSTOM_ALLOC) { 88 void* if__allocator; 89 void* function(void* al, size_t size) if__malloc; 90 void* function(void* al, void* ptr, size_t size) if__realloc; 91 void function(void* al, void* ptr) if__free; 92 93 void* _malloc(size_t size) { return if__malloc(if__allocator, size); } 94 void* _realloc(void* ptr, size_t size) { return if__realloc(if__allocator, ptr, size); } 95 void _free(void* ptr) { return if__free(if__allocator, ptr); } 96 } else { 97 void* _malloc(size_t size) { return cstd.malloc(size); } 98 void* _realloc(void* ptr, size_t size) { return cstd.realloc(ptr, size); } 99 void _free(void* ptr) { return cstd.free(ptr); } 100 } 101 102 /// Error values returned from the functions. 103 enum ERROR { fopen = 1, oom, stream, data, oddfmt, unsupp, dim, arg, bigimg, 104 nodata, lackdata, zinit, zstream } 105 106 /// Descriptions for errors. 107 immutable string[ERROR.max + 1] IF_ERROR = [ 108 0 : "no error", 109 ERROR.fopen : "cannot open file", 110 ERROR.oom : "out of memory", 111 ERROR.stream : "stream error", 112 ERROR.data : "bad data", 113 ERROR.oddfmt : "unknown format", 114 ERROR.unsupp : "unsupported", 115 ERROR.dim : "invalid dimensions", 116 ERROR.arg : "bad argument", 117 ERROR.bigimg : "image too large", 118 ERROR.nodata : "no data", // at all 119 ERROR.lackdata : "not enough data", 120 ERROR.zinit : "zlib init failed", 121 ERROR.zstream : "zlib stream error", 122 ]; 123 124 /// Reads basic information about an image. 125 IFInfo read_info(in char[] fname) 126 { 127 IFInfo info; 128 auto tmp = NTString(fname); 129 if (!tmp.ptr) { 130 info.e = ERROR.oom; 131 return info; 132 } 133 FILE* f = fopen(tmp.ptr, "rb"); 134 tmp.drop(); 135 if (!f) { 136 info.e = ERROR.fopen; 137 return info; 138 } 139 info = read_info(f); 140 fclose(f); 141 return info; 142 } 143 144 /// Reads from f which must already be open. Does not close it afterwards. 145 IFInfo read_info(FILE* f) 146 { 147 Read io = { cast(void*) f, &fileread, &fileseek }; 148 return read_info(io); 149 } 150 151 /// Reads basic information about an image. 152 IFInfo read_info(Read io) 153 { 154 ubyte[256] iobuf; 155 IFInfo info; 156 Reader rc; 157 info.e = init_reader(&rc, io, iobuf[0..$]); 158 if (info.e) return info; 159 if (detect_png(&rc)) return read_png_info(&rc); 160 if (detect_bmp(&rc)) return read_bmp_info(&rc); 161 if (detect_jpeg(&rc)) return read_jpeg_info(&rc); 162 if (detect_tga(&rc)) return read_tga_info(&rc); 163 info.e = ERROR.oddfmt; 164 return info; 165 } 166 167 /// Reads basic information about an image. 168 IFInfo read_info(in ubyte[] buf) 169 { 170 IFInfo info; 171 Reader rc; 172 Read io = { null, null, null }; 173 info.e = init_reader(&rc, io, cast(ubyte[]) buf); // the cast? care is taken! 174 if (info.e) return info; 175 if (detect_png(&rc)) return read_png_info(&rc); 176 if (detect_bmp(&rc)) return read_bmp_info(&rc); 177 if (detect_jpeg(&rc)) return read_jpeg_info(&rc); 178 if (detect_tga(&rc)) return read_tga_info(&rc); 179 info.e = ERROR.oddfmt; 180 return info; 181 } 182 183 /// Reads an image file, detecting its type. 184 IFImage read_image(in char[] fname, in int c = 0, in int bpc = 8) 185 { 186 IFImage image; 187 auto tmp = NTString(fname); 188 if (!tmp.ptr) { 189 image.e = ERROR.oom; 190 return image; 191 } 192 FILE* f = fopen(tmp.ptr, "rb"); 193 tmp.drop(); 194 if (f) { 195 image = read_image(f, c, bpc); 196 fclose(f); 197 } else 198 image.e = ERROR.fopen; 199 return image; 200 } 201 202 /// Reads from f which must already be open. Does not close it afterwards. 203 IFImage read_image(FILE* f, in int c = 0, in int bpc = 8) 204 { 205 IFImage image; 206 Read io = { cast(void*) f, &fileread, &fileseek }; 207 image = read_image(io, c, bpc); 208 return image; 209 } 210 211 /// Reads an image using given io functions. 212 IFImage read_image(Read io, in int c = 0, in int bpc = 8) 213 { 214 IFImage image; 215 Reader rc; 216 if (!io.stream || !io.read || !io.seek) { 217 image.e = ERROR.arg; 218 return image; 219 } 220 ubyte e; 221 ubyte[] iobuf = new_buffer(4096, e); if (e) return image; 222 scope(exit) _free(iobuf.ptr); 223 image.e = init_reader(&rc, io, iobuf); if (image.e) return image; 224 if (detect_png(&rc)) { image.e = read_png(&rc, image, c, bpc); return image; } 225 if (detect_bmp(&rc)) { image.e = read_bmp(&rc, image, c, bpc); return image; } 226 if (detect_jpeg(&rc)) { image.e = read_jpeg(&rc, image, c, bpc); return image; } 227 if (detect_tga(&rc)) { image.e = read_tga(&rc, image, c, bpc); return image; } 228 image.e = ERROR.oddfmt; 229 return image; 230 } 231 232 /// Reads an image from buf. 233 IFImage read_image(in ubyte[] buf, in int c = 0, in int bpc = 8) 234 { 235 IFImage image; 236 Reader rc; 237 Read io = { null, null, null }; 238 image.e = init_reader(&rc, io, cast(ubyte[]) buf); // the cast? care is taken! 239 if (image.e) return image; 240 if (detect_png(&rc)) { image.e = read_png(&rc, image, c, bpc); return image; } 241 if (detect_bmp(&rc)) { image.e = read_bmp(&rc, image, c, bpc); return image; } 242 if (detect_jpeg(&rc)) { image.e = read_jpeg(&rc, image, c, bpc); return image; } 243 if (detect_tga(&rc)) { image.e = read_tga(&rc, image, c, bpc); return image; } 244 image.e = ERROR.oddfmt; 245 return image; 246 } 247 248 /// Returns 0 on success, else an error code. Assumes RGB order for color components 249 /// in buf, if present. Note: The file will remain even if the write fails. 250 ubyte write_image(in char[] fname, int w, int h, in ubyte[] buf, int reqchans = 0) 251 { 252 const int fmt = fname2fmt(fname); 253 if (fmt == -1) 254 return ERROR.unsupp; 255 auto tmp = NTString(fname); 256 if (!tmp.ptr) 257 return ERROR.oom; 258 FILE* f = fopen(tmp.ptr, "wb"); 259 tmp.drop(); 260 if (!f) 261 return ERROR.fopen; 262 ubyte e = write_image(fmt, f, w, h, buf, reqchans); 263 fclose(f); 264 return e; 265 } 266 267 enum IF_BMP = 0; /// the BMP format 268 enum IF_TGA = 1; /// the TGA format 269 enum IF_PNG = 2; /// the PNG format 270 enum IF_JPG = 3; /// the JPEG format 271 272 /// Writes to f which must already be open. Does not close it afterwards. Returns 0 273 /// on success, else an error code. Assumes RGB order for color components in buf, if 274 /// present. Note: The file will remain even if the write fails. 275 ubyte write_image(int fmt, FILE* f, int w, int h, in ubyte[] buf, int reqchans = 0) 276 { 277 Write io = { cast(void*) f, &filewrite, &fileflush }; 278 return write_image(fmt, io, w, h, buf, reqchans); 279 } 280 281 /// Returns 0 on success, else an error code. Assumes RGB order for color components 282 /// in buf, if present. 283 ubyte write_image(int fmt, Write io, int w, int h, in ubyte[] buf, int reqchans = 0) 284 { 285 Writer wc; 286 if (!io.stream || !io.write || !io.flush) 287 return ERROR.arg; 288 ubyte e; 289 ubyte[] iobuf = new_buffer(4096, e); if (e) return e; 290 scope(exit) _free(iobuf.ptr); 291 e = init_writer(&wc, io, iobuf); if (e) return e; 292 e = _write_image(fmt, &wc, w, h, buf, reqchans); if (e) return e; 293 e = fullflush(&wc); 294 return e; 295 } 296 297 /// Returns null on error and the error code through e. Assumes RGB order for color 298 /// components in buf, if present. 299 ubyte[] write_image_mem(int fmt, int w, int h, in ubyte[] buf, int reqchans, out int e) 300 { 301 Writer wc; 302 Write io = { null, null, null }; 303 e = init_writer(&wc, io, null); if (e) goto failure; 304 e = _write_image(fmt, &wc, w, h, buf, reqchans); if (e) goto failure; 305 306 if ((wc.cap - wc.n) * 100 / wc.cap > 20) { // max 20% waste 307 ubyte* p = cast(ubyte*) _realloc(wc.buf, wc.n); 308 if (!p) goto failure; 309 wc.buf = p; 310 } 311 312 return wc.buf[0..wc.n]; 313 failure: 314 _free(wc.buf); 315 return null; 316 } 317 318 /* ------------------ conversions ------------------ */ 319 320 /// Converts an 8-bit buffer to a 16-bit buffer in place. 321 /// On error, returns null and frees the original buffer. 322 ushort[] bpc8to16(ubyte[] b8) 323 { 324 ubyte* p8 = cast(ubyte*) _realloc(b8.ptr, b8.length * 2); 325 if (!p8) { 326 _free(b8.ptr); 327 return null; 328 } 329 ushort[] b16 = (cast(ushort*) p8)[0 .. b8.length]; 330 for (size_t i = b8.length - 1; i < b8.length; --i) 331 b16[i] = p8[i] * 257; 332 return b16; 333 } 334 335 /// Converts a 16-bit buffer to an 8-bit buffer in place. 336 /// On error, returns null and frees the original buffer. 337 ubyte[] bpc16to8(ushort[] b16) 338 { 339 ubyte[] b8 = (cast(ubyte*) b16.ptr)[0 .. b16.length]; 340 for (size_t i = 0; i < b16.length; i++) 341 b8[i] = b16[i] >> 8; 342 ubyte* p8 = cast(ubyte*) _realloc(b16.ptr, b16.length); 343 if (!p8) { 344 _free(b16.ptr); 345 return null; 346 } 347 return p8[0 .. b8.length]; 348 } 349 350 alias conv8 = void function(in ubyte[] src, ubyte[] tgt) @nogc nothrow; 351 alias conv16 = void function(in ushort[] src, ushort[] tgt) @nogc nothrow; 352 353 void* getconv(in int sc, in int tc, in int bpc) 354 { 355 if (sc == tc) 356 return bpc == 8 ? © : cast(void*) ©16; 357 switch (16*sc + tc) with(CHANS) { 358 case 16*y + ya : return bpc == 8 ? &conv_y2ya : cast(void*) &conv16_y2ya; 359 case 16*y + rgb : return bpc == 8 ? &conv_y2rgb : cast(void*) &conv16_y2rgb; 360 case 16*y + rgba : return bpc == 8 ? &conv_y2rgba : cast(void*) &conv16_y2rgba; 361 case 16*y + bgr : return bpc == 8 ? &conv_y2rgb : cast(void*) &conv16_y2rgb; // reuse 362 case 16*y + bgra : return bpc == 8 ? &conv_y2rgba : cast(void*) &conv16_y2rgba; // reuse 363 case 16*ya + y : return bpc == 8 ? &conv_ya2y : cast(void*) &conv16_ya2y; 364 case 16*ya + rgb : return bpc == 8 ? &conv_ya2rgb : cast(void*) &conv16_ya2rgb; 365 case 16*ya + rgba : return bpc == 8 ? &conv_ya2rgba : cast(void*) &conv16_ya2rgba; 366 case 16*ya + bgr : return bpc == 8 ? &conv_ya2rgb : cast(void*) &conv16_ya2rgb; // reuse 367 case 16*ya + bgra : return bpc == 8 ? &conv_ya2rgba : cast(void*) &conv16_ya2rgba; // reuse 368 case 16*rgb + y : return bpc == 8 ? &conv_rgb2y : cast(void*) &conv16_rgb2y; 369 case 16*rgb + ya : return bpc == 8 ? &conv_rgb2ya : cast(void*) &conv16_rgb2ya; 370 case 16*rgb + rgba : return bpc == 8 ? &conv_rgb2rgba : cast(void*) &conv16_rgb2rgba; 371 case 16*rgb + bgr : return bpc == 8 ? &conv_rgb2bgr : cast(void*) &conv16_rgb2bgr; 372 case 16*rgb + bgra : return bpc == 8 ? &conv_rgb2bgra : cast(void*) &conv16_rgb2bgra; 373 case 16*rgba + y : return bpc == 8 ? &conv_rgba2y : cast(void*) &conv16_rgba2y; 374 case 16*rgba + ya : return bpc == 8 ? &conv_rgba2ya : cast(void*) &conv16_rgba2ya; 375 case 16*rgba + rgb : return bpc == 8 ? &conv_rgba2rgb : cast(void*) &conv16_rgba2rgb; 376 case 16*rgba + bgr : return bpc == 8 ? &conv_rgba2bgr : cast(void*) &conv16_rgba2bgr; 377 case 16*rgba + bgra : return bpc == 8 ? &conv_rgba2bgra : cast(void*) &conv16_rgba2bgra; 378 case 16*bgr + y : return bpc == 8 ? &conv_bgr2y : cast(void*) &conv16_bgr2y; 379 case 16*bgr + ya : return bpc == 8 ? &conv_bgr2ya : cast(void*) &conv16_bgr2ya; 380 case 16*bgr + rgb : return bpc == 8 ? &conv_rgb2bgr : cast(void*) &conv16_rgb2bgr; // reuse 381 case 16*bgr + rgba : return bpc == 8 ? &conv_rgb2bgra : cast(void*) &conv16_rgb2bgra; // reuse 382 case 16*bgra + y : return bpc == 8 ? &conv_bgra2y : cast(void*) &conv16_bgra2y; 383 case 16*bgra + ya : return bpc == 8 ? &conv_bgra2ya : cast(void*) &conv16_bgra2ya; 384 case 16*bgra + rgb : return bpc == 8 ? &conv_rgba2bgr : cast(void*) &conv16_rgba2bgr; // reuse 385 case 16*bgra + rgba : return bpc == 8 ? &conv_rgba2bgra : cast(void*) &conv16_rgba2bgra; // reuse 386 default: assert(0); 387 } 388 } 389 390 ubyte luminance(in ubyte r, in ubyte g, in ubyte b) 391 { 392 return cast(ubyte) (0.21*r + 0.64*g + 0.15*b); // arbitrary weights 393 } 394 395 void copy(in ubyte[] src, ubyte[] tgt) 396 { 397 tgt[0..$] = src[0..$]; 398 } 399 400 void conv_y2ya(in ubyte[] src, ubyte[] tgt) 401 { 402 for (int k, t; k < src.length; k+=1, t+=2) { 403 tgt[t] = src[k]; 404 tgt[t+1] = 255; 405 } 406 } 407 408 void conv_y2rgb(in ubyte[] src, ubyte[] tgt) 409 { 410 for (int k, t; k < src.length; k+=1, t+=3) 411 tgt[t .. t+3] = src[k]; 412 } 413 414 void conv_y2rgba(in ubyte[] src, ubyte[] tgt) 415 { 416 for (int k, t; k < src.length; k+=1, t+=4) { 417 tgt[t .. t+3] = src[k]; 418 tgt[t+3] = 255; 419 } 420 } 421 422 void conv_ya2y(in ubyte[] src, ubyte[] tgt) 423 { 424 for (int k, t; k < src.length; k+=2, t+=1) 425 tgt[t] = src[k]; 426 } 427 428 void conv_ya2rgb(in ubyte[] src, ubyte[] tgt) 429 { 430 for (int k, t; k < src.length; k+=2, t+=3) 431 tgt[t .. t+3] = src[k]; 432 } 433 434 void conv_ya2rgba(in ubyte[] src, ubyte[] tgt) 435 { 436 for (int k, t; k < src.length; k+=2, t+=4) { 437 tgt[t .. t+3] = src[k]; 438 tgt[t+3] = src[k+1]; 439 } 440 } 441 442 void conv_rgb2y(in ubyte[] src, ubyte[] tgt) 443 { 444 for (int k, t; k < src.length; k+=3, t+=1) 445 tgt[t] = luminance(src[k], src[k+1], src[k+2]); 446 } 447 448 void conv_rgb2ya(in ubyte[] src, ubyte[] tgt) 449 { 450 for (int k, t; k < src.length; k+=3, t+=2) { 451 tgt[t] = luminance(src[k], src[k+1], src[k+2]); 452 tgt[t+1] = 255; 453 } 454 } 455 456 void conv_rgb2rgba(in ubyte[] src, ubyte[] tgt) 457 { 458 for (int k, t; k < src.length; k+=3, t+=4) { 459 tgt[t .. t+3] = src[k .. k+3]; 460 tgt[t+3] = 255; 461 } 462 } 463 464 void conv_rgb2bgr(in ubyte[] src, ubyte[] tgt) 465 { 466 for (int k; k < src.length; k+=3) { 467 tgt[k ] = src[k+2]; 468 tgt[k+1] = src[k+1]; 469 tgt[k+2] = src[k ]; 470 } 471 } 472 473 void conv_rgb2bgra(in ubyte[] src, ubyte[] tgt) 474 { 475 for (int k, t; k < src.length; k+=3, t+=4) { 476 tgt[t ] = src[k+2]; 477 tgt[t+1] = src[k+1]; 478 tgt[t+2] = src[k ]; 479 tgt[t+3] = 255; 480 } 481 } 482 483 void conv_rgba2y(in ubyte[] src, ubyte[] tgt) 484 { 485 for (int k, t; k < src.length; k+=4, t+=1) 486 tgt[t] = luminance(src[k], src[k+1], src[k+2]); 487 } 488 489 void conv_rgba2ya(in ubyte[] src, ubyte[] tgt) 490 { 491 for (int k, t; k < src.length; k+=4, t+=2) { 492 tgt[t] = luminance(src[k], src[k+1], src[k+2]); 493 tgt[t+1] = src[k+3]; 494 } 495 } 496 497 void conv_rgba2rgb(in ubyte[] src, ubyte[] tgt) 498 { 499 for (int k, t; k < src.length; k+=4, t+=3) 500 tgt[t .. t+3] = src[k .. k+3]; 501 } 502 503 void conv_rgba2bgr(in ubyte[] src, ubyte[] tgt) 504 { 505 for (int k, t; k < src.length; k+=4, t+=3) { 506 tgt[t ] = src[k+2]; 507 tgt[t+1] = src[k+1]; 508 tgt[t+2] = src[k ]; 509 } 510 } 511 512 void conv_rgba2bgra(in ubyte[] src, ubyte[] tgt) 513 { 514 for (int k, t; k < src.length; k+=4, t+=4) { 515 tgt[t ] = src[k+2]; 516 tgt[t+1] = src[k+1]; 517 tgt[t+2] = src[k ]; 518 tgt[t+3] = src[k+3]; 519 } 520 } 521 522 void conv_bgr2y(in ubyte[] src, ubyte[] tgt) 523 { 524 for (int k, t; k < src.length; k+=3, t+=1) 525 tgt[t] = luminance(src[k+2], src[k+1], src[k+1]); 526 } 527 528 void conv_bgr2ya(in ubyte[] src, ubyte[] tgt) 529 { 530 for (int k, t; k < src.length; k+=3, t+=2) { 531 tgt[t] = luminance(src[k+2], src[k+1], src[k+1]); 532 tgt[t+1] = 255; 533 } 534 } 535 536 void conv_bgra2y(in ubyte[] src, ubyte[] tgt) 537 { 538 for (int k, t; k < src.length; k+=4, t+=1) 539 tgt[t] = luminance(src[k+2], src[k+1], src[k]); 540 } 541 542 void conv_bgra2ya(in ubyte[] src, ubyte[] tgt) 543 { 544 for (int k, t; k < src.length; k+=4, t+=2) { 545 tgt[t] = luminance(src[k+2], src[k+1], src[k]); 546 tgt[t+1] = 255; 547 } 548 } 549 550 /* --------------- 16-bit --------------- */ 551 552 ushort luminance16(in ushort r, in ushort g, in ushort b) 553 { 554 return cast(ushort) (0.21*r + 0.64*g + 0.15*b); // arbitrary weights 555 } 556 557 void copy16(in ushort[] src, ushort[] tgt) 558 { 559 tgt[0..$] = src[0..$]; 560 } 561 562 void conv16_y2ya(in ushort[] src, ushort[] tgt) 563 { 564 for (int k, t; k < src.length; k+=1, t+=2) { 565 tgt[t] = src[k]; 566 tgt[t+1] = 0xffff; 567 } 568 } 569 570 void conv16_y2rgb(in ushort[] src, ushort[] tgt) 571 { 572 for (int k, t; k < src.length; k+=1, t+=3) 573 tgt[t .. t+3] = src[k]; 574 } 575 576 void conv16_y2rgba(in ushort[] src, ushort[] tgt) 577 { 578 for (int k, t; k < src.length; k+=1, t+=4) { 579 tgt[t .. t+3] = src[k]; 580 tgt[t+3] = 0xffff; 581 } 582 } 583 584 void conv16_ya2y(in ushort[] src, ushort[] tgt) 585 { 586 for (int k, t; k < src.length; k+=2, t+=1) 587 tgt[t] = src[k]; 588 } 589 590 void conv16_ya2rgb(in ushort[] src, ushort[] tgt) 591 { 592 for (int k, t; k < src.length; k+=2, t+=3) 593 tgt[t .. t+3] = src[k]; 594 } 595 596 void conv16_ya2rgba(in ushort[] src, ushort[] tgt) 597 { 598 for (int k, t; k < src.length; k+=2, t+=4) { 599 tgt[t .. t+3] = src[k]; 600 tgt[t+3] = src[k+1]; 601 } 602 } 603 604 void conv16_rgb2y(in ushort[] src, ushort[] tgt) 605 { 606 for (int k, t; k < src.length; k+=3, t+=1) 607 tgt[t] = luminance16(src[k], src[k+1], src[k+2]); 608 } 609 610 void conv16_rgb2ya(in ushort[] src, ushort[] tgt) 611 { 612 for (int k, t; k < src.length; k+=3, t+=2) { 613 tgt[t] = luminance16(src[k], src[k+1], src[k+2]); 614 tgt[t+1] = 0xffff; 615 } 616 } 617 618 void conv16_rgb2rgba(in ushort[] src, ushort[] tgt) 619 { 620 for (int k, t; k < src.length; k+=3, t+=4) { 621 tgt[t .. t+3] = src[k .. k+3]; 622 tgt[t+3] = 0xffff; 623 } 624 } 625 626 void conv16_rgb2bgr(in ushort[] src, ushort[] tgt) 627 { 628 for (int k; k < src.length; k+=3) { 629 tgt[k ] = src[k+2]; 630 tgt[k+1] = src[k+1]; 631 tgt[k+2] = src[k ]; 632 } 633 } 634 635 void conv16_rgb2bgra(in ushort[] src, ushort[] tgt) 636 { 637 for (int k, t; k < src.length; k+=3, t+=4) { 638 tgt[t ] = src[k+2]; 639 tgt[t+1] = src[k+1]; 640 tgt[t+2] = src[k ]; 641 tgt[t+3] = 0xffff; 642 } 643 } 644 645 void conv16_rgba2y(in ushort[] src, ushort[] tgt) 646 { 647 for (int k, t; k < src.length; k+=4, t+=1) 648 tgt[t] = luminance16(src[k], src[k+1], src[k+2]); 649 } 650 651 void conv16_rgba2ya(in ushort[] src, ushort[] tgt) 652 { 653 for (int k, t; k < src.length; k+=4, t+=2) { 654 tgt[t] = luminance16(src[k], src[k+1], src[k+2]); 655 tgt[t+1] = src[k+3]; 656 } 657 } 658 659 void conv16_rgba2rgb(in ushort[] src, ushort[] tgt) 660 { 661 for (int k, t; k < src.length; k+=4, t+=3) 662 tgt[t .. t+3] = src[k .. k+3]; 663 } 664 665 void conv16_rgba2bgr(in ushort[] src, ushort[] tgt) 666 { 667 for (int k, t; k < src.length; k+=4, t+=3) { 668 tgt[t ] = src[k+2]; 669 tgt[t+1] = src[k+1]; 670 tgt[t+2] = src[k ]; 671 } 672 } 673 674 void conv16_rgba2bgra(in ushort[] src, ushort[] tgt) 675 { 676 for (int k, t; k < src.length; k+=4, t+=4) { 677 tgt[t ] = src[k+2]; 678 tgt[t+1] = src[k+1]; 679 tgt[t+2] = src[k ]; 680 tgt[t+3] = src[k+3]; 681 } 682 } 683 684 void conv16_bgr2y(in ushort[] src, ushort[] tgt) 685 { 686 for (int k, t; k < src.length; k+=3, t+=1) 687 tgt[t] = luminance16(src[k+2], src[k+1], src[k+1]); 688 } 689 690 void conv16_bgr2ya(in ushort[] src, ushort[] tgt) 691 { 692 for (int k, t; k < src.length; k+=3, t+=2) { 693 tgt[t] = luminance16(src[k+2], src[k+1], src[k+1]); 694 tgt[t+1] = 0xffff; 695 } 696 } 697 698 void conv16_bgra2y(in ushort[] src, ushort[] tgt) 699 { 700 for (int k, t; k < src.length; k+=4, t+=1) 701 tgt[t] = luminance16(src[k+2], src[k+1], src[k]); 702 } 703 704 void conv16_bgra2ya(in ushort[] src, ushort[] tgt) 705 { 706 for (int k, t; k < src.length; k+=4, t+=2) { 707 tgt[t] = luminance16(src[k+2], src[k+1], src[k]); 708 tgt[t+1] = 0xffff; 709 } 710 } 711 712 /*------------------------------*/ package: /*------------------------------*/ 713 714 ubyte _write_image(int fmt, Writer* wc, int w, int h, in ubyte[] buf, int reqchans) 715 { 716 switch (fmt) { 717 case IF_BMP: return write_bmp(wc, w, h, buf, reqchans); 718 case IF_TGA: return write_tga(wc, w, h, buf, reqchans); 719 case IF_PNG: return write_png(wc, w, h, buf, reqchans); 720 // case IF_JPG: return write_jpg(wc, w, h, buf, reqchans); 721 default: 722 return ERROR.unsupp; 723 } 724 } 725 726 enum CHANS { unknown, y, ya, rgb, rgba, bgr, bgra } 727 728 struct Reader { 729 private: 730 Read io; 731 ubyte* buf; 732 int cap; 733 int a; 734 int b; 735 int bufpos; // buffer's start position 736 int iopos; // position of the io cursor 737 public: 738 bool fail; 739 } 740 741 void function(Reader* rc) fillbuf; 742 void function(Reader* rc) reset2start; 743 void function(Reader* rc, int n) skip; 744 745 ubyte init_reader(Reader* rc, Read io, ubyte[] buf) 746 { 747 if (buf.length < 16) { 748 if (io.stream) return ERROR.arg; 749 if (!buf.length) return ERROR.nodata; 750 } 751 rc.io = io; 752 rc.buf = buf.ptr; 753 rc.cap = cast(int) buf.length; 754 rc.a = 0; 755 rc.b = cast(int) buf.length; 756 rc.bufpos = 0; 757 rc.iopos = 0; 758 rc.fail = false; 759 if (rc.io.stream) { 760 fillbuf = &fillbuf_io; 761 reset2start = &reset2start_io; 762 skip = &skip_io; 763 fillbuf(rc); 764 if (rc.iopos == 0) 765 return ERROR.nodata; 766 } else { 767 fillbuf = &fillbuf_mem; 768 reset2start = &reset2start_mem; 769 skip = &skip_mem; 770 } 771 return 0; 772 } 773 774 void fillbuf_mem(Reader* rc) 775 { 776 rc.a = 0; 777 rc.fail = true; 778 } 779 780 void reset2start_mem(Reader* rc) 781 { 782 rc.a = 0; 783 rc.fail = false; 784 } 785 786 void skip_mem(Reader* rc, int n) 787 { 788 if (n <= rc.b - rc.a) { 789 rc.a += n; 790 return; 791 } 792 rc.fail = true; 793 } 794 795 void fillbuf_io(Reader* rc) 796 { 797 rc.a = 0; 798 if (rc.fail) 799 return; 800 int n = rc.io.read(rc.io.stream, rc.buf, rc.cap); 801 rc.bufpos = rc.iopos; 802 rc.iopos += n; 803 rc.b = n; 804 rc.fail = n == 0; 805 } 806 807 void reset2start_io(Reader* rc) 808 { 809 rc.fail = false; 810 rc.a = 0; 811 812 // this assumes buffer has been filled 813 const int off = -rc.iopos + rc.cap * (rc.bufpos == 0); 814 815 if (rc.io.seek(rc.io.stream, off) == 0) 816 rc.iopos += off; 817 else 818 rc.fail = true; 819 if (rc.bufpos != 0) 820 fillbuf(rc); 821 } 822 823 void skip_io(Reader* rc, int n) 824 { 825 if (n <= rc.b - rc.a) { 826 rc.a += n; 827 return; 828 } 829 if (rc.a < rc.b) { 830 n -= rc.b - rc.a; 831 rc.a = rc.b; 832 } 833 if (rc.io.seek(rc.io.stream, n) == 0) 834 rc.iopos += n; 835 else 836 rc.fail = true; 837 } 838 839 // does not allow jumping backwards 840 void skipto(Reader* rc, int pos) 841 { 842 if (pos >= rc.bufpos + rc.b) { 843 skip(rc, pos - rc.bufpos + rc.a); 844 return; 845 } 846 if (pos >= rc.bufpos + rc.a) { 847 rc.a = pos - rc.bufpos; 848 return; 849 } 850 rc.fail = true; 851 } 852 853 void read_block(Reader* rc, ubyte[] tgt) 854 { 855 int ti; 856 while (true) { 857 const size_t need = tgt.length - ti; 858 if (rc.a + need <= rc.b) { 859 tgt[ti .. $] = rc.buf[rc.a .. rc.a + need]; 860 rc.a += need; 861 return; 862 } 863 if (rc.a < rc.b) { 864 const int got = rc.b - rc.a; 865 tgt[ti .. ti + got] = rc.buf[rc.a .. rc.b]; 866 rc.a += got; 867 ti += got; 868 } 869 fillbuf(rc); 870 } 871 } 872 873 // Returns a slice of fresh data in the buffer filling it 874 // first if no fresh bytes in it already. 875 ubyte[] read_slice(Reader* rc, in int maxn) 876 { 877 do { 878 if (rc.a < rc.b) { 879 const int a = rc.a; 880 const int avail = rc.b - rc.a; 881 const int take = maxn < avail ? maxn : avail; 882 rc.a += take; 883 return rc.buf[a .. a + take]; 884 } 885 fillbuf(rc); 886 } while (!rc.fail); 887 return null; 888 } 889 890 ubyte read_u8(Reader* rc) 891 { 892 if (rc.a < rc.b) 893 return rc.buf[rc.a++]; 894 if (rc.b == rc.cap) { 895 fillbuf(rc); 896 return rc.buf[rc.a++]; 897 } 898 rc.fail = true; 899 return 0; 900 } 901 902 ushort read_u16le(Reader* rc) 903 { 904 ubyte a = read_u8(rc); 905 return (read_u8(rc) << 8) + a; 906 } 907 908 ushort read_u16be(Reader* rc) 909 { 910 ubyte a = read_u8(rc); 911 return (a << 8) + read_u8(rc); 912 } 913 914 uint read_u32le(Reader* rc) 915 { 916 ushort a = read_u16le(rc); 917 return (read_u16le(rc) << 16) + a; 918 } 919 920 uint read_u32be(Reader* rc) 921 { 922 ushort a = read_u16be(rc); 923 return (a << 16) + read_u16be(rc); 924 } 925 926 struct Writer { 927 Write io; 928 ubyte* buf; 929 int cap; 930 int n; 931 bool fail; 932 } 933 934 ubyte init_writer(Writer* wc, Write io, ubyte[] iobuf) 935 { 936 ubyte e = 0; 937 wc.io = io; 938 if (io.stream) { 939 wc.buf = iobuf.ptr; 940 wc.cap = cast(int) iobuf.length; 941 } else { 942 const int initcap = 32 * 1024; 943 wc.buf = new_buffer(initcap, e).ptr; 944 wc.cap = initcap; 945 } 946 wc.n = 0; 947 wc.fail = false; 948 return e; 949 } 950 951 // Flushes writer's buffer and calls io.flush. 952 ubyte fullflush(Writer* wc) 953 { 954 if (!wc.io.stream) 955 return 0; 956 weakflush(wc); 957 wc.fail |= wc.io.flush(wc.io.stream) != 0; 958 return wc.fail ? ERROR.stream : 0; 959 } 960 961 // Only flushes writer's buffer, does not call io.flush. 962 void weakflush(Writer* wc) 963 { 964 assert(wc.io.stream); 965 int c = 0; 966 while (c < wc.n) { 967 int written = wc.io.write(wc.io.stream, wc.buf[c..wc.n]); 968 c += written; 969 if (!written) { 970 wc.fail = true; 971 return; 972 } 973 } 974 wc.n = 0; 975 } 976 977 void morespace(Writer* wc) 978 { 979 if (wc.io.stream) { 980 weakflush(wc); 981 } else if (wc.n == wc.cap) { 982 const int newcap = 2 * wc.cap; 983 ubyte* ptr = cast(ubyte*) _realloc(wc.buf, newcap); 984 if (!ptr) { 985 wc.fail = true; 986 } else { 987 wc.cap = newcap; 988 wc.buf = ptr; 989 } 990 } 991 } 992 993 void write_u8(Writer* wc, in ubyte x) 994 { 995 if (wc.n < wc.cap) { 996 wc.buf[wc.n++] = x; 997 return; 998 } 999 morespace(wc); 1000 if (wc.n < wc.cap) 1001 wc.buf[wc.n++] = x; 1002 } 1003 1004 void write_u16le(Writer* wc, in ushort x) 1005 { 1006 write_u8(wc, x & 0xff); 1007 write_u8(wc, x >> 8); 1008 } 1009 1010 void write_u32le(Writer* wc, in uint x) 1011 { 1012 write_u16le(wc, x & 0xffff); 1013 write_u16le(wc, x >> 16); 1014 } 1015 1016 void write_block(Writer* wc, in ubyte[] block) 1017 { 1018 int k = wc.cap - wc.n; 1019 int todo = cast(int) block.length; 1020 if (todo <= k) { 1021 wc.buf[wc.n .. wc.n + todo] = block[0..todo]; 1022 wc.n += todo; 1023 return; 1024 } 1025 int amount = k; 1026 int bi = 0; 1027 do { 1028 wc.buf[wc.n .. wc.n + amount] = block[bi .. bi + amount]; 1029 wc.n += amount; 1030 todo -= amount; 1031 if (!todo) 1032 return; 1033 bi += amount; 1034 morespace(wc); 1035 k = wc.cap - wc.n; 1036 amount = k < todo ? k : todo; 1037 } while (!wc.fail); 1038 } 1039 1040 /* --------------- helper constructs --------------- */ 1041 1042 int findlast(in char[] s, in char c) 1043 { 1044 int i; 1045 for (i = cast(int) s.length - 1; i >= 0; i--) { 1046 if (s[i] == c) 1047 break; 1048 } 1049 return i; 1050 } 1051 1052 int fname2fmt(in char[] fname) 1053 { 1054 int i = findlast(fname, '.'); 1055 const int extlen = cast(int) fname.length - i - 1; // exclude dot 1056 if (i < 0 || extlen < 3 || extlen > 4) 1057 return -1; 1058 char[4] extbuf; 1059 foreach (k, char c; fname[i+1 .. $]) 1060 extbuf[k] = cast(char) (c >= 'A' && c <= 'Z' ? c + 'a' - 'A' : c); 1061 char[] ext = extbuf[0..extlen]; 1062 switch (ext[0]) { 1063 case 't': if (ext == "tga") return IF_TGA; else return -1; 1064 case 'b': if (ext == "bmp") return IF_BMP; else return -1; 1065 case 'p': if (ext == "png") return IF_PNG; else return -1; 1066 case 'j': if (ext == "jpg" || ext == "jpeg") return IF_JPG; return -1; 1067 default: return -1; 1068 } 1069 } 1070 1071 ubyte[] new_buffer(in size_t count, ref ubyte err) 1072 { 1073 ubyte* p = cast(ubyte*) _malloc(count); 1074 if (!p) { 1075 err = ERROR.oom; 1076 return null; 1077 } 1078 return p[0..count]; 1079 } 1080 1081 ushort[] new_buffer16(in size_t count, ref ubyte err) 1082 { 1083 ushort* p = cast(ushort*) _malloc(count * ushort.sizeof); 1084 if (!p) { 1085 err = ERROR.oom; 1086 return null; 1087 } 1088 return p[0..count]; 1089 } 1090 1091 struct NTString { 1092 const(char)* ptr; 1093 char[255] tmp; 1094 bool heap; 1095 1096 @nogc nothrow: 1097 1098 // Leaves ptr null on malloc error. 1099 this(in char[] s) 1100 { 1101 tmp[0..$] = 0; 1102 heap = false; 1103 if (!s.length) { 1104 ptr = cast(const(char*)) tmp.ptr; 1105 } else if (s[$-1] == 0) { 1106 ptr = s.ptr; 1107 } else if (s.length < tmp.length) { 1108 tmp[0 .. s.length] = s[0 .. $]; 1109 ptr = cast(const(char*)) tmp.ptr; 1110 } else { 1111 ptr = cast(char*) _malloc(s.length + 1); 1112 if (!ptr) 1113 return; 1114 heap = true; 1115 (cast(char*) ptr)[0..s.length] = s[0..$]; 1116 (cast(char*) ptr)[s.length] = 0; 1117 } 1118 } 1119 1120 void drop() { 1121 if (heap) 1122 _free(cast(void*) ptr); 1123 } 1124 } 1125 1126 unittest { 1127 string png_path = "tests/pngsuite/"; 1128 string tga_path = "tests/pngsuite-tga/"; 1129 string bmp_path = "tests/pngsuite-bmp/"; 1130 1131 static files = [ 1132 "basi0g08", // PNG image data, 32 x 32, 8-bit grayscale, interlaced 1133 "basi2c08", // PNG image data, 32 x 32, 8-bit/color RGB, interlaced 1134 "basi3p08", // PNG image data, 32 x 32, 8-bit colormap, interlaced 1135 "basi4a08", // PNG image data, 32 x 32, 8-bit gray+alpha, interlaced 1136 "basi6a08", // PNG image data, 32 x 32, 8-bit/color RGBA, interlaced 1137 "basn0g08", // PNG image data, 32 x 32, 8-bit grayscale, non-interlaced 1138 "basn2c08", // PNG image data, 32 x 32, 8-bit/color RGB, non-interlaced 1139 "basn3p08", // PNG image data, 32 x 32, 8-bit colormap, non-interlaced 1140 "basn4a08", // PNG image data, 32 x 32, 8-bit gray+alpha, non-interlaced 1141 "basn6a08", // PNG image data, 32 x 32, 8-bit/color RGBA, non-interlaced 1142 ]; 1143 1144 char[256] path; 1145 1146 static char[] buildpath(ref char[256] path, in char[] dir, in char[] file, in char[] ext) 1147 { 1148 path[0 .. dir.length] = dir[0..$]; 1149 path[dir.length .. dir.length + file.length] = file[0..$]; 1150 const size_t ei = dir.length + file.length; 1151 path[ei .. ei + ext.length] = ext[0..$]; 1152 return path[0 .. ei + ext.length]; 1153 } 1154 1155 foreach (file; files) { 1156 //writefln("%s", file); 1157 auto a = read_image(buildpath(path, png_path, file, ".png"), 4); 1158 auto b = read_image(buildpath(path, tga_path, file, ".tga"), 4); 1159 auto c = read_image(buildpath(path, bmp_path, file, ".bmp"), 4); 1160 scope(exit) { 1161 a.free(); 1162 b.free(); 1163 c.free(); 1164 } 1165 assert(a.e + b.e + c.e == 0); 1166 assert(a.w == b.w && a.w == c.w); 1167 assert(a.h == b.h && a.h == c.h); 1168 assert(a.buf8.length == b.buf8.length && a.buf8.length == c.buf8.length); 1169 assert(a.buf8[0..$] == b.buf8[0..$], "png/tga"); 1170 assert(a.buf8[0..$] == c.buf8[0..$], "png/bmp"); 1171 } 1172 }