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 	PF_RGB_BINARY = "PF",
504 }
505 
506 /**
507 	Common ancestor for all subsequent image types.
508 	Implements a generic way to load/save images by providing generic load/save methods. 
509 	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.
510 	
511 	Implementation Note: The specific loading method is already implemented by descendant classes by overriding the abstract loader/saver methods in their implementations.
512 */
513 class PixMapFile
514 {
515 	mixin(addProperty!(PixMapImage, "Image"));
516 	
517 	protected
518 	{
519 		File _file;
520 		PixMapFormat _header;
521 		
522 		abstract void loader();
523 		abstract void saver();
524 	}
525 
526 	private
527 	{
528 		/// Set i/o mode for reading/writing Portable Anymap Images. Actual for OS Windows. For internal use.
529 		auto IOMode(string mode)
530 		{
531 			
532 			if (isBinaryFormat) 
533 			{
534 				return mode ~ `b`;
535 			}
536 			else
537 			{
538 				return mode;
539 			}
540 		}
541 	}
542 
543 	/// Basic file loading procedure
544 	void load(string filename)
545 	{
546 		with (_file)
547 		{
548 			open(filename, IOMode(`r`));
549 
550 			if (readln.strip == EnumValue(_header))
551 			{
552 				auto imageSize = readln.split;
553 				auto width = imageSize[0].parse!int;
554 				auto height = imageSize[1].parse!int;
555 
556 				_image = new PixMapImage(width, height);
557 
558 				loader;
559 			}
560 		}
561 	}
562 	
563 	/// Basic file saving procedure
564 	void save(string filename)
565 	{
566 		with (_file)
567 		{
568 			open(filename, IOMode("w"));
569 			writeln(EnumValue(_header));
570 			writeln(_image.width, " ", _image.height);
571 
572 			saver;
573 		}
574 	}
575 
576 	/// Is raw format ?
577 	final bool isBinaryFormat()
578 	{
579 		return 
580 				( 
581 				  (_header == PixMapFormat.PBM_BINARY) |
582 				  (_header == PixMapFormat.PGM_BINARY) |
583 				  (_header == PixMapFormat.PPM_BINARY) |
584 				  (_header == PixMapFormat.PF_RGB_BINARY)
585 				);
586 	}
587 
588 	/// Is text format ?
589 	final bool isTextFormat()
590 	{
591 		return 
592 				( 
593 				  (_header == PixMapFormat.PBM_TEXT) |
594 				  (_header == PixMapFormat.PGM_TEXT) |
595 				  (_header == PixMapFormat.PPM_TEXT)
596 				);
597 	}	
598 
599 	/// Get image object as PixMapImage object
600 	final PixMapImage image() 
601 	{ 
602 		return this.getImage; 
603 	}
604 	
605 	/// Set image object as PixMapImage object
606 	final void image(PixMapImage image)
607 	{
608 		this.setImage(image);
609 	}
610 
611 	/// Convenient alias for working with PixMapFile same as PixMapImage
612 	alias image this;
613 }
614 
615 /**
616 	A class that provides the ability to work with color images in P6 format. 
617 	NB: The format is raw binary. 
618 	
619 	Note: 
620 		This class supports indexing and assigning values ​​to specific pixels via 1D or 2D indexing, and provides P6 file loading/saving capabilities. 
621 		According to the accepted convention, in the original description of the format inside the Netpbm package, the extension of these files should be `*.ppm`.
622 		
623 	Typical usage:
624     ----
625     // creating of empty image
626     auto img = new P6Image;  					
627     // load image from file `Lenna.ppm`
628     img.load(`Lenna.ppm`);   					
629     // change pixel at coords (10; 10), now are white
630     img[10, 10] = new RGBColor(255, 255, 255); 	
631     // get color of 11th pixel
632     img[10].writeln;							
633     // save file as `Lenna2.ppm`
634     img.save(`Lenna2.ppm`);						
635     
636     // creating image of 10x10, all pixels are red
637     auto img2 = new P6Image(10, 10, new RGBColor(255, 0, 255)); 
638     // increasing luminance by two
639     img2[10] = img2[10] * 2; 									
640     // save as `test.ppm`
641     img2.save(`test.ppm`);										
642     ----
643 */
644 class P6Image : PixMapFile
645 {
646 	mixin(addProperty!(int, "Intensity", "255"));
647 	mixin addConstructor!(PixMapFormat.PPM_BINARY);
648 
649 	override void loader()
650 	{
651 		auto data = _file.readln;
652 		_intensity = data.parse!int;
653 
654 		auto buffer = new ubyte[width * 3];
655 		
656 		for (uint i = 0; i < height; i++)
657 		{
658 		 	_file.rawRead!ubyte(buffer);
659 						
660 		    for (uint j = 0; j < width; j++)
661 		    {
662 				auto R = buffer[j * 3];
663 				auto G = buffer[j * 3 + 1];
664 				auto B = buffer[j * 3 + 2];
665 		 	 	_image[j, i] = new RGBColor(
666 					(R > _intensity) ? _intensity : R,
667 					(G > _intensity) ? _intensity : G,
668 					(B > _intensity) ? _intensity : B
669 				);
670 		    } 
671 		}
672 	}
673 
674 	override void saver()
675 	{
676 		_file.writeln(_intensity);
677 
678 		foreach (e; _image.array)
679 		{
680 			auto R = e.getR;
681 			auto G = e.getG;
682 			auto B = e.getB;
683 
684 			auto rr = (R > _intensity) ? _intensity : R;
685 			auto gg = (G > _intensity) ? _intensity : G;
686 			auto bb = (B > _intensity) ? _intensity : B;
687 
688 			_file.write(
689 				cast(char) rr,
690 				cast(char) gg,
691 				cast(char) bb
692 		    );
693 	    }
694 	}
695 }
696 
697 /**
698 	A class that provides the ability to work with color images in P3 format. 
699 	NB: The format is raw text. 
700 	
701 	Note: 
702 		This class supports indexing and assigning values ​​to specific pixels via 1D or 2D indexing, and provides P3 file loading/saving capabilities. 
703 		According to the accepted convention, in the original description of the format inside the Netpbm package, the extension of these files should be `*.ppm`.
704 		
705 	Typical usage:
706     ----
707     // creating of empty image
708     auto img = new P3Image;  					
709     // load image from file `Lenna.ppm`
710     img.load(`Lenna.ppm`);   					
711     // change pixel at coords (10; 10), now are white
712     img[10, 10] = new RGBColor(255, 255, 255); 	
713     // get color of 11th pixel
714     img[10].writeln;							
715     // save file as `Lenna2.ppm`
716     img.save(`Lenna2.ppm`);						
717     
718     // creating image of 10x10, all pixels are red
719     auto img2 = new P3Image(10, 10, new RGBColor(255, 0, 255)); 
720     // increasing luminance by two
721     img2[10] = img2[10] * 2; 									
722     // save as `test.ppm`
723     img2.save(`test.ppm`);										
724     ----
725 */
726 class P3Image : PixMapFile
727 {
728 	mixin(addProperty!(int, "Intensity", "255"));
729 	mixin addConstructor!(PixMapFormat.PPM_TEXT);
730 
731 	override void loader()
732 	{
733 		// skip maximal intensity description
734 		auto data = _file.readln;
735 		_intensity = data.parse!int;
736 		
737 		string triplet;
738 		int index = 0;
739 						
740 		while ((triplet = _file.readln) !is null)
741 		{				
742 			auto rgb = triplet.split;
743 			auto R = rgb[0].parse!int;
744 		    auto G = rgb[1].parse!int;
745 		    auto B = rgb[2].parse!int;
746 
747 			_image[index] = new RGBColor(
748 		 		(R > _intensity) ? _intensity : R,
749 		        (G > _intensity) ? _intensity : G,
750 		        (B > _intensity) ? _intensity : B		
751  			);
752 		 	index++;
753 		}
754 	}
755 
756 	override void saver()
757 	{
758 		_file.writeln(_intensity);
759 
760 		foreach (e; _image.array)
761 		{
762 			auto R = e.getR;
763 			auto G = e.getG;
764 			auto B = e.getB;
765 
766 			_file.writefln(
767 				"%d %d %d",
768 				(R > _intensity) ? _intensity : R,
769 				(G > _intensity) ? _intensity : G,
770 				(B > _intensity) ? _intensity : B
771 		    );
772 	    }
773      }
774 }
775 
776 /**
777 	A class that provides the ability to work with color images in P1 format. 
778 	NB: The format is raw text. 
779 	
780 	Note: 
781 		This class supports indexing and assigning values ​​to specific pixels via 1D or 2D indexing, and provides P1 file loading/saving capabilities. 
782 		According to the accepted convention, in the original description of the format inside the Netpbm package, the extension of these files should be `*.pbm`.
783 		
784 	Typical usage:
785     ----
786     // creating of empty image
787     auto img = new P1Image;  					
788     // load image from file `Lenna.pbm`
789     img.load(`Lenna.pbm`);   					
790     // change pixel at coords (10; 10), now are white
791     img[10, 10] = new RGBColor(255, 255, 255); 	
792     // get color of 11th pixel
793     img[10].writeln;							
794     // save file as `Lenna2.pbm`
795     mg.save(`Lenna2.pbm`);						
796     
797     // creating image of 10x10, all pixels are black
798     auto img2 = new P1Image(10, 10, new RGBColor(0, 0, 0)); 
799     // increasing luminance by two
800     img2[10] = img2[10] * 2; 									
801     // save as `test.pbm`
802     img2.save(`test.pbm`);										
803     ----
804 */
805 class P1Image : PixMapFile
806 {
807 	mixin addConstructor!(PixMapFormat.PBM_TEXT);
808 
809 	override void loader()
810 	{
811 		 string line;
812 		 int index;
813 		
814 		 auto WHITE = new RGBColor(255, 255, 255);
815 		 auto BLACK = new RGBColor(0, 0, 0);
816 						
817 		 while ((line = _file.readln) !is null)
818 		 {
819 		 	auto row  = line.replace(" ", "").replace("\n", "");
820 		
821 		 	foreach (i, e; row)
822 		 	{
823 		 		_image[i, index] = (e.to!string == "0") ? WHITE : BLACK; 			
824 		 	}					
825 		 	index++;
826 		 }				
827 	}
828 
829 	override void saver()
830 	{
831 		foreach (rows; _image.array.chunks(width))
832 		{
833 		 	_file.writeln(
834 		 		rows
835 		 			.map!(a => (a.luminance < 255) ? "1" : "0")
836 		 			.join(" ")
837 		 	);
838 		}
839 	}
840 }
841 
842 /**
843 	A class that provides the ability to work with color images in P2 format. 
844 	NB: The format is raw text. 
845 	
846 	Note: 
847 		This class supports indexing and assigning values ​​to specific pixels via 1D or 2D indexing, and provides P2 file loading/saving capabilities. 
848 		According to the accepted convention, in the original description of the format inside the Netpbm package, the extension of these files should be `*.pgm`.
849 		
850 	Typical usage:
851     ----
852     // creating of empty image
853     auto img = new P2Image;  					
854     // load image from file `Lenna.pgm`
855     img.load(`Lenna.pgm`);   					
856     // change pixel at coords (10; 10), now are white
857     img[10, 10] = new RGBColor(255, 255, 255); 	
858     // get color of 11th pixel
859     img[10].writeln;							
860     // save file as `Lenna2.pgm`
861     img.save(`Lenna2.pgm`);						
862     
863     // creating image of 10x10, pixels are black
864     auto img2 = new P2Image(10, 10, new RGBColor(0, 0, 0)); 
865     // increasing luminance by two
866     img2[10] = img2[10] * 2; 									
867     // save as `test.pgm`
868     img2.save(`test.pgm`);										
869     ----
870 */
871 class P2Image : PixMapFile
872 {
873 	mixin(addProperty!(int, "Intensity", "255"));
874 	mixin addConstructor!(PixMapFormat.PGM_TEXT);
875 
876 	override void loader()
877 	{
878 		 // skip maximal intensity description
879 		 auto data = _file.readln;
880 		 _intensity = data.parse!int;
881 		
882 	     string line;
883 		 int index;
884 		
885 		 while ((line = _file.readln) !is null)
886 		 {
887 		 	auto row  = line.split;
888 		
889 		 	foreach (i, e; row)
890 		 	{
891 		 		auto l = e.parse!int;
892 				auto I = (l > _intensity) ? _intensity : l;
893 		 		_image[i, index] = new RGBColor(I, I, I);  						
894 		 	}					
895 		 	index++;
896 		 } 
897 	}
898 
899 	override void saver()
900 	{
901 		_file.writeln(_intensity);
902 	
903 	   	foreach (rows; _image.array.chunks(width))
904 	    {
905 			auto toIntensity(RGBColor color)
906 			{
907 				int I;
908 				if ((color.getR == color.getG) && (color.getG == color.getB) && (color.getR == color.getB))
909 				{
910 					I = color.getR;
911 				}
912 				else
913 				{
914 					I = color.luminance601.to!int;
915 				}
916 				return (I > _intensity) ? _intensity : I;				
917 			}
918 			
919 	    	_file.writeln(
920 	    		 rows
921 	    		 	.map!(a => toIntensity(a).to!string)
922 	    		 	.join(" ")
923 	    	);
924 	    }
925      }
926 }
927 
928 /**
929 	A class that provides the ability to work with color images in P5 format. 
930 	NB: The format is raw binary. 
931 	
932 	Note: 
933 		This class supports indexing and assigning values ​​to specific pixels via 1D or 2D indexing, and provides P5 file loading/saving capabilities. 
934 		According to the accepted convention, in the original description of the format inside the Netpbm package, the extension of these files should be `*.pgm`.
935 		
936 	Typical usage:
937     ----
938     // create empty image
939     auto img = new P5Image;
940     // load from file  					
941     img.load(`Lenna.pgm`);   					
942     // set pixel at (10;10) to white color
943     img[10, 10] = new RGBColor(255, 255, 255);
944     // get color of 11th pixel 	
945     img[10].writeln;							
946     // save to file
947     img.save(`Lenna2.pgm`);						
948     
949     // creating image of size 10x10, all pixels black
950     auto img2 = new P5Image(10, 10, new RGBColor(0, 0, 0)); 
951     // increase luminance twice
952     img2[10] = img2[10] * 2;
953     // save as pgm file 									
954     img2.save(`test.pgm`);										
955     ----
956 */
957 class P5Image : PixMapFile
958 {
959 	mixin(addProperty!(int, "Intensity", "255"));
960 	mixin addConstructor!(PixMapFormat.PGM_BINARY);
961 
962 	override void loader()
963 	{
964 		// skip maximal intensity description
965 		auto data = _file.readln;
966 		_intensity = data.to!int;
967 
968 		auto buffer = new ubyte[width * height];
969 		_file.rawRead!ubyte(buffer);
970 
971 		foreach (i, e; buffer)
972 		{
973 			auto I =  (e > _intensity) ? _intensity : e;
974 			_image[i] = new RGBColor(I, I, I);
975 		}
976 	}
977 
978 	override void saver()
979 	{
980 		_file.writeln(_intensity);
981 
982 		foreach (e; _image.array)
983 		{
984 			ubyte I;
985 			if ((e.getR == e.getG) && (e.getG == e.getB) && (e.getR == e.getB))
986 			{
987 				I = e.getR.to!ubyte;
988 			}
989 			else
990 			{
991 				I = e.luminance601.to!ubyte;
992 			}
993 
994 			_file.write(
995 				cast(char) I
996 			);
997 		}
998      }
999 }
1000 
1001 /**
1002 	A class that provides the ability to work with color images in P4 format. 
1003 	NB: The format is raw binary. 
1004 	
1005 	Note: 
1006 		This class supports indexing and assigning values ​​to specific pixels via 1D or 2D indexing, and provides P4 file loading/saving capabilities. 
1007 		According to the accepted convention, in the original description of the format inside the Netpbm package, the extension of these files should be `*.pbm`.
1008 		
1009 	Typical usage:
1010     ----
1011     // create empty P4 image
1012     auto img = new P4Image; 
1013     // load from file 					
1014     img.load(`Lenna.pbm`);   					
1015     // set pixel at (10; 10) as white
1016     img[10, 10] = new RGBColor(255, 255, 255); 	
1017     // get 11th pixel
1018     img[10].writeln;							
1019     // save to file
1020     img.save(`Lenna2.pbm`);						
1021     
1022     // new P4 image, size is 10x10, all pixels black
1023     auto img2 = new P4Image(10, 10, new RGBColor(0, 0, 0)); 
1024     // increase two times
1025     img2[10] = img2[10] * 2; 									
1026     // save as pbm file
1027     img2.save(`test.pbm`);										
1028     ----
1029 */
1030 class P4Image : PixMapFile
1031 {
1032 	mixin addConstructor!(PixMapFormat.PBM_BINARY);
1033 
1034 	auto setBit(int value, int n)
1035 	{
1036 		return (value | (1 << n));
1037 	}
1038 
1039 	auto getBit(int value, int n)
1040 	{
1041 		return ((value >> n) & 1);
1042 	}
1043 
1044 	auto clearBit(int value, int n)
1045 	{
1046 		return (value & ~(1 << n));
1047 	}
1048 	
1049 	auto BLACK = new RGBColor(0, 0, 0);
1050 	auto WHITE = new RGBColor(255, 255, 255);
1051 
1052 	override void loader()
1053 	{
1054 		auto imageSize = width * height;
1055 		auto buffer = new ubyte[imageSize];
1056 		_file.rawRead!ubyte(buffer);
1057 
1058 		int index;
1059 
1060 		foreach (e; buffer)
1061 		{
1062 			if (index < imageSize)
1063 			{
1064 				foreach (i; 0..8)
1065 				{
1066 					auto I = getBit(cast(int) e, 7 - i);
1067 					_image[index] = I ? BLACK : WHITE;
1068 					index++;
1069 				}
1070 			}
1071 			else
1072 			{
1073 				break;
1074 			}
1075 		}				
1076 	}
1077 
1078 	override void saver()
1079 	{	    
1080 	    foreach (e; _image.array.chunks(width))
1081 	    {
1082 			foreach (r; e.chunks(8))
1083 			{
1084 				auto bits = 0x00;
1085 				
1086 				foreach (i, b; r)
1087 				{
1088 					auto I = (b.luminance == 0) ? 1 : 0;
1089 					
1090 					if (I == 1)
1091 					{
1092 						bits = setBit(bits, cast(int) (7 - i));
1093 					}
1094 				}
1095 				_file.write(
1096 					cast(char) bits
1097 				);
1098 			}
1099 		}
1100 	}
1101 }
1102 
1103 /// Endianess (i.e byte-order)
1104 enum BYTE_ORDER
1105 {
1106 	/// Little-endian byte-order
1107 	LITTLE_ENDIAN,
1108 	/// Big-endian byte-order
1109 	BIG_ENDIAN
1110 }
1111 
1112 
1113 /**
1114 	A class that provides the ability to work with color images in PF (portable floatmap image) format. 
1115 	NB: The format is raw binary. Support of this format is EXPERIMENTAL (!!!).
1116 	
1117 	Note: 
1118 		This class supports indexing and assigning values ​​to specific pixels via 1D or 2D indexing, and provides PF file loading/saving capabilities. 
1119 		According to the accepted convention, in the original description of the format inside the Netpbm package, the extension of these files should be `*.pfm`.
1120 		
1121 	Typical usage:
1122     ----
1123     // create empty PF image
1124     auto img = new PFImage; 
1125     // load from file 					
1126     img.load(`Lenna.pfm`);   					
1127     // set pixel at (10; 10) as white
1128     img[10, 10] = new RGBColor(255, 255, 255); 	
1129     // get 11th pixel
1130     img[10].writeln;							
1131     // save to file
1132     img.save(`Lenna2.pfm`);						
1133     
1134     // new PF image, size is 10x10, all pixels black
1135     auto img2 = new PFImage(10, 10, new RGBColor(0, 0, 0)); 
1136     // increase two times
1137     img2[10] = img2[10] * 2; 
1138     // select byte order for saving (by default, little-endian, i.e BYTE_ORDER.LITTLE_ENDIAN)
1139     img2.setOrder(BYTE_ORDER.BIG_ENDIAN);									
1140     // save as pfm file
1141     img2.save(`test.pfm`);										
1142     ----
1143 */
1144 class PFImage : PixMapFile
1145 {
1146 	mixin(addProperty!(uint, "Intensity", "255"));
1147 	mixin(addProperty!(uint, "Order", "BYTE_ORDER.LITTLE_ENDIAN"));
1148 	
1149 	mixin addConstructor!(PixMapFormat.PF_RGB_BINARY);
1150 	
1151 	private 
1152 	{
1153 		/// reconstruct unsigned integer value from unsigned bytes (little-endian order)
1154 		static uint fromLEBytes(ubyte[] bytes)
1155 		{
1156 			return ((bytes[3] << 24) | (bytes[2] << 16) | (bytes[1] << 8) | bytes[0]);
1157 		}
1158 		
1159 		/// reconstruct unsigned integer value from unsigned bytes (big-endian order)
1160 		static uint fromBEBytes(ubyte[] bytes)
1161 		{
1162 			return ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]);
1163 		}
1164 		
1165 		static ubyte[] toBEBytes(uint value)
1166 		{
1167 			ubyte[] bytes;
1168 			
1169 			bytes ~= (value & 0xff000000) >> 24;
1170 			bytes ~= (value & 0x00ff0000) >> 16;
1171 			bytes ~= (value & 0x0000ff00) >> 8;
1172 			bytes ~= (value & 0x000000ff);
1173 			
1174 			return bytes;
1175 		}
1176 		
1177 		static ubyte[] toLEBytes(uint value)
1178 		{
1179 			ubyte[] bytes;
1180 		
1181 			bytes ~= (value & 0x000000ff);
1182 			bytes ~= (value & 0x0000ff00) >> 8;
1183 			bytes ~= (value & 0x00ff0000) >> 16;
1184 			bytes ~= (value & 0xff000000) >> 24;
1185 			
1186 			return bytes;
1187 		}
1188 	}
1189 	
1190 	override void loader()
1191 	{
1192 		auto data = _file.readln;
1193 		auto ef = data.parse!float;
1194 		
1195 		uint function(ubyte[]) byteLoader;
1196 		
1197 		if (ef < 0)
1198 		{
1199 			_order = BYTE_ORDER.LITTLE_ENDIAN;
1200 			byteLoader = &fromLEBytes;
1201 		}
1202 		else
1203 		{
1204 			_order = BYTE_ORDER.BIG_ENDIAN;
1205 			byteLoader = &fromBEBytes;
1206 		}
1207 		
1208 		float bytes2float(ubyte[] bytes)
1209 		{
1210 			uint tmp = byteLoader(bytes);
1211 			float value = *(cast(float*) &tmp);
1212 			return value;
1213 		}
1214 		
1215 		auto blockSize = 3 * float.sizeof;
1216 		auto buffer = new ubyte[_width * blockSize];
1217 	
1218 		foreach (i; 0.._height)
1219 		{
1220 			_file.rawRead!ubyte(buffer);
1221 			
1222 			foreach (j; 0.._width)
1223 			{
1224 				auto wq = buffer[(j * blockSize)..(j * blockSize + blockSize)];
1225 				
1226 				_image[j, _height - i] = new RGBColor(
1227 					cast(int) (_intensity * bytes2float(wq[0..4])),
1228 					cast(int) (_intensity * bytes2float(wq[4..8])),
1229 					cast(int) (_intensity * bytes2float(wq[8..12]))
1230 				);
1231 			}
1232 		}
1233 	}
1234 	
1235 	override void saver()
1236 	{
1237 		ubyte[] function(uint) byteSaver;
1238 		
1239 		final switch (_order) with (BYTE_ORDER) {
1240 			case LITTLE_ENDIAN:
1241 				_file.writeln(-1.0);
1242 				byteSaver = &toLEBytes;
1243 				break;
1244 			case BIG_ENDIAN:
1245 				_file.writeln(1.0);
1246 				byteSaver = &toBEBytes;
1247 				break;
1248 		}
1249 		
1250 		ubyte[] int2bytes(int value)
1251 		{
1252 			float I = float(value) / float(_intensity);
1253 			uint tmp = *(cast(uint*) &I);
1254 			return byteSaver(tmp);
1255 		}
1256 		
1257 		foreach (i; 0.._height)
1258 		{
1259 			foreach (j; 0.._width)
1260 			{
1261 				auto color = _image[j, _height - i];
1262 				
1263 				_file.write(
1264 					cast(char[]) int2bytes(color.getR),
1265 					cast(char[]) int2bytes(color.getG),
1266 					cast(char[]) int2bytes(color.getB)
1267 				);
1268 			}
1269 		}
1270 	}
1271 }
1272 
1273 /**
1274 A constructor function that creates an image with the given length, width, and format. 
1275 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.
1276 Params:
1277 width = Width of image as size_t value. 
1278 height = Height of image as size_t value.
1279 pmFormat = Image format as enum PixMapFormat
1280 
1281 Typical usage:
1282 ----
1283 auto img = image(20, 20, PixMapFormat.PPM_TEXT); 	// creates image with P3 format type
1284 ----
1285 */
1286 PixMapFile image(size_t width = 0, size_t height = 0, PixMapFormat pmFormat = PixMapFormat.PPM_BINARY)
1287 {
1288 	PixMapFile pixmap;
1289 
1290 	final switch (pmFormat) with (PixMapFormat)
1291 	{
1292 		case PBM_TEXT:
1293 			pixmap = new P1Image(width, height);
1294 			break;
1295 		case PBM_BINARY:
1296 			pixmap = new P4Image(width, height);
1297 			break;
1298 		case PGM_TEXT:
1299 			pixmap = new P2Image(width, height);
1300 			break;
1301 		case PGM_BINARY:
1302 			pixmap = new P5Image(width, height);
1303 			break;
1304 		case PPM_TEXT:
1305 			pixmap = new P3Image(width, height);
1306 			break;
1307 		case PPM_BINARY:
1308 			pixmap = new P6Image(width, height);
1309 			break;
1310 		case PF_RGB_BINARY:
1311 			pixmap = new PFImage(width, height);
1312 			break;
1313 	}
1314 
1315 	return pixmap;
1316 }
1317 
1318 /**
1319 A constructor function that creates an image with the given length, width, and format. 
1320 By default, all parameters are 0, and the format is represented by the "P6" value, which corresponds to an image with a P6 format.
1321 Params:
1322 width = Width of image as size_t value. 
1323 height = Height of image as size_t value.
1324 pmFormat = Image format as string
1325 
1326 Typical usage:
1327 ----
1328 auto img = image(20, 20, "P3"); 	// creates image with P3 format type
1329 ----
1330 */
1331 PixMapFile image(size_t width = 0, size_t height = 0, string pmFormat = "P6")
1332 {
1333 	PixMapFile pixmap;
1334 
1335 	switch (pmFormat) 
1336 	{
1337 		case "P1":
1338 			pixmap = new P1Image(width, height);
1339 			break;
1340 		case "P4":
1341 			pixmap = new P4Image(width, height);
1342 			break;
1343 		case "P2":
1344 			pixmap = new P2Image(width, height);
1345 			break;
1346 		case "P5":
1347 			pixmap = new P5Image(width, height);
1348 			break;
1349 		case "P3":
1350 			pixmap = new P3Image(width, height);
1351 			break;
1352 		case "P6":
1353 			pixmap = new P6Image(width, height);
1354 			break;
1355 		case "PF":
1356 			pixmap = new PFImage(width, height);
1357 			break;
1358 		default:
1359 			assert(0);
1360 	}
1361 
1362 	return pixmap;
1363 }