#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 "PngBitmapCodec.h" #if defined(NUCLEX_PIXELS_HAVE_LIBPNG) #include "Nuclex/Pixels/Storage/VirtualFile.h" #include "Nuclex/Pixels/Errors/FileFormatError.h" #include "Nuclex/Pixels/Errors/WrongSizeError.h" #include "Nuclex/Pixels/PixelFormats/PixelFormatQuery.h" #include "Nuclex/Pixels/PixelFormats/PixelFormatConverter.h" #include "Nuclex/Support/ScopeGuard.h" #include "LibPngHelpers.h" namespace { // ------------------------------------------------------------------------------------------- // /// Handles an error occuring while a PNG is being read /// PNG main structure, unused /// Describes the error that has occurred /// /// /// libpng is a C library, but its error handling scheme expects this function to never /// return (either it calls abort() or longjmp()). To allow this, all memory libpng /// allocates must be tracked in the png_struct and there must be no open ends on /// the stack when the error handler is called. /// /// /// This gives us all the guarantees we need to fire a C++ exception right through /// libpng back to our original call site. /// /// void handlePngError(::png_struct *png, const char *errorMessage) { (void)png; throw Nuclex::Pixels::Errors::FileFormatError(errorMessage); } // ------------------------------------------------------------------------------------------- // /// Handles a warning being issues by libpng /// PNG main structure, unused /// Describes the warning, unused void handlePngWarning(::png_struct *png, const char *warningMessage) { (void)png; (void)warningMessage; } // ------------------------------------------------------------------------------------------- // /// Loads a .png file into a Bitmap's memory keeping the pixel format /// LibPNG read structure through which reading will take place /// LibPNG info structure required for some query functions /// /// Description of the bitmap memory layout the loaded file will be stored in /// void loadPngIntoBitmapMemoryDirect( ::png_struct &pngRead, const ::png_info &pngInfo, const Nuclex::Pixels::BitmapMemory &memory ) { (void)pngInfo; // unused, but still passed because I may want to check settings // Finally, build an array of row addresses for libpng and use it to load // the whole image in one call. This minimizes the number of method calls and // should be the most efficient method to get the pixels into the Bitmap. { std::vector<::png_byte *> rowAddresses; { rowAddresses.reserve(memory.Height); std::uint8_t *rowStartPointer = reinterpret_cast(memory.Pixels); for(std::size_t index = 0; index < memory.Height; ++index) { rowAddresses.push_back(rowStartPointer); rowStartPointer += memory.Stride; } } // Load entire bitmap. Error handling via assigned error handler (-> exceptions) ::png_read_image(&pngRead, rowAddresses.data()); } } // ------------------------------------------------------------------------------------------- // /// /// Loads a .png file into a Bitmap's memory, converting the pixel format on the fly /// /// LibPNG read structure through which reading will take place /// LibPNG info structure required for some query functions /// /// Pixel format as which LibPNG will load the .png file (usually determined by the call /// to ) // /// /// Description of the bitmap memory layout the loaded file will be stored in /// void loadPngIntoBitmapMemoryWithConversion( ::png_struct &pngRead, const ::png_info &pngInfo, Nuclex::Pixels::PixelFormat storagePixelFormat, const Nuclex::Pixels::BitmapMemory &memory ) { // Allocate memory for 1 row (we're converting the pixel format of the image // row by row, this should yield good performance without wasting megabytes of memory) std::vector rowBytes( Nuclex::Pixels::CountRequiredBytes(storagePixelFormat, memory.Width) ); { std::size_t pngRowByteCount = ::png_get_rowbytes(&pngRead, &pngInfo); if(pngRowByteCount > rowBytes.size()) { rowBytes.resize(pngRowByteCount); } } { using Nuclex::Pixels::PixelFormats::PixelFormatConverter; PixelFormatConverter::ConvertRowFunction *convertRow = ( PixelFormatConverter::GetRowConverter(storagePixelFormat, memory.PixelFormat) ); // Let LibPNG load the image successively row-by-row and convert each // row from the temporary buffer into the correct location in the Bitmap's memory std::uint8_t *targetRowStart = ( reinterpret_cast(memory.Pixels) ); for(std::size_t rowIndex = 0; rowIndex < memory.Height; ++rowIndex) { ::png_read_row(&pngRead, rowBytes.data(), nullptr); convertRow( rowBytes.data(), // + CountBitsPerPixel(storagePixelFormat), targetRowStart, // + CountBitsPerPixel(memory.PixelFormat), memory.Width ); targetRowStart += memory.Stride; } } } // ------------------------------------------------------------------------------------------- // } // anonymous namespace namespace Nuclex { namespace Pixels { namespace Storage { namespace Png { // ------------------------------------------------------------------------------------------- // std::optional PngBitmapCodec::TryLoad( const VirtualFile &source, const std::string &extensionHint /* = std::string() */ ) const { (void)extensionHint; // If this doesn't look like a .png file, bail out immediately if(!Helpers::CheckIfPngHeaderPresent(source)) { return std::optional(); } { // Allocate the main LibPNG structure. It contains all pointers to user-defined // functions (IO, error handling and custom chunk processing, etc.) ::png_struct *pngRead = ::png_create_read_struct( PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr ); if(pngRead == nullptr) { throw std::bad_alloc(); } ON_SCOPE_EXIT { ::png_destroy_read_struct(&pngRead, nullptr, nullptr); }; // Install a custom error handler function that simply throws a C++ exception. // LibPNG is one of the few C libraries designed to allow exceptions passing through // because it's based on setjmp()/longjmp(). ::png_set_error_fn(pngRead, nullptr, &handlePngError, &handlePngWarning); { // We also need the info structure. This is filled with all important informations // describing the image's dimensions, pixel format, palette, gamma etc. ::png_info *pngInfo = ::png_create_info_struct(pngRead); if(pngInfo == nullptr) { throw std::bad_alloc(); } ON_SCOPE_EXIT { ::png_destroy_info_struct(pngRead, &pngInfo); }; { // Install a custom read function. This is used to read data from the virtual // file. The read environment emulates a file cursor. PngReadEnvironment environment(*pngRead, source); // Now that we're ready to actually access the PNG file, // attempt to obtain the image's resolution, pixel format and so on ::png_read_info(pngRead, pngInfo); // Determine the pixel format used in the .png file (this will also configure // LibPNG to perform adjustment in case the native pixel format is not supported) PixelFormat storagePixelFormat = Helpers::SelectPixelFormatForLoad(*pngRead, *pngInfo); std::size_t width = ::png_get_image_width(pngRead, pngInfo); std::size_t height = ::png_get_image_height(pngRead, pngInfo); // Perform the actual load through the shared loading code // (since we can match the pixel format used for storage, this needs no conversion) Bitmap image(width, height, storagePixelFormat); loadPngIntoBitmapMemoryDirect(*pngRead, *pngInfo, image.Access()); return std::optional(std::move(image)); } // PngReadEnvironment scope } // pngInfo scope } // pngRead scope } // ------------------------------------------------------------------------------------------- // bool PngBitmapCodec::TryReload( Bitmap &exactlyFittingBitmap, const VirtualFile &source, const std::string &extensionHint /* = std::string() */ ) const { (void)extensionHint; // If this doesn't look like a .png file, bail out immediately if(!Helpers::CheckIfPngHeaderPresent(source)) { return false; } // Allocate the main LibPNG structure. It contains all pointers to user-defined // functions (IO, error handling and custom chunk processing, etc.) { ::png_struct *pngRead = ::png_create_read_struct( PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr ); if(pngRead == nullptr) { throw std::bad_alloc(); } ON_SCOPE_EXIT { ::png_destroy_read_struct(&pngRead, nullptr, nullptr); }; // Install a custom error handler function that simply throws a C++ exception. // LibPNG is one of the few C libraries designed to allow exceptions passing through // because it's based on setjmp()/longjmp(). ::png_set_error_fn(pngRead, nullptr, &handlePngError, &handlePngWarning); { // We also need the info structure. This is filled with all important informations // describing the image's dimensions, pixel format, palette, gamma etc. ::png_info *pngInfo = ::png_create_info_struct(pngRead); if(pngInfo == nullptr) { throw std::bad_alloc(); } ON_SCOPE_EXIT { ::png_destroy_info_struct(pngRead, &pngInfo); }; { // Install a custom read function. This is used to read data from the virtual // file. The read environment emulates a file cursor. PngReadEnvironment environment(*pngRead, source); // Now that we're ready to actually access the PNG file, // attempt to obtain the image's resolution, pixel format and so on ::png_read_info(pngRead, pngInfo); std::size_t width = ::png_get_image_width(pngRead, pngInfo); std::size_t height = ::png_get_image_height(pngRead, pngInfo); const BitmapMemory &memory = exactlyFittingBitmap.Access(); if((width != memory.Width) || (height != memory.Height)) { throw Errors::WrongSizeError( u8"Size of existing target Bitmap did not match the image file being loaded" ); } // Determine the pixel format used in the .png file (this will also configure // LibPNG to perform adjustment in case the native pixel format is not supported) PixelFormat storagePixelFormat = Helpers::SelectPixelFormatForLoad(*pngRead, *pngInfo); // Perform the actual load. If the pixel format of the provided bitmap matches // the pixel format of the .png file, we can do a direct load, otherwise we will // load the .png file row-by-row and convert the pixel format while copying. if(memory.PixelFormat == storagePixelFormat) { loadPngIntoBitmapMemoryDirect(*pngRead, *pngInfo, memory); } else { loadPngIntoBitmapMemoryWithConversion( *pngRead, *pngInfo, storagePixelFormat, memory ); } } // PngReadEnvironment scope } // pngInfo scope } // pngRead scope return true; } // ------------------------------------------------------------------------------------------- // }}}} // namespace Nuclex::Pixels::Storage::Png #endif //defined(NUCLEX_PIXELS_HAVE_LIBPNG)