Skip to content

Commit

Permalink
Support EXIF data in PNGs when using GDI+
Browse files Browse the repository at this point in the history
  • Loading branch information
qbnu committed Nov 20, 2023
1 parent c8cd092 commit 6cf5640
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 26 deletions.
62 changes: 36 additions & 26 deletions src/JPEGView/ImageLoadThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -322,19 +322,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();
Expand Down Expand Up @@ -652,7 +647,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;
Expand All @@ -670,36 +664,42 @@ void CImageLoadThread::ProcessReadPNGRequest(CRequest* request) {
return;
}
}
char* pBuffer = NULL;
HGLOBAL hFileBuffer = NULL;
void* pBuffer = NULL;
try {
unsigned int nFileSize;
unsigned int nNumBytesRead;
if (!bUseCachedDecoder) {
// 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)
Expand All @@ -710,25 +710,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

Expand Down
28 changes: 28 additions & 0 deletions src/JPEGView/PNGWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -400,3 +400,31 @@ bool PngReader::IsAnimated(void* buffer, size_t sizebytes) {
}
return false;
}

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;
}
3 changes: 3 additions & 0 deletions src/JPEGView/PNGWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ class PngReader
// Returns true if PNG is animated, false otherwise
static bool IsAnimated(void* buffer, size_t sizebytes);

// Get EXIF Block
static void* GetEXIFBlock(void* buffer, size_t sizebytes);

private:
struct png_cache;
static png_cache cache;
Expand Down

0 comments on commit 6cf5640

Please sign in to comment.