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