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