1 module ppmformats; 2 3 private 4 { 5 import std.algorithm; 6 import std.conv; 7 import std.range; 8 import std.stdio; 9 import std..string; 10 import std.traits; 11 12 template addProperty(T, string propertyName, string defaultValue = T.init.to!string) 13 { 14 const char[] addProperty = format( 15 ` 16 private %2$s %1$s = %4$s; 17 18 void set%3$s(%2$s %1$s) 19 { 20 this.%1$s = %1$s; 21 } 22 23 %2$s get%3$s() 24 { 25 return %1$s; 26 } 27 `, 28 "_" ~ propertyName.toLower, 29 T.stringof, 30 propertyName, 31 defaultValue 32 ); 33 } 34 35 auto EnumValue(E)(E e) 36 if(is(E == enum)) 37 { 38 OriginalType!E tmp = e; 39 return tmp; 40 } 41 42 mixin template addConstructor(alias pmf) 43 { 44 this(size_t width = 0, size_t height = 0, RGBColor color = new RGBColor(0, 0, 0)) 45 { 46 _image = new PixMapImage(width, height, color); 47 _header = pmf; 48 } 49 50 alias image this; 51 } 52 } 53 54 class RGBColor 55 { 56 mixin(addProperty!(int, "R")); 57 mixin(addProperty!(int, "G")); 58 mixin(addProperty!(int, "B")); 59 60 this(int R = 0, int G = 0, int B = 0) 61 { 62 this._r = R; 63 this._g = G; 64 this._b = B; 65 } 66 67 const float luminance709() 68 { 69 return (_r * 0.2126f + _g * 0.7152f + _b * 0.0722f); 70 } 71 72 const float luminance601() 73 { 74 return (_r * 0.3f + _g * 0.59f + _b * 0.11f); 75 } 76 77 const float luminanceAverage() 78 { 79 return (_r + _g + _b) / 3.0; 80 } 81 82 alias luminance = luminance709; 83 84 override string toString() 85 { 86 return format("RGBColor(%d, %d, %d, I = %f)", _r, _g, _b, this.luminance); 87 } 88 89 RGBColor opBinary(string op, T)(auto ref T rhs) 90 { 91 return mixin( 92 format(`new RGBColor( 93 clamp(cast(int) (_r %1$s rhs), 0, 255), 94 clamp(cast(int) (_g %1$s rhs), 0, 255), 95 clamp(cast(int) (_b %1$s rhs), 0, 255) 96 ) 97 `, 98 op 99 ) 100 ); 101 } 102 103 RGBColor opBinary(string op)(RGBColor rhs) 104 { 105 return mixin( 106 format(`new RGBColor( 107 clamp(cast(int) (_r %1$s rhs.getR), 0, 255), 108 clamp(cast(int) (_g %1$s rhs.getG), 0, 255), 109 clamp(cast(int) (_b %1$s rhs.getB), 0, 255) 110 ) 111 `, 112 op 113 ) 114 ); 115 } 116 } 117 118 class PixMapImage 119 { 120 mixin(addProperty!(size_t, "Width")); 121 mixin(addProperty!(size_t, "Height")); 122 123 private 124 { 125 RGBColor[] _image; 126 127 auto actualIndex(size_t i) 128 { 129 auto S = _width * _height; 130 131 return clamp(i, 0, S - 1); 132 } 133 134 auto actualIndex(size_t i, size_t j) 135 { 136 auto W = cast(size_t) clamp(i, 0, _width - 1); 137 auto H = cast(size_t) clamp(j, 0, _height - 1); 138 auto S = _width * _height; 139 140 return clamp(W + H * _width, 0, S); 141 } 142 } 143 144 this(size_t width = 0, size_t height = 0, RGBColor color = new RGBColor(0, 0, 0)) 145 { 146 this._width = width; 147 this._height = height; 148 149 foreach (x; 0.._width) 150 { 151 foreach (y; 0.._height) 152 { 153 _image ~= color; 154 } 155 } 156 } 157 158 RGBColor opIndexAssign(RGBColor color, size_t x, size_t y) 159 { 160 _image[actualIndex(x, y)] = color; 161 return color; 162 } 163 164 RGBColor opIndexAssign(RGBColor color, size_t x) 165 { 166 _image[actualIndex(x)] = color; 167 return color; 168 } 169 170 RGBColor opIndex(size_t x, size_t y) 171 { 172 return _image[actualIndex(x, y)]; 173 } 174 175 RGBColor opIndex(size_t x) 176 { 177 return _image[actualIndex(x)]; 178 } 179 180 override string toString() 181 { 182 string accumulator = "["; 183 184 foreach (x; 0.._width) 185 { 186 string tmp = "["; 187 foreach (y; 0.._height) 188 { 189 tmp ~= _image[actualIndex(x, y)].toString ~ ", "; 190 } 191 tmp = tmp[0..$-2] ~ "], "; 192 accumulator ~= tmp; 193 } 194 return accumulator[0..$-2] ~ "]"; 195 } 196 197 alias width = getWidth; 198 alias height = getHeight; 199 200 final RGBColor[] array() 201 { 202 return _image; 203 } 204 205 final void array(RGBColor[] image) 206 { 207 if (image.length == _image.length) 208 { 209 this._image = image; 210 } 211 else 212 { 213 throw new Exception("Lengths must be the same"); 214 } 215 } 216 217 final void changeCapacity(size_t x, size_t y) 218 { 219 long newLength = (x * y); 220 221 if (newLength > _image.length) 222 { 223 auto restLength = cast(long) newLength - _image.length; 224 _image.length += cast(size_t) restLength; 225 } 226 else 227 { 228 if (newLength < _image.length) 229 { 230 auto restLength = cast(long) _image.length - newLength; 231 _image.length -= cast(size_t) restLength; 232 } 233 } 234 _width = x; 235 _height = y; 236 } 237 } 238 239 240 enum PixMapFormat : string 241 { 242 PBM_TEXT = "P1", 243 PBM_BINARY = "P4", 244 PGM_TEXT = "P2", 245 PGM_BINARY = "P5", 246 PPM_TEXT = "P3", 247 PPM_BINARY = "P6", 248 } 249 250 class PixMapFile 251 { 252 protected 253 { 254 File _file; 255 PixMapFormat _header; 256 PixMapImage _image; 257 258 abstract void loader(); 259 abstract void saver(); 260 } 261 262 private 263 { 264 // set i/o mode (actual for windows system) 265 auto IOMode(string mode) 266 { 267 268 if (isBinaryFormat) 269 { 270 return mode ~ `b`; 271 } 272 else 273 { 274 return mode; 275 } 276 } 277 } 278 279 void load(string filename) 280 { 281 with (_file) 282 { 283 open(filename, IOMode(`r`)); 284 285 if (readln.strip == EnumValue(_header)) 286 { 287 auto imageSize = readln.split; 288 auto width = imageSize[0].parse!int; 289 auto height = imageSize[1].parse!int; 290 291 _image = new PixMapImage(width, height); 292 293 loader; 294 } 295 } 296 } 297 298 void save(string filename) 299 { 300 with (_file) 301 { 302 open(filename, IOMode("w")); 303 writeln(EnumValue(_header)); 304 writeln(_image.width, " ", _image.height); 305 306 saver; 307 } 308 } 309 310 // is raw format ? 311 final bool isBinaryFormat() 312 { 313 return 314 ( 315 (_header == PixMapFormat.PBM_BINARY) | 316 (_header == PixMapFormat.PGM_BINARY) | 317 (_header == PixMapFormat.PPM_BINARY) 318 ); 319 } 320 321 // is text format ? 322 final bool isTextFormat() 323 { 324 return 325 ( 326 (_header == PixMapFormat.PBM_TEXT) | 327 (_header == PixMapFormat.PGM_TEXT) | 328 (_header == PixMapFormat.PPM_TEXT) 329 ); 330 } 331 332 // get image 333 final PixMapImage image() 334 { 335 return _image; 336 } 337 338 // set image 339 final void image(PixMapImage image) 340 { 341 this._image = image; 342 } 343 344 alias image this; 345 } 346 347 348 class P6Image : PixMapFile 349 { 350 mixin(addProperty!(int, "Intensity", "255")); 351 mixin addConstructor!(PixMapFormat.PPM_BINARY); 352 353 override void loader() 354 { 355 auto data = _file.readln; 356 _intensity = data.parse!int; 357 358 auto buffer = new ubyte[width * 3]; 359 360 for (uint i = 0; i < height; i++) 361 { 362 _file.rawRead!ubyte(buffer); 363 364 for (uint j = 0; j < width; j++) 365 { 366 auto R = buffer[j * 3]; 367 auto G = buffer[j * 3 + 1]; 368 auto B = buffer[j * 3 + 2]; 369 _image[j, i] = new RGBColor( 370 (R > _intensity) ? _intensity : R, 371 (G > _intensity) ? _intensity : G, 372 (B > _intensity) ? _intensity : B 373 ); 374 } 375 } 376 } 377 378 override void saver() 379 { 380 _file.writeln(_intensity); 381 382 foreach (e; _image.array) 383 { 384 auto R = e.getR; 385 auto G = e.getG; 386 auto B = e.getB; 387 388 auto rr = (R > _intensity) ? _intensity : R; 389 auto gg = (G > _intensity) ? _intensity : G; 390 auto bb = (B > _intensity) ? _intensity : B; 391 392 _file.write( 393 cast(char) rr, 394 cast(char) gg, 395 cast(char) bb 396 ); 397 } 398 } 399 } 400 401 402 class P3Image : PixMapFile 403 { 404 mixin(addProperty!(int, "Intensity", "255")); 405 mixin addConstructor!(PixMapFormat.PPM_TEXT); 406 407 override void loader() 408 { 409 // skip maximal intensity description 410 auto data = _file.readln; 411 _intensity = data.parse!int; 412 413 string triplet; 414 int index = 0; 415 416 while ((triplet = _file.readln) !is null) 417 { 418 auto rgb = triplet.split; 419 auto R = rgb[0].parse!int; 420 auto G = rgb[1].parse!int; 421 auto B = rgb[2].parse!int; 422 423 _image[index] = new RGBColor( 424 (R > _intensity) ? _intensity : R, 425 (G > _intensity) ? _intensity : G, 426 (B > _intensity) ? _intensity : B 427 ); 428 index++; 429 } 430 } 431 432 override void saver() 433 { 434 _file.writeln(_intensity); 435 436 foreach (e; _image.array) 437 { 438 auto R = e.getR; 439 auto G = e.getG; 440 auto B = e.getB; 441 442 _file.writefln( 443 "%d %d %d", 444 (R > _intensity) ? _intensity : R, 445 (G > _intensity) ? _intensity : G, 446 (B > _intensity) ? _intensity : B 447 ); 448 } 449 } 450 } 451 452 453 class P1Image : PixMapFile 454 { 455 mixin addConstructor!(PixMapFormat.PBM_TEXT); 456 457 override void loader() 458 { 459 string line; 460 int index; 461 462 auto WHITE = new RGBColor(255, 255, 255); 463 auto BLACK = new RGBColor(0, 0, 0); 464 465 while ((line = _file.readln) !is null) 466 { 467 auto row = line.replace(" ", "").replace("\n", ""); 468 469 foreach (i, e; row) 470 { 471 _image[i, index] = (e.to!string == "0") ? WHITE : BLACK; 472 } 473 index++; 474 } 475 } 476 477 override void saver() 478 { 479 foreach (rows; _image.array.chunks(width)) 480 { 481 _file.writeln( 482 rows 483 .map!(a => (a.luminance < 255) ? "1" : "0") 484 .join(" ") 485 ); 486 } 487 } 488 } 489 490 491 class P2Image : PixMapFile 492 { 493 mixin(addProperty!(int, "Intensity", "255")); 494 mixin addConstructor!(PixMapFormat.PGM_TEXT); 495 496 override void loader() 497 { 498 // skip maximal intensity description 499 auto data = _file.readln; 500 _intensity = data.parse!int; 501 502 string line; 503 int index; 504 505 while ((line = _file.readln) !is null) 506 { 507 auto row = line.split; 508 509 foreach (i, e; row) 510 { 511 auto l = e.parse!int; 512 auto I = (l > _intensity) ? _intensity : l; 513 _image[i, index] = new RGBColor(I, I, I); 514 } 515 index++; 516 } 517 } 518 519 override void saver() 520 { 521 _file.writeln(_intensity); 522 523 foreach (rows; _image.array.chunks(width)) 524 { 525 auto toIntensity(RGBColor color) 526 { 527 int I; 528 if ((color.getR == color.getG) && (color.getG == color.getB) && (color.getR == color.getB)) 529 { 530 I = color.getR; 531 } 532 else 533 { 534 I = color.luminance601.to!int; 535 } 536 return (I > _intensity) ? _intensity : I; 537 } 538 539 _file.writeln( 540 rows 541 .map!(a => toIntensity(a).to!string) 542 .join(" ") 543 ); 544 } 545 } 546 } 547 548 class P5Image : PixMapFile 549 { 550 mixin(addProperty!(int, "Intensity", "255")); 551 mixin addConstructor!(PixMapFormat.PGM_BINARY); 552 553 override void loader() 554 { 555 // skip maximal intensity description 556 auto data = _file.readln; 557 _intensity = data.to!int; 558 559 auto buffer = new ubyte[width * height]; 560 _file.rawRead!ubyte(buffer); 561 562 foreach (i, e; buffer) 563 { 564 auto I = (e > _intensity) ? _intensity : e; 565 _image[i] = new RGBColor(I, I, I); 566 } 567 } 568 569 override void saver() 570 { 571 _file.writeln(_intensity); 572 573 foreach (e; _image.array) 574 { 575 ubyte I; 576 if ((e.getR == e.getG) && (e.getG == e.getB) && (e.getR == e.getB)) 577 { 578 I = e.getR.to!ubyte; 579 } 580 else 581 { 582 I = e.luminance601.to!ubyte; 583 } 584 585 _file.write( 586 cast(char) I 587 ); 588 } 589 } 590 } 591 592 593 class P4Image : PixMapFile 594 { 595 mixin addConstructor!(PixMapFormat.PBM_BINARY); 596 597 auto setBit(int value, int n) 598 { 599 return (value | (1 << n)); 600 } 601 602 auto getBit(int value, int n) 603 { 604 return ((value >> n) & 1); 605 } 606 607 auto clearBit(int value, int n) 608 { 609 return (value & ~(1 << n)); 610 } 611 612 auto BLACK = new RGBColor(0, 0, 0); 613 auto WHITE = new RGBColor(255, 255, 255); 614 615 override void loader() 616 { 617 auto imageSize = width * height; 618 auto buffer = new ubyte[imageSize]; 619 _file.rawRead!ubyte(buffer); 620 621 int index; 622 623 foreach (e; buffer) 624 { 625 if (index < imageSize) 626 { 627 foreach (i; 0..8) 628 { 629 auto I = getBit(cast(int) e, 7 - i); 630 _image[index] = I ? BLACK : WHITE; 631 index++; 632 } 633 } 634 else 635 { 636 break; 637 } 638 } 639 } 640 641 override void saver() 642 { 643 foreach (e; _image.array.chunks(width)) 644 { 645 foreach (r; e.chunks(8)) 646 { 647 auto bits = 0x00; 648 649 foreach (i, b; r) 650 { 651 auto I = (b.luminance == 0) ? 1 : 0; 652 653 if (I == 1) 654 { 655 bits = setBit(bits, cast(int) (7 - i)); 656 } 657 } 658 _file.write( 659 cast(char) bits 660 ); 661 } 662 } 663 } 664 } 665 666 667 PixMapFile image(size_t width = 0, size_t height = 0, PixMapFormat pmFormat = PixMapFormat.PPM_BINARY) 668 { 669 PixMapFile pixmap; 670 671 final switch (pmFormat) with (PixMapFormat) 672 { 673 case PBM_TEXT: 674 pixmap = new P1Image(width, height); 675 break; 676 case PBM_BINARY: 677 pixmap = new P4Image(width, height); 678 break; 679 case PGM_TEXT: 680 pixmap = new P2Image(width, height); 681 break; 682 case PGM_BINARY: 683 pixmap = new P5Image(width, height); 684 break; 685 case PPM_TEXT: 686 pixmap = new P3Image(width, height); 687 break; 688 case PPM_BINARY: 689 pixmap = new P6Image(width, height); 690 break; 691 } 692 693 return pixmap; 694 } 695 696 PixMapFile image(size_t width = 0, size_t height = 0, string pmFormat = "P6") 697 { 698 PixMapFile pixmap; 699 700 switch (pmFormat) 701 { 702 case "P1": 703 pixmap = new P1Image(width, height); 704 break; 705 case "P4": 706 pixmap = new P4Image(width, height); 707 break; 708 case "P2": 709 pixmap = new P2Image(width, height); 710 break; 711 case "P5": 712 pixmap = new P5Image(width, height); 713 break; 714 case "P3": 715 pixmap = new P3Image(width, height); 716 break; 717 case "P6": 718 pixmap = new P6Image(width, height); 719 break; 720 default: 721 assert(0); 722 } 723 724 return pixmap; 725 }