1 // Written in the D programming language. 2 3 /** 4 Minimalistic library for working with Netpbm image formats. Currently, work with formats from P1 to P6 is supported. 5 An overview of the internal structure of the library, its interfaces and applications is available at the link (in Russian): https://lhs-blog.info/programming/dlang/ppmformats-library/ 6 7 Copyright: LightHouse Software, 2020 - 2022 8 Authors: Oleg Bakharev, 9 Ilya Pertsev 10 */ 11 module ppmformats; 12 13 private 14 { 15 import std.algorithm; 16 import std.conv; 17 import std.range; 18 import std.stdio; 19 import std.string; 20 import std.traits; 21 22 /** 23 A handy template for creating setters and getters for properties. For internal use only. 24 See_Also: 25 https://lhs-blog.info/programming/dlang/udobnoe-sozdanie-svoystv-v-klassah-i-strukturah/ (in Russian) 26 */ 27 template addProperty(T, string propertyName, string defaultValue = T.init.to!string) 28 { 29 const char[] addProperty = format( 30 ` 31 private %2$s %1$s = %4$s; 32 33 void set%3$s(%2$s %1$s) 34 { 35 this.%1$s = %1$s; 36 } 37 38 %2$s get%3$s() 39 { 40 return %1$s; 41 } 42 `, 43 "_" ~ propertyName.toLower, 44 T.stringof, 45 propertyName, 46 defaultValue 47 ); 48 } 49 50 /// Extract type value from enum. For internal use. 51 auto EnumValue(E)(E e) 52 if(is(E == enum)) 53 { 54 OriginalType!E tmp = e; 55 return tmp; 56 } 57 58 /// Add basic constructor for all image filetypes. For internal use. 59 mixin template addConstructor(alias pmf) 60 { 61 this(size_t width = 0, size_t height = 0, RGBColor color = new RGBColor(0, 0, 0)) 62 { 63 _image = new PixMapImage(width, height, color); 64 _header = pmf; 65 } 66 67 alias image this; 68 } 69 } 70 71 /** 72 Class for representing color in RGB format. 73 */ 74 class RGBColor 75 { 76 mixin(addProperty!(int, "R")); 77 mixin(addProperty!(int, "G")); 78 mixin(addProperty!(int, "B")); 79 80 81 /** 82 Constructor for creating colors in RGB format. 83 If called without parameters, then all three values of the channels R, G and B take the value 0, which corresponds to black. 84 Params: 85 R = 32-bit value for red channel. The value ranges from 0 (minimum value) to 255 (maximum value). 86 G = 32-bit value for green channel. The value ranges from 0 (minimum value) to 255 (maximum value). 87 B = 32-bit value for blue channel. The value ranges from 0 (minimum value) to 255 (maximum value). 88 89 Typical usage: 90 ---- 91 // Black color 92 RGBColor color = new RGBColor; 93 94 // Red color 95 RGBColor color = new RGBColor(255, 0, 0); 96 97 // White color 98 RGBColor color = new RGBColor(255, 255, 255); 99 ---- 100 */ 101 this(int R = 0, int G = 0, int B = 0) 102 { 103 this._r = R; 104 this._g = G; 105 this._b = B; 106 } 107 108 /** 109 Luminance according to ITU 709 standard. 110 Returns: 111 Luminance for a specific color as a floating point value. 112 113 Typical usage: 114 ---- 115 import std.stdio : writeln; 116 117 // Red color 118 RGBColor color = new RGBColor(255, 0, 0); 119 color.luminance709.writeln; 120 ---- 121 */ 122 const float luminance709() 123 { 124 return (_r * 0.2126f + _g * 0.7152f + _b * 0.0722f); 125 } 126 127 /** 128 Luminance according to ITU 601 standard. 129 Returns: 130 Luminance for a specific color as a floating point value. 131 132 Typical usage: 133 ---- 134 import std.stdio : writeln; 135 136 // Red color 137 RGBColor color = new RGBColor(255, 0, 0); 138 color.luminance601.writeln; 139 ---- 140 */ 141 const float luminance601() 142 { 143 return (_r * 0.3f + _g * 0.59f + _b * 0.11f); 144 } 145 146 /** 147 Average luminance. 148 Returns: 149 Luminance for a specific color as a floating point value. 150 151 Typical usage: 152 ---- 153 import std.stdio : writeln; 154 155 // Red color 156 RGBColor color = new RGBColor(255, 0, 0); 157 color.luminanceAverage.writeln; 158 ---- 159 */ 160 const float luminanceAverage() 161 { 162 return (_r + _g + _b) / 3.0; 163 } 164 165 /// Alias for standard (default) luminance calculation. Value is the same as luminance709. 166 alias luminance = luminance709; 167 168 /** 169 A string representation of a color. 170 The color is output as a string in the following format: RGBColor(R=<value>, G=<value>, B=<value>, I=<value>), where R,G,B are the values of the three color channels, and I is color brightness according to ITU 709. 171 Returns: 172 String color representation. 173 174 Typical usage: 175 ---- 176 import std.stdio : writeln; 177 178 // Red color 179 RGBColor color = new RGBColor(255, 0, 0); 180 color.writeln; 181 ---- 182 */ 183 override string toString() 184 { 185 return format("RGBColor(%d, %d, %d, I = %f)", _r, _g, _b, this.luminance); 186 } 187 188 /** 189 Basic arithmetic for color operations. The value on the right can be a value of any numeric type. 190 191 Typical usage: 192 ---- 193 // Red color 194 RGBColor color = new RGBColor(255, 0, 0); 195 196 // Add two for all channels in color 197 auto newColor = color + 2; 198 // Divide all channels by two 199 color = color / 2; 200 ---- 201 */ 202 RGBColor opBinary(string op, T)(auto ref T rhs) 203 { 204 return mixin( 205 format(`new RGBColor( 206 clamp(cast(int) (_r %1$s rhs), 0, 255), 207 clamp(cast(int) (_g %1$s rhs), 0, 255), 208 clamp(cast(int) (_b %1$s rhs), 0, 255) 209 ) 210 `, 211 op 212 ) 213 ); 214 } 215 216 /** 217 Basic arithmetic for color operations. Only the RGBColor type can be used as the value on the right. 218 219 Typical usage: 220 ---- 221 // Red color 222 RGBColor color = new RGBColor(255, 0, 0); 223 // Blue color 224 RGBColor color2 = new RGBColor(0, 0, 255); 225 226 // mix two colors 227 auto mix = color + color2; 228 // difference between color 229 auto diff = color - color2; 230 ---- 231 */ 232 RGBColor opBinary(string op)(RGBColor rhs) 233 { 234 return mixin( 235 format(`new RGBColor( 236 clamp(cast(int) (_r %1$s rhs.getR), 0, 255), 237 clamp(cast(int) (_g %1$s rhs.getG), 0, 255), 238 clamp(cast(int) (_b %1$s rhs.getB), 0, 255) 239 ) 240 `, 241 op 242 ) 243 ); 244 } 245 } 246 247 /** 248 A class that provides a convenient interface for working with images. Represents a one-dimensional array. 249 */ 250 class PixMapImage 251 { 252 mixin(addProperty!(size_t, "Width")); 253 mixin(addProperty!(size_t, "Height")); 254 255 private 256 { 257 RGBColor[] _image; 258 259 /** 260 Calculation of the real index in the internal one-dimensional array storing pixels. 261 The real length of the internal array is taken into account, therefore it is allowed to specify an index value greater than the actual length of the array. 262 263 Internal use only for implementing class object accessor methods through indexing operators. 264 */ 265 auto actualIndex(size_t i) 266 { 267 auto S = _width * _height; 268 269 return clamp(i, 0, S - 1); 270 } 271 272 /** 273 Calculation of the real index in a one-dimensional array through two indexes. 274 Thus, the possibility of referring to the internal array as a two-dimensional one is realized. 275 As in the previous method, the binding to the actual length of the internal array is taken into account, so both indexes can be greater than the actual values of the length and width of the image. 276 277 Internal use only for implementing class object accessor methods through indexing operators. 278 */ 279 auto actualIndex(size_t i, size_t j) 280 { 281 auto W = cast(size_t) clamp(i, 0, _width - 1); 282 auto H = cast(size_t) clamp(j, 0, _height - 1); 283 auto S = _width * _height; 284 285 return clamp(W + H * _width, 0, S); 286 } 287 } 288 289 /** 290 A constructor for creating an image with given dimensions (length and width) and starting color for pixels. 291 By default, all values are zero, and black (i.e: RGBColor(0, 0, 0)) is used as the starting color. 292 Params: 293 width = Width of image as size_t value. 294 height = Height of image as size_t value. 295 color = Initial color for pixels in image. 296 297 Typical usage: 298 ---- 299 // creating of empty image 300 PixMapImage pmi = new PixMapImage; 301 302 // creating image of size 20x20, all pixels are black 303 PixMapImage pmi2 = new PixMapImage(20, 20); 304 305 // creating image of size 20x20, all pixels are red 306 PixMapImage pmi3 = new PixMapImage(20, 20, new RGBColor(255, 0, 255)); 307 ---- 308 */ 309 this(size_t width = 0, size_t height = 0, RGBColor color = new RGBColor(0, 0, 0)) 310 { 311 this._width = width; 312 this._height = height; 313 314 foreach (x; 0.._width) 315 { 316 foreach (y; 0.._height) 317 { 318 _image ~= color; 319 } 320 } 321 } 322 323 /** 324 Assigning a color value to an individual pixel through a two-index indexing operation. 325 Note: It is allowed to use as indices values greater than the length and width (or less than 0) as indices, since the values will be converted to the actual length of the image array. 326 327 Typical usage: 328 ---- 329 // creating image of size 20x20, all pixels are black 330 auto pmi = new PixMapImage(20, 20); 331 // pixel at coords (5;5) now are green 332 pmi[5, 5] = new RGBColor(0, 255, 0); 333 ---- 334 */ 335 RGBColor opIndexAssign(RGBColor color, size_t x, size_t y) 336 { 337 _image[actualIndex(x, y)] = color; 338 return color; 339 } 340 341 /** 342 Assigning a color value to an individual pixel through a one-index indexing operation. 343 Note: It is allowed to use an index greater than the actual length of the array or less than 0, since it is bound to the real length of the internal array of the image. 344 345 Typical usage: 346 ---- 347 // creating image of size 20x20, all pixels are black 348 auto pmi = new PixMapImage(20, 20); 349 // 6th pixel now are green 350 pmi[5] = new RGBColor(0, 255, 0); 351 ---- 352 */ 353 RGBColor opIndexAssign(RGBColor color, size_t x) 354 { 355 _image[actualIndex(x)] = color; 356 return color; 357 } 358 359 /** 360 Getting a color value from an individual pixel through a two-index indexing operation. 361 Note: It is allowed to use as indices values greater than the length and width (or less than 0) as indices, since the values will be converted to the actual length of the image array. 362 363 Typical usage: 364 ---- 365 // creating image of size 20x20, all pixels are black 366 auto pmi = new PixMapImage(20, 20); 367 // get pixel color at coords (5;5) 368 pmi[5, 5].writeln; 369 ---- 370 */ 371 RGBColor opIndex(size_t x, size_t y) 372 { 373 return _image[actualIndex(x, y)]; 374 } 375 376 /** 377 Assigning a color value to an individual pixel through a one-index indexing operation. 378 Note: It is allowed to use an index greater than the actual length of the array or less than 0, since it is bound to the real length of the internal array of the image. 379 380 Typical usage: 381 ---- 382 // creating image of size 20x20, all pixels are black 383 auto pmi = new PixMapImage(20, 20); 384 // getting color of 6th pixel 385 pmi[5].writeln; 386 ---- 387 */ 388 RGBColor opIndex(size_t x) 389 { 390 return _image[actualIndex(x)]; 391 } 392 393 /** 394 The string representation of the image. Returns a string representing the image as a two-dimensional array of RGBColor objects. 395 */ 396 override string toString() 397 { 398 string accumulator = "["; 399 400 foreach (x; 0.._width) 401 { 402 string tmp = "["; 403 foreach (y; 0.._height) 404 { 405 tmp ~= _image[actualIndex(x, y)].toString ~ ", "; 406 } 407 tmp = tmp[0..$-2] ~ "], "; 408 accumulator ~= tmp; 409 } 410 return accumulator[0..$-2] ~ "]"; 411 } 412 413 /// Returns actual width of image as size_t value 414 alias width = getWidth; 415 /// Returns actual height of image as size_t value 416 alias height = getHeight; 417 418 /** 419 Returns the entire internal one-dimensional pixel array of the image. 420 Returns: 421 One-dimensional array of RGBColor objects. 422 423 Typical usage: 424 ---- 425 PixMapImage pmi = new PixMapFile(10, 10); 426 // get all pixels 427 RGBColor[] pixels = pmi.array; 428 ---- 429 */ 430 final RGBColor[] array() 431 { 432 return _image; 433 } 434 435 /** 436 Sets the inner pixel array of the image by feeding the outer array. 437 The size of the array must be equal to the actual size of the image (i.e. the size of the given one-dimensional array must be equal to the product of the length of the image and its width) 438 Throws: 439 Exception if the length of the supplied array does not match the actual length of the internal array, as above. 440 441 Typical usage: 442 ---- 443 PixMapImage pmi = new PixMapFile(2); 444 RGBColor[] pixels = [new RGBColor(255, 255, 255), new RGBColor(255, 255, 255)]; 445 // set all pixels as white 446 pmi.array(pixels); 447 ---- 448 */ 449 final void array(RGBColor[] image) 450 { 451 if (image.length == _image.length) 452 { 453 this._image = image; 454 } 455 else 456 { 457 throw new Exception("Lengths must be the same"); 458 } 459 } 460 461 /** 462 Resizing an image according to its given length and width. 463 Note: 464 If the length and/or width are smaller than the original values, then a literal cropping to the desired dimensions will be performed (not interpolation or approximation, but real cropping!). 465 If the size parameters are larger than the original ones, then the image size will be increased by adding the default color to the end of the image (real array addition will be performed, not interpolation). 466 467 WARNING: 468 The method is highly controversial and experimental. We strongly discourage its use in real projects. 469 */ 470 final void changeCapacity(size_t x, size_t y) 471 { 472 long newLength = (x * y); 473 474 if (newLength > _image.length) 475 { 476 auto restLength = cast(long) newLength - _image.length; 477 _image.length += cast(size_t) restLength; 478 } 479 else 480 { 481 if (newLength < _image.length) 482 { 483 auto restLength = cast(long) _image.length - newLength; 484 _image.length -= cast(size_t) restLength; 485 } 486 } 487 _width = x; 488 _height = y; 489 } 490 } 491 492 /** 493 All possible types of Portable Anymap Image formats in the form of a convenient division into binary and text image formats. 494 */ 495 enum PixMapFormat : string 496 { 497 PBM_TEXT = "P1", 498 PBM_BINARY = "P4", 499 PGM_TEXT = "P2", 500 PGM_BINARY = "P5", 501 PPM_TEXT = "P3", 502 PPM_BINARY = "P6", 503 } 504 505 /** 506 Common ancestor for all subsequent image types. 507 Implements a generic way to load/save images by providing generic load/save methods. 508 Also, inheritance from this class allows descendant classes to have methods for working with images: indexing, assigning values to pixels and accessing them without the need to create an object of the PixMapImage class to manipulate images. 509 510 Implementation Note: The specific loading method is already implemented by descendant classes by overriding the abstract loader/saver methods in their implementations. 511 */ 512 class PixMapFile 513 { 514 mixin(addProperty!(PixMapImage, "Image")); 515 516 protected 517 { 518 File _file; 519 PixMapFormat _header; 520 521 abstract void loader(); 522 abstract void saver(); 523 } 524 525 private 526 { 527 /// Set i/o mode for reading/writing Portable Anymap Images. Actual for OS Windows. For internal use. 528 auto IOMode(string mode) 529 { 530 531 if (isBinaryFormat) 532 { 533 return mode ~ `b`; 534 } 535 else 536 { 537 return mode; 538 } 539 } 540 } 541 542 /// Basic file loading procedure 543 void load(string filename) 544 { 545 with (_file) 546 { 547 open(filename, IOMode(`r`)); 548 549 if (readln.strip == EnumValue(_header)) 550 { 551 auto imageSize = readln.split; 552 auto width = imageSize[0].parse!int; 553 auto height = imageSize[1].parse!int; 554 555 _image = new PixMapImage(width, height); 556 557 loader; 558 } 559 } 560 } 561 562 /// Basic file saving procedure 563 void save(string filename) 564 { 565 with (_file) 566 { 567 open(filename, IOMode("w")); 568 writeln(EnumValue(_header)); 569 writeln(_image.width, " ", _image.height); 570 571 saver; 572 } 573 } 574 575 /// Is raw format ? 576 final bool isBinaryFormat() 577 { 578 return 579 ( 580 (_header == PixMapFormat.PBM_BINARY) | 581 (_header == PixMapFormat.PGM_BINARY) | 582 (_header == PixMapFormat.PPM_BINARY) 583 ); 584 } 585 586 /// Is text format ? 587 final bool isTextFormat() 588 { 589 return 590 ( 591 (_header == PixMapFormat.PBM_TEXT) | 592 (_header == PixMapFormat.PGM_TEXT) | 593 (_header == PixMapFormat.PPM_TEXT) 594 ); 595 } 596 597 /// Get image object as PixMapImage object 598 final PixMapImage image() 599 { 600 return this.getImage; 601 } 602 603 /// Set image object as PixMapImage object 604 final void image(PixMapImage image) 605 { 606 this.setImage(image); 607 } 608 609 /// Convenient alias for working with PixMapFile same as PixMapImage 610 alias image this; 611 } 612 613 /** 614 A class that provides the ability to work with color images in P6 format. 615 NB: The format is raw binary. 616 617 Note: 618 This class supports indexing and assigning values to specific pixels via 1D or 2D indexing, and provides P6 file loading/saving capabilities. 619 According to the accepted convention, in the original description of the format inside the Netpbm package, the extension of these files should be `*.ppm`. 620 621 Typical usage: 622 ---- 623 // creating of empty image 624 auto img = new P6Image; 625 // load image from file `Lenna.ppm` 626 img.load(`Lenna.ppm`); 627 // change pixel at coords (10; 10), now are white 628 img[10, 10] = new RGBColor(255, 255, 255); 629 // get color of 11th pixel 630 img[10].writeln; 631 // save file as `Lenna2.ppm` 632 img.save(`Lenna2.ppm`); 633 634 // creating image of 10x10, all pixels are red 635 auto img2 = new P6Image(10, 10, new RGBColor(255, 0, 255)); 636 // increasing luminance by two 637 img2[10] = img2[10] * 2; 638 // save as `test.ppm` 639 img2.save(`test.ppm`); 640 ---- 641 */ 642 class P6Image : PixMapFile 643 { 644 mixin(addProperty!(int, "Intensity", "255")); 645 mixin addConstructor!(PixMapFormat.PPM_BINARY); 646 647 override void loader() 648 { 649 auto data = _file.readln; 650 _intensity = data.parse!int; 651 652 auto buffer = new ubyte[width * 3]; 653 654 for (uint i = 0; i < height; i++) 655 { 656 _file.rawRead!ubyte(buffer); 657 658 for (uint j = 0; j < width; j++) 659 { 660 auto R = buffer[j * 3]; 661 auto G = buffer[j * 3 + 1]; 662 auto B = buffer[j * 3 + 2]; 663 _image[j, i] = new RGBColor( 664 (R > _intensity) ? _intensity : R, 665 (G > _intensity) ? _intensity : G, 666 (B > _intensity) ? _intensity : B 667 ); 668 } 669 } 670 } 671 672 override void saver() 673 { 674 _file.writeln(_intensity); 675 676 foreach (e; _image.array) 677 { 678 auto R = e.getR; 679 auto G = e.getG; 680 auto B = e.getB; 681 682 auto rr = (R > _intensity) ? _intensity : R; 683 auto gg = (G > _intensity) ? _intensity : G; 684 auto bb = (B > _intensity) ? _intensity : B; 685 686 _file.write( 687 cast(char) rr, 688 cast(char) gg, 689 cast(char) bb 690 ); 691 } 692 } 693 } 694 695 /** 696 A class that provides the ability to work with color images in P3 format. 697 NB: The format is raw text. 698 699 Note: 700 This class supports indexing and assigning values to specific pixels via 1D or 2D indexing, and provides P3 file loading/saving capabilities. 701 According to the accepted convention, in the original description of the format inside the Netpbm package, the extension of these files should be `*.ppm`. 702 703 Typical usage: 704 ---- 705 // creating of empty image 706 auto img = new P3Image; 707 // load image from file `Lenna.ppm` 708 img.load(`Lenna.ppm`); 709 // change pixel at coords (10; 10), now are white 710 img[10, 10] = new RGBColor(255, 255, 255); 711 // get color of 11th pixel 712 img[10].writeln; 713 // save file as `Lenna2.ppm` 714 img.save(`Lenna2.ppm`); 715 716 // creating image of 10x10, all pixels are red 717 auto img2 = new P3Image(10, 10, new RGBColor(255, 0, 255)); 718 // increasing luminance by two 719 img2[10] = img2[10] * 2; 720 // save as `test.ppm` 721 img2.save(`test.ppm`); 722 ---- 723 */ 724 class P3Image : PixMapFile 725 { 726 mixin(addProperty!(int, "Intensity", "255")); 727 mixin addConstructor!(PixMapFormat.PPM_TEXT); 728 729 override void loader() 730 { 731 // skip maximal intensity description 732 auto data = _file.readln; 733 _intensity = data.parse!int; 734 735 string triplet; 736 int index = 0; 737 738 while ((triplet = _file.readln) !is null) 739 { 740 auto rgb = triplet.split; 741 auto R = rgb[0].parse!int; 742 auto G = rgb[1].parse!int; 743 auto B = rgb[2].parse!int; 744 745 _image[index] = new RGBColor( 746 (R > _intensity) ? _intensity : R, 747 (G > _intensity) ? _intensity : G, 748 (B > _intensity) ? _intensity : B 749 ); 750 index++; 751 } 752 } 753 754 override void saver() 755 { 756 _file.writeln(_intensity); 757 758 foreach (e; _image.array) 759 { 760 auto R = e.getR; 761 auto G = e.getG; 762 auto B = e.getB; 763 764 _file.writefln( 765 "%d %d %d", 766 (R > _intensity) ? _intensity : R, 767 (G > _intensity) ? _intensity : G, 768 (B > _intensity) ? _intensity : B 769 ); 770 } 771 } 772 } 773 774 /** 775 A class that provides the ability to work with color images in P1 format. 776 NB: The format is raw text. 777 778 Note: 779 This class supports indexing and assigning values to specific pixels via 1D or 2D indexing, and provides P1 file loading/saving capabilities. 780 According to the accepted convention, in the original description of the format inside the Netpbm package, the extension of these files should be `*.pbm`. 781 782 Typical usage: 783 ---- 784 // creating of empty image 785 auto img = new P1Image; 786 // load image from file `Lenna.pbm` 787 img.load(`Lenna.pbm`); 788 // change pixel at coords (10; 10), now are white 789 img[10, 10] = new RGBColor(255, 255, 255); 790 // get color of 11th pixel 791 img[10].writeln; 792 // save file as `Lenna2.pbm` 793 mg.save(`Lenna2.pbm`); 794 795 // creating image of 10x10, all pixels are black 796 auto img2 = new P1Image(10, 10, new RGBColor(0, 0, 0)); 797 // increasing luminance by two 798 img2[10] = img2[10] * 2; 799 // save as `test.pbm` 800 img2.save(`test.pbm`); 801 ---- 802 */ 803 class P1Image : PixMapFile 804 { 805 mixin addConstructor!(PixMapFormat.PBM_TEXT); 806 807 override void loader() 808 { 809 string line; 810 int index; 811 812 auto WHITE = new RGBColor(255, 255, 255); 813 auto BLACK = new RGBColor(0, 0, 0); 814 815 while ((line = _file.readln) !is null) 816 { 817 auto row = line.replace(" ", "").replace("\n", ""); 818 819 foreach (i, e; row) 820 { 821 _image[i, index] = (e.to!string == "0") ? WHITE : BLACK; 822 } 823 index++; 824 } 825 } 826 827 override void saver() 828 { 829 foreach (rows; _image.array.chunks(width)) 830 { 831 _file.writeln( 832 rows 833 .map!(a => (a.luminance < 255) ? "1" : "0") 834 .join(" ") 835 ); 836 } 837 } 838 } 839 840 /** 841 A class that provides the ability to work with color images in P2 format. 842 NB: The format is raw text. 843 844 Note: 845 This class supports indexing and assigning values to specific pixels via 1D or 2D indexing, and provides P2 file loading/saving capabilities. 846 According to the accepted convention, in the original description of the format inside the Netpbm package, the extension of these files should be `*.pgm`. 847 848 Typical usage: 849 ---- 850 // creating of empty image 851 auto img = new P2Image; 852 // load image from file `Lenna.pgm` 853 img.load(`Lenna.pgm`); 854 // change pixel at coords (10; 10), now are white 855 img[10, 10] = new RGBColor(255, 255, 255); 856 // get color of 11th pixel 857 img[10].writeln; 858 // save file as `Lenna2.pgm` 859 img.save(`Lenna2.pgm`); 860 861 // creating image of 10x10, pixels are black 862 auto img2 = new P2Image(10, 10, new RGBColor(0, 0, 0)); 863 // increasing luminance by two 864 img2[10] = img2[10] * 2; 865 // save as `test.pgm` 866 img2.save(`test.pgm`); 867 ---- 868 */ 869 class P2Image : PixMapFile 870 { 871 mixin(addProperty!(int, "Intensity", "255")); 872 mixin addConstructor!(PixMapFormat.PGM_TEXT); 873 874 override void loader() 875 { 876 // skip maximal intensity description 877 auto data = _file.readln; 878 _intensity = data.parse!int; 879 880 string line; 881 int index; 882 883 while ((line = _file.readln) !is null) 884 { 885 auto row = line.split; 886 887 foreach (i, e; row) 888 { 889 auto l = e.parse!int; 890 auto I = (l > _intensity) ? _intensity : l; 891 _image[i, index] = new RGBColor(I, I, I); 892 } 893 index++; 894 } 895 } 896 897 override void saver() 898 { 899 _file.writeln(_intensity); 900 901 foreach (rows; _image.array.chunks(width)) 902 { 903 auto toIntensity(RGBColor color) 904 { 905 int I; 906 if ((color.getR == color.getG) && (color.getG == color.getB) && (color.getR == color.getB)) 907 { 908 I = color.getR; 909 } 910 else 911 { 912 I = color.luminance601.to!int; 913 } 914 return (I > _intensity) ? _intensity : I; 915 } 916 917 _file.writeln( 918 rows 919 .map!(a => toIntensity(a).to!string) 920 .join(" ") 921 ); 922 } 923 } 924 } 925 926 /** 927 A class that provides the ability to work with color images in P5 format. 928 NB: The format is raw binary. 929 930 Note: 931 This class supports indexing and assigning values to specific pixels via 1D or 2D indexing, and provides P5 file loading/saving capabilities. 932 According to the accepted convention, in the original description of the format inside the Netpbm package, the extension of these files should be `*.pgm`. 933 934 Typical usage: 935 ---- 936 // create empty image 937 auto img = new P5Image; 938 // load from file 939 img.load(`Lenna.pgm`); 940 // set pixel at (10;10) to white color 941 img[10, 10] = new RGBColor(255, 255, 255); 942 // get color of 11th pixel 943 img[10].writeln; 944 // save to file 945 img.save(`Lenna2.pgm`); 946 947 // creating image of size 10x10, all pixels black 948 auto img2 = new P5Image(10, 10, new RGBColor(0, 0, 0)); 949 // increase luminance twice 950 img2[10] = img2[10] * 2; 951 // save as pgm file 952 img2.save(`test.pgm`); 953 ---- 954 */ 955 class P5Image : PixMapFile 956 { 957 mixin(addProperty!(int, "Intensity", "255")); 958 mixin addConstructor!(PixMapFormat.PGM_BINARY); 959 960 override void loader() 961 { 962 // skip maximal intensity description 963 auto data = _file.readln; 964 _intensity = data.to!int; 965 966 auto buffer = new ubyte[width * height]; 967 _file.rawRead!ubyte(buffer); 968 969 foreach (i, e; buffer) 970 { 971 auto I = (e > _intensity) ? _intensity : e; 972 _image[i] = new RGBColor(I, I, I); 973 } 974 } 975 976 override void saver() 977 { 978 _file.writeln(_intensity); 979 980 foreach (e; _image.array) 981 { 982 ubyte I; 983 if ((e.getR == e.getG) && (e.getG == e.getB) && (e.getR == e.getB)) 984 { 985 I = e.getR.to!ubyte; 986 } 987 else 988 { 989 I = e.luminance601.to!ubyte; 990 } 991 992 _file.write( 993 cast(char) I 994 ); 995 } 996 } 997 } 998 999 /** 1000 A class that provides the ability to work with color images in P4 format. 1001 NB: The format is raw binary. 1002 1003 Note: 1004 This class supports indexing and assigning values to specific pixels via 1D or 2D indexing, and provides P4 file loading/saving capabilities. 1005 According to the accepted convention, in the original description of the format inside the Netpbm package, the extension of these files should be `*.pbm`. 1006 1007 Typical usage: 1008 ---- 1009 // create empty P4 image 1010 auto img = new P4Image; 1011 // load from file 1012 img.load(`Lenna.pbm`); 1013 // set pixel at (10; 10) as white 1014 img[10, 10] = new RGBColor(255, 255, 255); 1015 // get 11th pixel 1016 img[10].writeln; 1017 // save to file 1018 img.save(`Lenna2.pbm`); 1019 1020 // new P4 image, size is 10x10, all pixels black 1021 auto img2 = new P4Image(10, 10, new RGBColor(0, 0, 0)); 1022 // increase two times 1023 img2[10] = img2[10] * 2; 1024 // save as pbm file 1025 img2.save(`test.pbm`); 1026 ---- 1027 */ 1028 class P4Image : PixMapFile 1029 { 1030 mixin addConstructor!(PixMapFormat.PBM_BINARY); 1031 1032 auto setBit(int value, int n) 1033 { 1034 return (value | (1 << n)); 1035 } 1036 1037 auto getBit(int value, int n) 1038 { 1039 return ((value >> n) & 1); 1040 } 1041 1042 auto clearBit(int value, int n) 1043 { 1044 return (value & ~(1 << n)); 1045 } 1046 1047 auto BLACK = new RGBColor(0, 0, 0); 1048 auto WHITE = new RGBColor(255, 255, 255); 1049 1050 override void loader() 1051 { 1052 auto imageSize = width * height; 1053 auto buffer = new ubyte[imageSize]; 1054 _file.rawRead!ubyte(buffer); 1055 1056 int index; 1057 1058 foreach (e; buffer) 1059 { 1060 if (index < imageSize) 1061 { 1062 foreach (i; 0..8) 1063 { 1064 auto I = getBit(cast(int) e, 7 - i); 1065 _image[index] = I ? BLACK : WHITE; 1066 index++; 1067 } 1068 } 1069 else 1070 { 1071 break; 1072 } 1073 } 1074 } 1075 1076 override void saver() 1077 { 1078 foreach (e; _image.array.chunks(width)) 1079 { 1080 foreach (r; e.chunks(8)) 1081 { 1082 auto bits = 0x00; 1083 1084 foreach (i, b; r) 1085 { 1086 auto I = (b.luminance == 0) ? 1 : 0; 1087 1088 if (I == 1) 1089 { 1090 bits = setBit(bits, cast(int) (7 - i)); 1091 } 1092 } 1093 _file.write( 1094 cast(char) bits 1095 ); 1096 } 1097 } 1098 } 1099 } 1100 1101 /** 1102 A constructor function that creates an image with the given length, width, and format. 1103 By default, all parameters are 0, and the format is represented by the PixMapFormat.PPM_BINARY value, which corresponds to an image with a P6 format. 1104 Params: 1105 width = Width of image as size_t value. 1106 height = Height of image as size_t value. 1107 pmFormat = Image format as enum PixMapFormat 1108 1109 Typical usage: 1110 ---- 1111 auto img = image(20, 20, PixMapFormat.PPM_TEXT); // creates image with P3 format type 1112 ---- 1113 */ 1114 PixMapFile image(size_t width = 0, size_t height = 0, PixMapFormat pmFormat = PixMapFormat.PPM_BINARY) 1115 { 1116 PixMapFile pixmap; 1117 1118 final switch (pmFormat) with (PixMapFormat) 1119 { 1120 case PBM_TEXT: 1121 pixmap = new P1Image(width, height); 1122 break; 1123 case PBM_BINARY: 1124 pixmap = new P4Image(width, height); 1125 break; 1126 case PGM_TEXT: 1127 pixmap = new P2Image(width, height); 1128 break; 1129 case PGM_BINARY: 1130 pixmap = new P5Image(width, height); 1131 break; 1132 case PPM_TEXT: 1133 pixmap = new P3Image(width, height); 1134 break; 1135 case PPM_BINARY: 1136 pixmap = new P6Image(width, height); 1137 break; 1138 } 1139 1140 return pixmap; 1141 } 1142 1143 /** 1144 A constructor function that creates an image with the given length, width, and format. 1145 By default, all parameters are 0, and the format is represented by the "P6" value, which corresponds to an image with a P6 format. 1146 Params: 1147 width = Width of image as size_t value. 1148 height = Height of image as size_t value. 1149 pmFormat = Image format as string 1150 1151 Typical usage: 1152 ---- 1153 auto img = image(20, 20, "P3"); // creates image with P3 format type 1154 ---- 1155 */ 1156 PixMapFile image(size_t width = 0, size_t height = 0, string pmFormat = "P6") 1157 { 1158 PixMapFile pixmap; 1159 1160 switch (pmFormat) 1161 { 1162 case "P1": 1163 pixmap = new P1Image(width, height); 1164 break; 1165 case "P4": 1166 pixmap = new P4Image(width, height); 1167 break; 1168 case "P2": 1169 pixmap = new P2Image(width, height); 1170 break; 1171 case "P5": 1172 pixmap = new P5Image(width, height); 1173 break; 1174 case "P3": 1175 pixmap = new P3Image(width, height); 1176 break; 1177 case "P6": 1178 pixmap = new P6Image(width, height); 1179 break; 1180 default: 1181 assert(0); 1182 } 1183 1184 return pixmap; 1185 }