diff --git a/src/JPEGView/ImageLoadThread.cpp b/src/JPEGView/ImageLoadThread.cpp index 6a7aec58..24d6abd3 100644 --- a/src/JPEGView/ImageLoadThread.cpp +++ b/src/JPEGView/ImageLoadThread.cpp @@ -11,8 +11,8 @@ #include "BasicProcessing.h" #include "dcraw_mod.h" #include "TJPEGWrapper.h" -#ifndef WINXP #include "PNGWrapper.h" +#ifndef WINXP #include "JXLWrapper.h" #include "HEIFWrapper.h" #include "AVIFWrapper.h" @@ -320,19 +320,14 @@ void CImageLoadThread::ProcessRequest(CRequestBase& request) { DeleteCachedAvifDecoder(); ProcessReadWEBPRequest(&rq); break; -#ifndef WINXP case IF_PNG: DeleteCachedGDIBitmap(); DeleteCachedWebpDecoder(); DeleteCachedJxlDecoder(); DeleteCachedAvifDecoder(); - if (CSettingsProvider::This().ForceGDIPlus()) { - DeleteCachedPngDecoder(); - ProcessReadGDIPlusRequest(&rq); - } else { - ProcessReadPNGRequest(&rq); - } + ProcessReadPNGRequest(&rq); break; +#ifndef WINXP case IF_JXL: DeleteCachedGDIBitmap(); DeleteCachedWebpDecoder(); @@ -650,7 +645,6 @@ void CImageLoadThread::ProcessReadWEBPRequest(CRequest * request) { #ifndef WINXP void CImageLoadThread::ProcessReadPNGRequest(CRequest* request) { - bool bSuccess = false; bool bUseCachedDecoder = false; const wchar_t* sFileName; sFileName = (const wchar_t*)request->FileName; @@ -668,7 +662,8 @@ void CImageLoadThread::ProcessReadPNGRequest(CRequest* request) { return; } } - char* pBuffer = NULL; + HGLOBAL hFileBuffer = NULL; + void* pBuffer = NULL; try { unsigned int nFileSize; unsigned int nNumBytesRead; @@ -676,28 +671,33 @@ void CImageLoadThread::ProcessReadPNGRequest(CRequest* request) { // Don't read too huge files nFileSize = ::GetFileSize(hFile, NULL); if (nFileSize > MAX_PNG_FILE_SIZE) { + request->OutOfMemory = true; ::CloseHandle(hFile); - return ProcessReadGDIPlusRequest(request); + return; } - - pBuffer = new(std::nothrow) char[nFileSize]; + hFileBuffer = ::GlobalAlloc(GMEM_MOVEABLE, nFileSize); + pBuffer = (hFileBuffer == NULL) ? NULL : ::GlobalLock(hFileBuffer); if (pBuffer == NULL) { + if (hFileBuffer) ::GlobalFree(hFileBuffer); + request->OutOfMemory = true; ::CloseHandle(hFile); - return ProcessReadGDIPlusRequest(request); + return; } - } - else { + } else { nFileSize = 0; // to avoid compiler warnings, not used } if (bUseCachedDecoder || (::ReadFile(hFile, pBuffer, nFileSize, (LPDWORD)&nNumBytesRead, NULL) && nNumBytesRead == nFileSize)) { int nWidth, nHeight, nBPP, nFrameCount, nFrameTimeMs; bool bHasAnimation; uint8* pPixelData = NULL; - void* pEXIFData; + void* pEXIFData = NULL; +#ifndef WINXP // If UseEmbeddedColorProfiles is true and the image isn't animated, we should use GDI+ for better color management - if (bUseCachedDecoder || !CSettingsProvider::This().UseEmbeddedColorProfiles() || PngReader::IsAnimated(pBuffer, nFileSize)) + bool bUseGDIPlus = CSettingsProvider::This().ForceGDIPlus() || CSettingsProvider::This().UseEmbeddedColorProfiles(); + if (bUseCachedDecoder || !bUseGDIPlus || PngReader::IsAnimated(pBuffer, nFileSize)) pPixelData = (uint8*)PngReader::ReadImage(nWidth, nHeight, nBPP, bHasAnimation, nFrameCount, nFrameTimeMs, pEXIFData, request->OutOfMemory, pBuffer, nFileSize); +#endif if (pPixelData != NULL) { if (bHasAnimation) @@ -708,25 +708,35 @@ void CImageLoadThread::ProcessReadPNGRequest(CRequest* request) { *pImage32++ = Helpers::AlphaBlendBackground(*pImage32, CSettingsProvider::This().ColorTransparency()); request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, pEXIFData, 4, 0, IF_PNG, bHasAnimation, request->FrameIndex, nFrameCount, nFrameTimeMs); - free(pEXIFData); - bSuccess = true; - } - else { + } else { DeleteCachedPngDecoder(); + + IStream* pStream = NULL; + if (::CreateStreamOnHGlobal(hFileBuffer, FALSE, &pStream) == S_OK) { + Gdiplus::Bitmap* pBitmap = Gdiplus::Bitmap::FromStream(pStream, CSettingsProvider::This().UseEmbeddedColorProfiles()); + bool isOutOfMemory, isAnimatedGIF; + pEXIFData = PngReader::GetEXIFBlock(pBuffer, nFileSize); + request->Image = ConvertGDIPlusBitmapToJPEGImage(pBitmap, 0, pEXIFData, 0, isOutOfMemory, isAnimatedGIF); + request->OutOfMemory = request->Image == NULL && isOutOfMemory; + pStream->Release(); + delete pBitmap; + } else { + request->OutOfMemory = true; + } } + free(pEXIFData); } } catch (...) { - // delete request->Image; - // request->Image = NULL; + delete request->Image; + request->Image = NULL; request->ExceptionError = true; } if (!bUseCachedDecoder) { ::CloseHandle(hFile); - delete[] pBuffer; + if (pBuffer) ::GlobalUnlock(hFileBuffer); + if (hFileBuffer) ::GlobalFree(hFileBuffer); } - if (!bSuccess) - return ProcessReadGDIPlusRequest(request); } #endif diff --git a/src/JPEGView/PNGWrapper.cpp b/src/JPEGView/PNGWrapper.cpp index 5d04d6ff..e840f1ed 100644 --- a/src/JPEGView/PNGWrapper.cpp +++ b/src/JPEGView/PNGWrapper.cpp @@ -1,10 +1,11 @@ #include "stdafx.h" #include "PNGWrapper.h" + +#ifndef WINXP #include "png.h" #include "MaxImageDef.h" #include -#include // Uncomment to build without APNG support //#undef PNG_APNG_SUPPORTED @@ -400,3 +401,32 @@ bool PngReader::IsAnimated(void* buffer, size_t sizebytes) { } return false; } +#else +#define PNG_UINT_31_MAX (0x7fffffff) +#endif + +void* PngReader::GetEXIFBlock(void* buffer, size_t sizebytes) { + size_t offset = 8; // skip PNG signature + while (offset + 7 < sizebytes) { + unsigned int chunksize = *(unsigned int*)((char*)buffer + offset); + // PNG chunk sizes are big-endian and must be converted to little-endian + chunksize = _byteswap_ulong(chunksize); + + if (memcmp((char*)buffer + offset + 4, "eXIf", 4) == 0 && chunksize < 65528 && offset + chunksize + 7 < sizebytes) { + void* exif_chunk = malloc(chunksize + 10); + if (exif_chunk != NULL) { + memcpy(exif_chunk, "\xFF\xE1\0\0Exif\0\0", 10); + *((unsigned short*)exif_chunk + 1) = _byteswap_ushort(chunksize + 8); + memcpy((char*)exif_chunk + 10, (char*)buffer + offset + 8, chunksize); + } + return exif_chunk; + } + + // Prevent infinite loop + if (chunksize > PNG_UINT_31_MAX) return NULL; + + // 12 comes from 4 bytes for chunk size, 4 for chunk name, and 4 for CRC32 + offset += chunksize + 12; + } + return NULL; +} diff --git a/src/JPEGView/PNGWrapper.h b/src/JPEGView/PNGWrapper.h index 0dac157d..71842a5b 100644 --- a/src/JPEGView/PNGWrapper.h +++ b/src/JPEGView/PNGWrapper.h @@ -4,6 +4,7 @@ class PngReader { public: +#ifndef WINXP // Returns data in 4 byte BGRA static void* ReadImage(int& width, // width of the image loaded. int& height, // height of the image loaded. @@ -20,11 +21,16 @@ class PngReader // Returns true if PNG is animated, false otherwise static bool IsAnimated(void* buffer, size_t sizebytes); +#endif + // Get EXIF Block + static void* GetEXIFBlock(void* buffer, size_t sizebytes); +#ifndef WINXP private: struct png_cache; static png_cache cache; static bool BeginReading(void* buffer, size_t sizebytes, bool& outOfMemory); static void* ReadNextFrame(void** exif_chunk, unsigned int* exif_size); static void DeleteCacheInternal(bool free_buffer); +#endif };