1 /*
2  * dimage - pcx.d
3  * by Laszlo Szeremi
4  *
5  * Copyright under Boost Software License.
6  */
7 
8 module leftout.pcx;
9 
10 import dimage.base;
11 import dimage.util;
12 static import std.stdio;
13 import bitleveld.datatypes;
14 import std.bitmanip;
15 
16 /**
17  * Implements ZSoft PCX file handling.
18  * Dummied out due to complexity.
19  */
20 public class PCX : Image {
21 	/**
22 	 * Version information
23 	 */
24 	public enum Version : ubyte {
25 		ver2_5				=	0,
26 		ver2_8				=	2,
27 		ver2_8noPal			=	3,
28 		paintbrushForWin	=	4,
29 		ver3_0				=	5,
30 	}
31 	/**
32 	 * Implementation of the PCX header
33 	 */
34 	public struct Header {
35 		ubyte	identifier	=	0x0A;	///PCX ID number
36 		ubyte	_version;				///Version number
37 		ubyte	encoding;				///Encoding format
38 		ubyte	bitsPerPixel;			///Bits per pixel
39 		ushort	xStart;					///Left of image
40 		ushort	yStart;					///Top of image
41 		ushort	xEnd;					///Right of image
42 		ushort	yEnd;					///Bottom of image
43 		ushort	hRes;					///Horizontal resolution
44 		ushort	vRes;					///Vertical resolution
45 		ubyte[48]	palette;			///16-Color EGA palette
46 		ubyte	reserved1;				///Unused padding
47 		ubyte	numBitPlanes;			///N of bitplanes
48 		ushort	bytesPerLine;			///Size of scanlines in bytes
49 		ushort	paletteType;			///Palette type
50 		ushort	hScreenSize;			///Horizontal screen size
51 		ushort	vScreenSize;			///Vertical screen size
52 		ubyte	reserved2;				///Unused padding
53 	}
54 	protected Header	header;		///Contains common information about the image
55 	protected uint		pitch;
56 	mixin PlanarAccess3Bit;
57 	mixin PlanarAccess4Bit;
58 	mixin ChunkyAccess2Bit;
59 	mixin MonochromeAccess;
60 	/**
61 	 * Blank constructor for files
62 	 */
63 	protected this() @nogc @safe pure {
64 
65 	}
66 	/**
67 	 * Basic constructor to create pcx objects
68 	 */
69 	public this(Header header, ubyte[] imageData, ubyte[] paletteData = []){
70 		//Validate header
71 		this.header = header;
72 		if (getPixelFormat == PixelFormat.Undefined) {
73 			throw new ImageFormatException("Unsupported pixelformat.");
74 		}
75 		//Force set version
76 		switch (getPixelFormat) {
77 			case PixelFormat.Indexed2Bit, PixelFormat.Indexed1Bit:
78 				this.header._version = Version.ver2_5;
79 				break;
80 			case PixelFormat.Indexed8Bit, PixelFormat.RGB888, PixelFormat.RGBA8888:
81 				this.header._version = Version.ver3_0;
82 				break;
83 			case PixelFormat.Planar8Color:
84 				this.header._version = Version.paintbrushForWin;
85 				break;
86 			default:
87 				if (paletteData.length) {
88 					this.header._version = Version.ver2_8;
89 				} else {
90 					this.header._version = Version.ver2_8noPal;
91 				}
92 				break;
93 		}
94 		if (paletteData.length <= 48) {
95 			for (int i ; i < paletteData.length ; i++) {
96 				this.header.palette[i] = paletteData[i];
97 			}
98 			this.paletteData = this.header.palette[0..$];
99 		} else {
100 			this.paletteData = paletteData;
101 		}
102 	}
103 	/**
104 	 * Loads a *.PCX file.
105 	 */
106 	public static load (F = std.stdio.File) (F file) {
107 		PCX result = new PCX();
108 		ubyte[] buffer;
109 		buffer.length = Header.sizeof;
110 		buffer = file.rawRead(buffer);
111 		if (buffer.length != Header.sizeof) 
112 			throw new ImageFormatException ("File is corrupted, cannot be read, and/or is not a *.PCX file!");
113 		result.header = reinterpretGet!Header(buffer);
114 		buffer.length = result.header.bytesPerLine;
115 		for (int y ; y < result.height ; y++){
116 			buffer = file.rawRead(buffer);
117 			if (buffer.length == result.header.bytesPerLine)
118 				result.imageData ~= buffer;
119 			else
120 				throw new ImageFormatException ("File is corrupted, cannot be read, and/or is not a *.PCX file!");
121 		}
122 		if (file.size - file.tell) {
123 			result.paletteData.length = 768;
124 			result.paletteData = file.rawRead(result.paletteData);
125 			if (result.paletteData.length == 768)
126 				throw new ImageFormatException ("File is corrupted, cannot be read, and/or is not a *.PCX file!");
127 		}
128 		return result;
129 	}
130 	protected void setupDelegates() @nogc @safe pure {
131 		switch (getPixelFormat) {
132 			case PixelFormat.Indexed1Bit:
133 				indexReader8Bit = &_readIndex_1bit;
134 				indexWriter8Bit = &_writeIndex_1bit;
135 				indexReader16bit = &_indexReadUpconv;
136 				pixelReader = &_readAndLookup;
137 				break;
138 			case PixelFormat.Indexed2Bit:
139 				indexReader8Bit = &_readIndex_2bit;
140 				indexWriter8Bit = &_writeIndex_2bit;
141 				indexReader16bit = &_indexReadUpconv;
142 				pixelReader = &_readAndLookup;
143 				break;
144 			case PixelFormat.Indexed8Bit:
145 				indexReader8Bit = &_readPixel_8bit;
146 				indexWriter8Bit = &_writePixel!(ubyte);
147 				indexReader16bit = &_indexReadUpconv;
148 				pixelReader = &_readAndLookup;
149 				break;
150 			case PixelFormat.Planar8Color:
151 				indexReader8Bit = &_readIndex_planar_3bit;
152 				indexWriter8Bit = &_writeIndex_planar_3bit;
153 				indexReader16bit = &_indexReadUpconv;
154 				pixelReader = &_readAndLookup;
155 				break;
156 			case PixelFormat.Planar16Color:
157 				indexReader8Bit = &_readIndex_planar_4bit;
158 				indexWriter8Bit = &_writeIndex_planar_4bit;
159 				indexReader16bit = &_indexReadUpconv;
160 				pixelReader = &_readAndLookup;
161 				break;
162 			case PixelFormat.RGB888:
163 				pixelReader = &_readPixelAndUpconv!(Pixel24Bit);
164 				break;
165 			case PixelFormat.RGBA8888:
166 				pixelReader = &_readPixelAndUpconv!(Pixel32BitRGBALE);
167 				break;
168 			default:
169 				break;
170 		}
171 	}
172 	override uint width() @nogc @safe @property const pure {
173 		return header.hRes;
174 	}
175 	override uint height() @nogc @safe @property const pure {
176 		return header.vRes;
177 	}
178 	override bool isIndexed() @nogc @safe @property const pure {
179 		return header.bitsPerPixel != 8 || header.numBitPlanes == 1;
180 	}
181 	override ubyte getBitdepth() @nogc @safe @property const pure {
182 		return cast(ubyte)(header.bitsPerPixel * header.numBitPlanes);
183 	}
184 	override ubyte getPaletteBitdepth() @nogc @safe @property const pure {
185 		return isIndexed() ? 24 : 0;
186 	}
187 	override uint getPixelFormat() @nogc @safe @property const pure {
188 		if (header.numBitPlanes == 1) {
189 			switch (header.bitsPerPixel) {
190 				case 1:		return PixelFormat.Indexed1Bit;
191 				case 2:		return PixelFormat.Indexed2Bit;
192 				case 8:		return PixelFormat.Indexed8Bit;
193 				default: 	break;
194 			}
195 		} else if (header.numBitPlanes == 3) {
196 			switch (header.bitsPerPixel) {
197 				case 1:		return PixelFormat.Planar8Color;
198 				case 8:		return PixelFormat.RGB888;
199 				default:	break;
200 			}
201 		} else if (header.numBitPlanes == 4) {
202 			switch (header.bitsPerPixel) {
203 				case 1:		return PixelFormat.Planar16Color;
204 				case 8:		return PixelFormat.RGBA8888;
205 				default:	break;
206 			}
207 		}
208 		return PixelFormat.Undefined;
209 	}
210 	override uint getPalettePixelFormat() @nogc @safe @property const pure {
211 		return isIndexed() ? PixelFormat.RGB888 : PixelFormat.Undefined;
212 	}
213 	/**
214 	 * Returns the number of planes the image have.
215 	 * If bitdepth is 1, then the image is a planar indexed image.
216 	 */
217 	override public ubyte getBitplanes() @nogc @safe @property const pure {
218 		return header.bitsPerPixel == 1 ? header.numBitPlanes : 1;
219 	}
220 }