/* Copyright 2015-2016 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF ANY PROPRIETARY RIGHTS. * * In no event and under no legal theory, whether in tort (including negligence), * contract, or otherwise, unless required by applicable law (such as deliberate * and grossly negligent acts) or agreed to in writing, shall any Contributor be * liable for any damages, including any direct, indirect, special, incidental, * or consequential damages of any character arising as a result of this License or * out of the use or inability to use the software (including but not limited to damages * for loss of goodwill, work stoppage, computer failure or malfunction, or any and * all other commercial damages or losses), even if such Contributor has been advised * of the possibility of such damages. */ #include "pch.h" #include "Image.h" #include "Errors.h" #include "tiffio.h" #include "png.h" #include "jpeglib.h" #include "DataBlobImpl.h" #include "DebugUtilities.h" #include "RefCntAutoPtr.h" using namespace Diligent; using namespace Diligent; namespace Diligent { class TIFFClientOpenWrapper { public: TIFFClientOpenWrapper( IDataBlob *pData ) : m_pData( pData ), m_Offset( 0 ), m_Size( pData->GetSize() ) { } static tmsize_t TIFFReadProc( thandle_t pClientData, void* pBuffer, tmsize_t Size ) { auto *pThis = reinterpret_cast(pClientData); auto *pSrcPtr = reinterpret_cast(pThis->m_pData->GetDataPtr()) + pThis->m_Offset; memcpy( pBuffer, pSrcPtr, Size ); pThis->m_Offset += Size; return Size; } static tmsize_t TIFFWriteProc( thandle_t pClientData, void* pBuffer, tmsize_t Size ) { auto *pThis = reinterpret_cast(pClientData); if( pThis->m_Offset + Size > pThis->m_Size ) { pThis->m_Size = pThis->m_Offset + Size; pThis->m_pData->Resize( pThis->m_Size ); } auto *pDstPtr = reinterpret_cast(pThis->m_pData->GetDataPtr()) + pThis->m_Offset; memcpy( pDstPtr, pBuffer, Size ); pThis->m_Offset += Size; return Size; } static toff_t TIFFSeekProc( thandle_t pClientData, toff_t Offset, int Whence ) { auto *pThis = reinterpret_cast(pClientData); switch( Whence ) { case SEEK_SET: pThis->m_Offset = static_cast(Offset); break; case SEEK_CUR: pThis->m_Offset += static_cast(Offset); break; case SEEK_END: pThis->m_Offset = pThis->m_Size + static_cast(Offset); break; default: UNEXPECTED( "Unexpected whence" ); } return pThis->m_Offset; } static int TIFFCloseProc( thandle_t pClientData ) { auto *pThis = reinterpret_cast(pClientData); pThis->m_pData.Release(); pThis->m_Size = 0; pThis->m_Offset = 0; return 0; } static toff_t TIFFSizeProc( thandle_t pClientData ) { auto *pThis = reinterpret_cast(pClientData); return pThis->m_Size; } static int TIFFMapFileProc( thandle_t pClientData, void** base, toff_t* size ) { UNEXPECTED( "Client file mapping is not implemented. Use \'m\' when opening TIFF file to disable file mapping." ) return 0; } static void TIFFUnmapFileProc( thandle_t pClientData, void* base, toff_t size ) { UNEXPECTED( "Client file mapping is not implemented. Use \'m\' when opening TIFF file to disable file mapping." ) } private: size_t m_Offset; size_t m_Size; RefCntAutoPtr m_pData; }; void Image::LoadTiffFile( IDataBlob *pFileData, const ImageLoadInfo& LoadInfo ) { TIFFClientOpenWrapper TiffClientOpenWrpr(pFileData); auto TiffFile = TIFFClientOpen("", "rm", &TiffClientOpenWrpr, TIFFClientOpenWrapper::TIFFReadProc, TIFFClientOpenWrapper::TIFFWriteProc, TIFFClientOpenWrapper::TIFFSeekProc, TIFFClientOpenWrapper::TIFFCloseProc, TIFFClientOpenWrapper::TIFFSizeProc, TIFFClientOpenWrapper::TIFFMapFileProc, TIFFClientOpenWrapper::TIFFUnmapFileProc); TIFFGetField(TiffFile, TIFFTAG_IMAGEWIDTH, &m_Desc.Width); TIFFGetField(TiffFile, TIFFTAG_IMAGELENGTH, &m_Desc.Height); Uint16 SamplesPerPixel = 0; // SamplesPerPixel is usually 1 for bilevel, grayscale, and palette-color images. // SamplesPerPixel is usually 3 for RGB images. If this value is higher, ExtraSamples // should give an indication of the meaning of the additional channels. TIFFGetField(TiffFile, TIFFTAG_SAMPLESPERPIXEL, &SamplesPerPixel); m_Desc.NumComponents = SamplesPerPixel; Uint16 BitsPerSample = 0; TIFFGetField(TiffFile, TIFFTAG_BITSPERSAMPLE, &BitsPerSample); m_Desc.BitsPerPixel = m_Desc.NumComponents * BitsPerSample; auto ScanlineSize = TIFFScanlineSize(TiffFile); m_Desc.RowStride = static_cast( ScanlineSize ); m_pData->Resize(m_Desc.Height * m_Desc.RowStride ); auto *pDataPtr = reinterpret_cast( m_pData->GetDataPtr() ); for (Uint32 row = 0; row < m_Desc.Height; row++, pDataPtr += ScanlineSize) { TIFFReadScanline(TiffFile, pDataPtr, row); } TIFFClose(TiffFile); } class PNGReadFnHelper { public: PNGReadFnHelper( IDataBlob *pData ) : m_pData( pData ), m_Offset( 0 ) { } static void ReadData( png_structp pngPtr, png_bytep data, png_size_t length ) { auto pThis = reinterpret_cast( png_get_io_ptr(pngPtr) ); memcpy( data, reinterpret_cast(pThis->m_pData->GetDataPtr()) + pThis->m_Offset, length ); pThis->m_Offset += length; } private: RefCntAutoPtr m_pData; size_t m_Offset; }; void Image::LoadPngFile( IDataBlob *pFileData, const ImageLoadInfo& LoadInfo ) { // http://www.piko3d.net/tutorials/libpng-tutorial-loading-png-files-from-streams/ // http://www.libpng.org/pub/png/book/chapter13.html#png.ch13.div.10 // https://gist.github.com/niw/5963798\ PNGReadFnHelper ReadFnHelper(pFileData); const size_t PngSigSize = 8; png_const_bytep pngsig = reinterpret_cast(pFileData->GetDataPtr()); //Let LibPNG check the signature. If this function returns 0, everything is OK. if( png_sig_cmp( pngsig, 0, PngSigSize ) != 0 ) { LOG_ERROR_AND_THROW( "Invalid png signature" ); } png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); VERIFY(png, "png_create_read_struct() failed") png_infop info = png_create_info_struct(png); VERIFY(info, "png_create_info_struct() failed") if( setjmp( png_jmpbuf( png ) ) ) { // When an error occurs during parsing, libPNG will jump to here png_destroy_read_struct(&png, &info, (png_infopp)0); LOG_ERROR_AND_THROW( "Failed to read png file" ); } png_set_read_fn(png, (png_voidp)&ReadFnHelper, PNGReadFnHelper::ReadData); png_read_info(png, info); m_Desc.Width = png_get_image_width(png, info); m_Desc.Height = png_get_image_height(png, info); m_Desc.NumComponents = png_get_channels(png, info); auto bit_depth = png_get_bit_depth(png, info); m_Desc.BitsPerPixel = bit_depth * m_Desc.NumComponents; auto color_type = png_get_color_type(png, info); // PNG files store 16-bit pixels in network byte order (big-endian, ie // most significant bytes first). png_set_swap() shall switch the byte-order // to little-endian (ie, least significant bits first). if( bit_depth == 16 ) png_set_swap(png); #if 0 // Read any color_type into 8bit depth, RGBA format. // See http://www.libpng.org/pub/png/libpng-manual.txt if( bit_depth == 16 ) png_set_strip_16( png ); if( color_type == PNG_COLOR_TYPE_PALETTE ) png_set_palette_to_rgb( png ); // PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth. if( color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8 ) png_set_expand_gray_1_2_4_to_8( png ); if( png_get_valid( png, info, PNG_INFO_tRNS ) ) png_set_tRNS_to_alpha( png ); // These color_type don't have an alpha channel then fill it with 0xff. if( color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE ) png_set_filler( png, 0xFF, PNG_FILLER_AFTER ); if( color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA ) png_set_gray_to_rgb( png ); png_read_update_info( png, info ); #endif //Array of row pointers. One for every row. std::vector rowPtrs(m_Desc.Height); //Alocate a buffer with enough space. m_Desc.RowStride = m_Desc.Width * bit_depth * m_Desc.NumComponents / 8; m_pData->Resize( m_Desc.Height * m_Desc.RowStride ); for( size_t i = 0; i < m_Desc.Height; i++ ) rowPtrs[i] = reinterpret_cast(m_pData->GetDataPtr()) + i * m_Desc.RowStride; //Read the imagedata and write it to the adresses pointed to //by rowptrs (in other words: our image databuffer) png_read_image( png, rowPtrs.data() ); png_destroy_read_struct(&png, &info, (png_infopp)0); } struct my_jpeg_error_mgr { jpeg_error_mgr pub; jmp_buf setjmp_buffer;// for return to caller }; // Here's the routine that will replace the standard error_exit method: METHODDEF(void) my_error_exit (j_common_ptr cinfo) { // cinfo->err really points to a my_jpeg_error_mgr struct, so coerce pointer my_jpeg_error_mgr* myerr = (my_jpeg_error_mgr*) cinfo->err; /* Always display the message. */ /* We could postpone this until after returning, if we chose. */ (*cinfo->err->output_message) (cinfo); // Return control to the setjmp point longjmp(myerr->setjmp_buffer, 1); } void Image::LoadJpegFile( IDataBlob *pFileData, const ImageLoadInfo& LoadInfo ) { // https://github.com/LuaDist/libjpeg/blob/master/example.c // This struct contains the JPEG decompression parameters and pointers to // working space (which is allocated as needed by the JPEG library). jpeg_decompress_struct cinfo; // We use our private extension JPEG error handler. // Note that this struct must live as long as the main JPEG parameter // struct, to avoid dangling-pointer problems. my_jpeg_error_mgr jerr; // Step 1: allocate and initialize JPEG decompression object // We set up the normal JPEG error routines, then override error_exit. cinfo.err = jpeg_std_error( &jerr.pub ); jerr.pub.error_exit = my_error_exit; // Establish the setjmp return context for my_error_exit to use. if( setjmp( jerr.setjmp_buffer ) ) { // If we get here, the JPEG code has signaled an error. // We need to clean up the JPEG object, close the input file, and return. jpeg_destroy_decompress( &cinfo ); LOG_ERROR_AND_THROW( "Failed to decompress JPEG image" ) } // Now we can initialize the JPEG decompression object. jpeg_create_decompress( &cinfo ); // Step 2: specify data source jpeg_mem_src( &cinfo, reinterpret_cast(pFileData->GetDataPtr()), static_cast(pFileData->GetSize()) ); // Step 3: read file parameters with jpeg_read_header() jpeg_read_header( &cinfo, TRUE ); // We can ignore the return value from jpeg_read_header since // (a) suspension is not possible with the stdio data source, and // (b) we passed TRUE to reject a tables-only JPEG file as an error. // See libjpeg.txt for more info. // Step 4: set parameters for decompression // In this example, we don't need to change any of the defaults set by // jpeg_read_header(), so we do nothing here. // Step 5: Start decompressor jpeg_start_decompress( &cinfo ); // We can ignore the return value since suspension is not possible // with the stdio data source. // We may need to do some setup of our own at this point before reading // the data. After jpeg_start_decompress() we have the correct scaled // output image dimensions available, as well as the output colormap // if we asked for color quantization. m_Desc.Width = cinfo.output_width; m_Desc.Height = cinfo.output_height; m_Desc.NumComponents = cinfo.output_components; m_Desc.RowStride = m_Desc.Width * m_Desc.NumComponents; m_Desc.BitsPerPixel = 8 * m_Desc.NumComponents; m_pData->Resize(m_Desc.RowStride * m_Desc.Height); // Step 6: while (scan lines remain to be read) // jpeg_read_scanlines(...); // Here we use the library's state variable cinfo.output_scanline as the // loop counter, so that we don't have to keep track ourselves. while( cinfo.output_scanline < cinfo.output_height ) { // jpeg_read_scanlines expects an array of pointers to scanlines. // Here the array is only one element long, but you could ask for // more than one scanline at a time if that's more convenient. auto *pDstScanline = reinterpret_cast( m_pData->GetDataPtr() ) + cinfo.output_scanline * m_Desc.RowStride; JSAMPROW RowPtrs[] = { reinterpret_cast(pDstScanline) }; jpeg_read_scanlines( &cinfo, RowPtrs, 1 ); } // Step 7: Finish decompression jpeg_finish_decompress( &cinfo ); // We can ignore the return value since suspension is not possible // with the stdio data source. // Step 8: Release JPEG decompression object // This is an important step since it will release a good deal of memory. jpeg_destroy_decompress( &cinfo ); // At this point you may want to check to see whether any corrupt-data // warnings occurred (test whether jerr.pub.num_warnings is nonzero). } Image::Image( Diligent::IFileStream *pSrcFile, const ImageLoadInfo& LoadInfo ) : m_pData( new Diligent::DataBlobImpl ) { RefCntAutoPtr pFileData( new Diligent::DataBlobImpl ); pSrcFile->Read(pFileData); if( LoadInfo.Format == EImageFileFormat::tiff ) { LoadTiffFile(pFileData, LoadInfo ); } else if( LoadInfo.Format == EImageFileFormat::png ) { LoadPngFile(pFileData, LoadInfo ); } else if( LoadInfo.Format == EImageFileFormat::jpeg ) { LoadJpegFile(pFileData, LoadInfo ); } } }