#pragma region CPL License /* Nuclex Native Framework Copyright (C) 2002-2021 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License // If the library is compiled as a DLL, this ensures symbols are exported #define NUCLEX_PIXELS_SOURCE 1 #include "TiffBitmapCodec.h" #if defined(NUCLEX_PIXELS_HAVE_LIBTIFF) #include "Nuclex/Pixels/Storage/VirtualFile.h" #include "Nuclex/Pixels/Errors/FileFormatError.h" #include "LibTiffHelpers.h" #include "Nuclex/Support/ScopeGuard.h" // for ScopeGuard #include // for std::uint32_t etc. // A TIFF file has a directory and can store multiple independent images in it. // We only read the first image in a TIFF file because multi-image TIFFs are // very rarely used (and designed in poor taste, as external containers like // .tar could have covered this without adding complexity and bloat to TIFF). // // Next, image data can be stored in strips or tiles and on top of that either // with interleaved or separated color channels: // // Chunks (strips or tiles): // * strips - The TIFF is stored (and compressed) in vertical strips. // Each strip contains one or more rows of the image. // * tiles - The TIFF is stored (and compressed) in rectangular tiles. // Reading the whole image requires reading all tiles. // // Color channels (contiguous/packed or separate/unpacked): // * contiguous - A strip or tile contains all color channels in interleaved // format (i.e. R-G-B-A R-G-B-A R-G-B-A) // * separate - A strip of tile contains all color values for the first channel, // then the next channel and so on (R-R-R | G-G-G | B-B-B | A-A-A) // // Add to that a bunch of historically grown conventions, a zoo of color models, // attempts to bloat the format even further by putting JPEG compression into it // and you end up with a complex decoder with many different code paths to take. // // Rewriting this correctly would be a lot of effort. On the other hand, decoding // the whole image into a full-sized RGBA buffer before doing the pixel format // conversion would waste a lot of memory. // // Luckily there's a way around that: we can make use of one of LibTIFF's // lesser-documented features: using custom 'put' functions that are supposed to // copy decoded pixels into the pixel buffer. We snatch the function pointer and // wrap it with our own method that does the pixel format conversion for us. // namespace { // ------------------------------------------------------------------------------------------- // struct CustomTIFFRGBAImage : ::TIFFRGBAImage { public: ::tileContigRoutine originalPackedPixelDecoder; public: ::tileSeparateRoutine originalSplitColorChannelDecoder; }; // ------------------------------------------------------------------------------------------- // /// LibTIFF 'put' function to write pixels with interleaved color channels /// Informations needed to decode the image pixels /// Target pixel buffer /// X coordinate the current strip or tile starts at /// Y coordinate the current strip or tile starts at /// Width of the current strip or tile /// Height of the current strip or tile /// /// Number of bytes between the end of one row and the start of the next in /// the source pixel buffer /// /// /// Number of 32 bit integers between the end of one row and the start of the next in /// the target pixel buffer /// /// /// Buffer holding the source pixels using the color model they are stored in /// void putPackedPixels( TIFFRGBAImage *decoderState, std::uint32_t *targetPixels, std::uint32_t x, std::uint32_t y, std::uint32_t w, std::uint32_t h, std::int32_t sourceSkew, std::int32_t targetSkew, std::uint8_t *sourcePixels ) {} // ------------------------------------------------------------------------------------------- // /// LibTIFF 'put' function to write pixels with interleaved color channels /// Informations needed to decode the image pixels /// Target pixel buffer /// X coordinate the current strip or tile starts at /// Y coordinate the current strip or tile starts at /// Width of the current strip or tile /// Height of the current strip or tile /// /// Number of bytes between the end of one row and the start of the next in any of /// the source color channel buffers /// /// /// Number of 32 bit integers between the end of one row and the start of the next in /// the target pixel buffer /// /// /// Buffer holding the red values of all pixels in the strip or tile /// /// /// Buffer holding the green values of all pixels in the strip or tile /// /// /// Buffer holding the blue values of all pixels in the strip or tile /// /// /// Buffer holding the alpha values of all pixels in the strip or tile /// void putSplitColorChannelPixels( TIFFRGBAImage *rgbaImage, std::uint32_t *targetPixels, std::uint32_t x, std::uint32_t y, std::uint32_t w, std::uint32_t h, std::int32_t sourceSkew, std::int32_t targetSkew, std::uint8_t *sourceRedValues, std::uint8_t *sourceGreenValues, std::uint8_t *sourceBlueValues, std::uint8_t *sourceAlphaValues ) {} // ------------------------------------------------------------------------------------------- // } // anonymous namespace namespace Nuclex { namespace Pixels { namespace Storage { namespace Tiff { // ------------------------------------------------------------------------------------------- // // ------------------------------------------------------------------------------------------- // std::optional TiffBitmapCodec::TryLoad( const VirtualFile &source, const std::string &extensionHint /* = std::string() */ ) const { (void)extensionHint; TIFF *tiffFile = Helpers::OpenForReading(source); ON_SCOPE_EXIT { Nuclex::Pixels::Storage::Tiff::Helpers::Close(tiffFile); }; PixelFormat targetPixelFormat = Helpers::GetClosestPixelFormat(tiffFile); std::uint32_t width, height; { int result = ::TIFFGetField(tiffFile, TIFFTAG_IMAGEWIDTH, &width); if(result == 0) { throw Errors::FileFormatError(u8"TIFF file has no image width tag. Corrupt file?"); } result = ::TIFFGetField(tiffFile, TIFFTAG_IMAGELENGTH, &height); if(result == 0) { throw Errors::FileFormatError(u8"TIFF file has no image height tag. Corrupt file?"); } } Bitmap loadedBitmap(width, height, targetPixelFormat); { CustomTIFFRGBAImage decoderState; int stopOnError = 1; char errorMessageBuffer[1024]; errorMessageBuffer[0] = 0; int result = ::TIFFRGBAImageBegin( &decoderState, tiffFile, stopOnError, errorMessageBuffer ); if(result == 0) { if(errorMessageBuffer[0] != 0) { std::string errorMessage; errorMessage.reserve(35); errorMessage.assign(u8"Error setting up TIFF file reader: ", 35); errorMessage.append(errorMessageBuffer); throw Errors::FileFormatError(errorMessage); } else { throw Errors::FileFormatError(u8"Error setting up TIFF file reader. Corrupt file?"); } } ON_SCOPE_EXIT { ::TIFFRGBAImageEnd(&decoderState); }; if(decoderState.isContig) { decoderState.originalPackedPixelDecoder = decoderState.put.contig; decoderState.put.contig = &putPackedPixels; } else { decoderState.originalSplitColorChannelDecoder = decoderState.put.separate; decoderState.put.separate = &putSplitColorChannelPixels; } } return std::optional(std::move(loadedBitmap)); } // ------------------------------------------------------------------------------------------- // bool TiffBitmapCodec::TryReload( Bitmap &exactlyFittingBitmap, const VirtualFile &source, const std::string &extensionHint /* = std::string() */ ) const { (void)exactlyFittingBitmap; (void)source; (void)extensionHint; throw std::runtime_error(u8"Not implemented yet"); } // ------------------------------------------------------------------------------------------- // }}}} // namespace Nuclex::Pixels::Storage::Tiff #endif //defined(NUCLEX_PIXELS_HAVE_LIBTIFF)