1 /*
2  * dimage - png.d
3  * by Laszlo Szeremi
4  *
5  * Copyright under Boost Software License.
6  */
7 
8 module dimage.tiff;
9 
10 import dimage.base;
11 import bitleveld.datatypes;
12 import dimage.util;
13 import std.bitmanip;
14 
15 static import std.stdio;
16 
17 /**
18  * Implements *.TIFF file handling.
19  * LZW compression support requires linking abainst ncompress42.
20  * JPEG support requires a codec of some sort.
21  */
22 public class TIFF : Image, MultiImage, ImageMetadata {
23 	/**
24 	 * Byte order identification for header.
25 	 * LE means little, BE means big endianness
26 	 */
27 	public enum ByteOrderIdentifier : ushort {
28 		LE		=	0x4949,
29 		BE		=	0x4D4D,
30 	}
31 	/**
32 	 * Standard TIFF header. Loaded as a bytestream at once.
33 	 */
34 	public struct Header {
35 	align(1):
36 		ushort		identifier;		///Byte order identifier.
37 		ushort		vers = 0x2A;	///Version number.
38 		uint		offset;			///Offset of first image file directory.
39 		/**
40 		 * Switches endianness if needed.
41 		 * Also changes identifier to the host machine's native endianness to avoid problems from conversion.
42 		 */
43 		void switchEndian() {
44 			version (LittleEndian) {
45 				if (identifier == ByteOrderIdentifier.BE) {
46 					identifier = ByteOrderIdentifier.LE;
47 					vers = swapEndian(vers);
48 					offset = swapEndian(offset);
49 				}
50 			} else {
51 				if (identifier == ByteOrderIdentifier.LE) {
52 					identifier = ByteOrderIdentifier.BE;
53 					vers = swapEndian(vers);
54 					offset = swapEndian(offset);
55 				}
56 			}
57 		}
58 	}
59 	/**
60 	 * TIFF Image File Directory. Loaded manually due to the nature of this field.
61 	 */
62 	public struct IFD {
63 		/**
64 		 * TIFF data types.
65 		 */
66 		public enum DataType : ushort {
67 			NULL		=	0,
68 			BYTE		=	1,	///unsigned byte
69 			ASCII		=	2,	///null terminated string, ASCII
70 			SHORT		=	3,	///unsigned short
71 			LONG		=	4,	///unsigned int
72 			RATIONAL	=	5,	///two unsigned ints
73 
74 			SBYTE		=	6,	///signed byte
75 			UNDEFINED	=	7,	///byte
76 			SSHORT		=	8,	///signed short
77 			SLONG		=	9,	///signed int
78 			SRATIONAL	=	10,	///two signed ints
79 			FLOAT		=	11,	///IEEE single-precision floating-point
80 			DOUBLE		=	12,	///IEEE double-precision floating-point
81 		}
82 		/**
83 		 * Common TIFF entry identifiers.
84 		 */
85 		public enum DataEntryID : ushort {
86 			Artist			=	315,
87 			BadFaxLines		=	326,
88 			BitsPerSample	=	258,
89 			CellLength		=	265,
90 			CellWidth		=	264,
91 			CleanFaxData	=	327,
92 			ColorMap		=	320,
93 			ColorResponseCurve	=	301,
94 			ColorResponseUnit	=	300,
95 			Compression		=	259,
96 			ConsecuitiveBadFaxLines	=	328,
97 			Copyright		=	33_432,
98 			DateTime		=	306,
99 			DocumentName	=	269,
100 			DotRange		=	336,
101 			ExtraSamples	=	338,
102 			FillOrder		=	266,
103 			FreeByteCounts	=	289,
104 			FreeOffsets		=	288,
105 			GrayResponseCurve	=	291,
106 			GrayResponseUnit	=	290,
107 			HalftoneHints	=	321,
108 			HostComputer	=	316,
109 			ImageDescription=	270,
110 			ImageHeight		=	257,
111 			ImageWidth		=	256,
112 			InkNames		=	333,
113 			InkSet			=	332,
114 			JPEGACTTables	=	521,
115 			JPEGDCTTables	=	520,
116 			JPEGInterchangeFormat	=	513,
117 			JPEGInterchangeFormatLength	=	514,
118 			JPEGLosslessPredictors	=	517,
119 			JPEGPointTransforms	=	518,
120 			JPEGProc		=	512,
121 			JPEGRestartInterval	=	515,
122 			JPEGQTables		=	519,
123 			Make			=	271,
124 			MaxSampleValue	=	281,
125 			MinSampleValue	=	280,
126 			Model			=	272,
127 			NewSubFileType	=	254,
128 			NumberOfInks	=	334,
129 			Orientation		=	274,
130 			PageName		=	285,
131 			PageNumber		=	297,
132 			PhotometricInterpretation	=	262,
133 			WhiteIsZero		=	0,
134 			BlackIsZero		=	1,
135 			RGB				=	2,
136 			RGBPalette		=	3,
137 			TransparencyMask=	4,
138 			CMYK			=	5,
139 			YCbCr			=	6,
140 			CIELab			=	8,
141 			PlanarConfiguration	=	284,
142 			Predictor		=	317,
143 			PrimaryChromaticities	=	319,
144 			ReferenceBlackWhite	=	532,
145 			ResolutionUnit	=	296,
146 			RowsPerStrip	=	278,
147 			SampleFormat	=	339,
148 			SamplesPerPixel	=	277,
149 			SMaxSampleValue	=	341,
150 			SMinSampleValue	=	340,
151 			Software		=	305,
152 			StripByteCounts	=	279,
153 			StripOffsets	=	273,
154 			SubFileType		=	255,
155 			T4Options		=	292,
156 			T6Options		=	293,
157 			TargetPrinter	=	337,
158 			Thresholding	=	263,
159 			TileByteCounts	=	325,
160 			TileLength		=	323,
161 			TileOffsets		=	324,
162 			TileWidth		=	322,
163 			TransferFunction=	301,
164 			TransferRange	=	342,
165 			XPosition		=	286,
166 			XResolution		=	282,
167 			YCbCrCoefficients	=	529,
168 			YCbCrPositioning=	531,
169 			YCbCrSubSampling=	530,
170 			YPosition		=	287,
171 			YResolution		=	283,
172 			WhitePoint		=	318,
173 		}
174 		/**
175 		 * TIFF tag. Loaded as bytestream at once.
176 		 */
177 		public struct Tag {
178 		align(1):
179 			ushort		tagID;			///Tag identifier.
180 			ushort		dataType;		///Type of the data items.
181 			uint		dataCount;		///The amount of data stored within this tag.
182 			uint		dataOffset;		///The offset of data in bytes.	
183 
184 			void switchEndian() @nogc @safe pure nothrow {
185 				tagID = swapEndian(tagID);
186 				dataType = swapEndian(dataType);
187 				dataCount = swapEndian(dataCount);
188 				dataOffset = swapEndian(dataOffset);
189 			}
190 			ubyte[] switchEndianOfData(ubyte[] data) @safe pure {
191 				switch(dataType){
192 					case DataType.SHORT, DataType.SSHORT:
193 						ushort[] workPad = reinterpretCast!ushort(data);
194 						for (size_t i ; i < workPad.length ; i++)
195 							workPad[i] = swapEndian(workPad[i]);
196 						return reinterpretCast!ubyte(workPad);
197 					case DataType.LONG, DataType.SLONG, DataType.FLOAT, DataType.RATIONAL, DataType.SRATIONAL:
198 						uint[] workPad = reinterpretCast!uint(data);
199 						for (size_t i ; i < workPad.length ; i++)
200 							workPad[i] = swapEndian(workPad[i]);
201 						return reinterpretCast!ubyte(workPad);
202 					case DataType.DOUBLE:
203 						ulong[] workPad = reinterpretCast!ulong(data);
204 						for (size_t i ; i < workPad.length ; i++)
205 							workPad[i] = swapEndian(workPad[i]);
206 						return reinterpretCast!ubyte(workPad);
207 					default: return [];
208 				}
209 			}
210 			@property size_t dataSize() @nogc @safe pure nothrow const {
211 				switch(dataType){
212 					case DataType.SHORT, DataType.SSHORT:
213 						return dataCount * 2;
214 					case DataType.LONG, DataType.SLONG, DataType.FLOAT:
215 						return dataCount * 4;
216 					case DataType.RATIONAL, DataType.SRATIONAL, DataType.DOUBLE:
217 						return dataCount * 8;
218 					default:
219 						return dataCount;
220 				}
221 			}
222 		}
223 	align(1):
224 		ushort			numDirEntries;	///Number of entries.
225 		Tag[]			tagList;		///List of tags in this field.
226 		ubyte[][]		tagData;		///Stores each datafields.
227 		uint			nextOffset;		///Offset of next IFD
228 
229 		/**
230 		 * Returns the first instance of a given tag if exists, or returns uint.max if not.
231 		 */
232 		uint getTagNum(ushort tagID) @nogc @safe nothrow pure const {
233 			foreach (size_t key, Tag elem; tagList) {
234 				if (elem.tagID == tagID) return cast(uint)key;
235 			}
236 			return uint.max;
237 		}
238 	}
239 	protected Header	header;				///TIFF file header.
240 	protected IFD[]		directoryEntries;	///Image data.
241 	protected uint		currentImg;			///Current image selected with the MultiImage interface's functions.
242 	protected uint		_width, _height;	///Sizes of the current image
243 	protected ubyte		_bitdepth, _palbitdepth;	///Bitdepths of the current image
244 	protected uint		_pixelFormat, _palettePixelFormat;	///Pixelformat of the current image
245 	///CTOR for loader
246 	protected this() @nogc @safe pure nothrow {}
247 	
248 	/** 
249 	 * Loads a TIFF file from either disk or memory.
250 	 */
251 	public static TIFF load(FILE = std.stdio.File, bool keepJPEG = false)(ref FILE file) {
252 		TIFF result = new TIFF();
253 		ubyte[] buffer;
254 		buffer.length = Header.sizeof;
255 		buffer = file.rawRead(buffer);
256 		if(buffer.length != Header.sizeof) throw new ImageFileException("File doesn't contain TIFF header!");
257 		result.header = reinterpretGet!Header(buffer);
258 		result.header.switchEndian();
259 		if(!result.header.offset) throw new ImageFileException("File doesn't contain any images!");
260 		size_t pos = result.header.offset;
261 		
262 		//Load Image Data
263 		while(pos) {
264 			file.seek(pos, std.stdio.SEEK_CUR);
265 			buffer.length = ushort.sizeof;
266 			IFD entry;
267 			buffer = file.rawRead(buffer);
268 			if(buffer.length != ushort.sizeof) throw new ImageFileException("File access error or missing parts!");
269 			version (LittleEndian){
270 				if(result.header.identifier == ByteEndianness.BE) entry.numDirEntries = swapEndian(reinterpretGet!ushort(buffer));
271 			} else {
272 				if(result.header.identifier == ByteEndianness.LE) entry.numDirEntries = swapEndian(reinterpretGet!ushort(buffer));
273 			}
274 			IFD.tagData.length = entry.numDirEntries;
275 			for (ushort i ; i < entry.numDirEntries ; i++){
276 				buffer.length = IFD.Tag.sizeof;
277 				buffer = file.rawRead(buffer);
278 				if(buffer.length == IFD.Tag.sizeof) 
279 					throw new ImageFileException("File access error or missing parts!");
280 				IFD.Tag tag = reinterpretCast!IFD.Tag(buffer);
281 				version(LittleEndian) {
282 					if(result.header.identifier == ByteOrderIdentifier.BE) {
283 						tag.switchEndian();
284 					}
285 				} else {
286 					if(result.header.identifier == ByteOrderIdentifier.LE) {
287 						tag.switchEndian();
288 					}
289 				}
290 				if(tag.dataSize > 4) {
291 					file.seek(tag.dataOffset, std.stdio.SEEK_CUR);
292 					buffer.length = tag.dataSize;
293 					buffer = file.rawRead(buffer);
294 					if(buffer.length == tag.dataSize) 
295 						throw new ImageFileException("File access error or missing parts!");
296 					version (LittleEndian) {
297 						if (result.header.identifier == ByteEndianness.LE) entry.tagData[i] = buffer;
298 						else entry.tagData[i] = tag.switchEndianOfData(buffer);
299 					} else {
300 						if (result.header.identifier == ByteEndianness.BE) entry.tagData[i] = buffer;
301 						else entry.tagData[i] = tag.switchEndianOfData(buffer);
302 					}
303 					file.seek((tag.dataOffset + tag.dataSize) * -1, std.stdio.SEEK_CUR);
304 				} else {
305 					entry.tagData[i] = reinterpretAsArray!ubyte(tag.dataOffset);
306 					entry.tagData[i].length = tag.dataSize;
307 					version (LittleEndian) {
308 						if (result.header.identifier == ByteEndianness.BE) entry.tagData[i] = tag.switchEndianOfData(entry.tagData[i]);
309 					} else {
310 						if (result.header.identifier == ByteEndianness.LE) entry.tagData[i] = tag.switchEndianOfData(entry.tagData[i]);
311 					}
312 				}
313 				entry.tagList ~= tag;
314 			}
315 			//stitch image together if uncompressed, or decompress if needed.
316 			
317 			buffer.length = uint.sizeof;
318 			buffer = file.rawRead(buffer);
319 			if(buffer.length != uint.sizeof) throw new ImageFileException("File access error or missing parts!");
320 			//entry.nextOffset = reinterpretGet!uint(buffer);
321 			version (LittleEndian){
322 				if(result.header.identifier == ByteEndianness.LE) entry.nextOffset = reinterpretGet!uint(buffer);
323 				else entry.nextOffset = swapEndian(reinterpretGet!uint(buffer));
324 			} else {
325 				if(result.header.identifier == ByteEndianness.BE) entry.nextOffset = reinterpretGet!uint(buffer);
326 				else entry.nextOffset = swapEndian(reinterpretGet!uint(buffer));
327 			}
328 			pos = entry.nextOffset;
329 			result.directoryEntries ~= entry;
330 		}
331 		
332 		return result;
333 	}
334 
335 	override uint width() @safe @property pure const {
336 		return _width;
337 	}
338 
339 	override uint height() @safe @property pure const {
340 		return _height;
341 	}
342 
343 	override bool isIndexed() @nogc @safe @property pure const {
344 		return _palbitdepth != 0;
345 	}
346 
347 	override ubyte getBitdepth() @nogc @safe @property pure const {
348 		return _bitdepth; 
349 	}
350 
351 	override ubyte getPaletteBitdepth() @nogc @safe @property pure const {
352 		if(isIndexed) return 48;
353 		return ubyte.init;
354 	}
355 
356 	override uint getPixelFormat() @nogc @safe @property pure const {
357 		return _pixelFormat;
358 	}
359 
360 	override uint getPalettePixelFormat() @nogc @safe @property pure const {
361 		if (isIndexed) return PixelFormat.RGB16_16_16;
362 		return uint.init; // TODO: implement
363 	}
364 
365 	public uint getCurrentImage() @safe pure {
366 		return currentImg; // TODO: implement
367 	}
368 
369 	public uint setCurrentImage(uint frame) @safe pure {
370 		return currentImg = frame; // TODO: implement
371 	}
372 	///Sets the current image to the static if available
373 	public void setStaticImage() @safe pure {
374 		
375 	}
376 	public uint nOfImages() @property @safe @nogc pure const {
377 		return cast(uint)directoryEntries.length;
378 	}
379 
380 	public uint frameTime() @property @safe @nogc pure const {
381 		return uint.init;
382 	}
383 
384 	public bool isAnimation() @property @safe @nogc pure const {
385 		return false;
386 	}
387 	
388 	public string getID() @safe pure {
389 		return string.init; // TODO: implement
390 	}
391 	
392 	public string getAuthor() @safe pure {
393 		return string.init; // TODO: implement
394 	}
395 	
396 	public string getComment() @safe pure {
397 		return string.init; // TODO: implement
398 	}
399 	
400 	public string getJobName() @safe pure {
401 		return string.init; // TODO: implement
402 	}
403 	
404 	public string getSoftwareInfo() @safe pure {
405 		return string.init; // TODO: implement
406 	}
407 	
408 	public string getSoftwareVersion() @safe pure {
409 		return string.init; // TODO: implement
410 	}
411 	
412 	public string getDescription() @safe pure {
413 		return string.init; // TODO: implement
414 	}
415 	
416 	public string getSource() @safe pure {
417 		return string.init; // TODO: implement
418 	}
419 	
420 	public string getCopyright() @safe pure {
421 		return string.init; // TODO: implement
422 	}
423 	
424 	public string getCreationTimeStr() @safe pure {
425 		return string.init; // TODO: implement
426 	}
427 	
428 	public string setID(string val) @safe pure {
429 		return string.init; // TODO: implement
430 	}
431 	
432 	public string setAuthor(string val) @safe pure {
433 		return string.init; // TODO: implement
434 	}
435 	
436 	public string setComment(string val) @safe pure {
437 		return string.init; // TODO: implement
438 	}
439 	
440 	public string setJobName(string val) @safe pure {
441 		return string.init; // TODO: implement
442 	}
443 	
444 	public string setSoftwareInfo(string val) @safe pure {
445 		return string.init; // TODO: implement
446 	}
447 	
448 	public string setSoftwareVersion(string val) @safe pure {
449 		return string.init; // TODO: implement
450 	}
451 	
452 	public string setDescription(string val) @safe pure {
453 		return string.init; // TODO: implement
454 	}
455 	
456 	public string setCopyright(string val) @safe pure {
457 		return string.init; // TODO: implement
458 	}
459 	
460 	public string setSource(string val) @safe pure {
461 		return string.init; // TODO: implement
462 	}
463 	
464 	public string setCreationTime(string val) @safe pure {
465 		return string.init; // TODO: implement
466 	}
467 }