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 }