diff --git a/src/JPEGView/AVIFWrapper.cpp b/src/JPEGView/AVIFWrapper.cpp index 863fa4a4..eb19fa4a 100644 --- a/src/JPEGView/AVIFWrapper.cpp +++ b/src/JPEGView/AVIFWrapper.cpp @@ -22,6 +22,7 @@ void* AvifReader::ReadImage(int& width, int frame_index, int& frame_count, int& frame_time, + void*& exif_chunk, bool& outOfMemory, const void* buffer, int sizebytes) @@ -30,6 +31,7 @@ void* AvifReader::ReadImage(int& width, width = height = 0; nchannels = 4; has_animation = false; + exif_chunk = NULL; avifResult result; int nthreads = 256; // sets maximum number of active threads allowed for libavif, default is 1 @@ -93,9 +95,22 @@ void* AvifReader::ReadImage(int& width, if (cache.transform == NULL) cache.transform = ICCProfileTransform::CreateTransform(icc.data, icc.size, ICCProfileTransform::FORMAT_BGRA); ICCProfileTransform::DoTransform(cache.transform, cache.rgb.pixels, cache.rgb.pixels, width, height); + + avifRWData exif = cache.decoder->image->exif; + if (exif.size > 8 && exif.size < 65528 && exif.data != NULL) { + exif_chunk = malloc(exif.size + 10); + if (exif_chunk != NULL) { + memcpy(exif_chunk, "\xFF\xE1\0\0Exif\0\0", 10); + *((unsigned short*)exif_chunk + 1) = _byteswap_ushort(exif.size + 8); + memcpy((uint8_t*)exif_chunk + 10, exif.data, exif.size); + } + } + + void* pPixelData = cache.rgb.pixels; if (!has_animation) DeleteCache(); - return cache.rgb.pixels; + + return pPixelData; } void AvifReader::DeleteCache() { diff --git a/src/JPEGView/AVIFWrapper.h b/src/JPEGView/AVIFWrapper.h index d875a179..bfccfa2f 100644 --- a/src/JPEGView/AVIFWrapper.h +++ b/src/JPEGView/AVIFWrapper.h @@ -14,6 +14,7 @@ class AvifReader int frame_index, // index of frame int& frame_count, // number of frames int& frame_time, // frame duration in milliseconds + void*& exif_chunk, // Pointer to Exif data (must be freed by caller) bool& outOfMemory, // set to true when no memory to read image const void* buffer, // memory address containing jxl compressed data. int sizebytes); // size of jxl compressed data diff --git a/src/JPEGView/EXIFDisplayCtl.cpp b/src/JPEGView/EXIFDisplayCtl.cpp index faf8cded..1f1e4d59 100644 --- a/src/JPEGView/EXIFDisplayCtl.cpp +++ b/src/JPEGView/EXIFDisplayCtl.cpp @@ -121,7 +121,7 @@ void CEXIFDisplayCtl::FillEXIFDataDisplay() { CRawMetadata* pRawMetaData = CurrentImage()->GetRawMetadata(); if (pEXIFReader != NULL) { sComment = pEXIFReader->GetUserComment(); - if (sComment == NULL || sComment[0] == 0) { + if (sComment == NULL || sComment[0] == 0 || ((std::wstring) sComment).find_first_not_of(L" \t\n\r\f\v", 0) == std::wstring::npos) { sComment = pEXIFReader->GetImageDescription(); } if (pEXIFReader->GetAcquisitionTimePresent()) { @@ -201,7 +201,7 @@ void CEXIFDisplayCtl::FillEXIFDataDisplay() { } } - if (sComment == NULL || sComment[0] == 0) { + if (sComment == NULL || sComment[0] == 0 || ((std::wstring)sComment).find_first_not_of(L" \t\n\r\f\v", 0) == std::wstring::npos) { sComment = CurrentImage()->GetJPEGComment(); } if (CSettingsProvider::This().ShowJPEGComments() && sComment != NULL && sComment[0] != 0) { diff --git a/src/JPEGView/EXIFReader.cpp b/src/JPEGView/EXIFReader.cpp index 718ff4a7..1a79ce0e 100644 --- a/src/JPEGView/EXIFReader.cpp +++ b/src/JPEGView/EXIFReader.cpp @@ -241,7 +241,7 @@ bool CEXIFReader::ParseDateString(SYSTEMTIME & date, const CString& str) { return false; } -CEXIFReader::CEXIFReader(void* pApp1Block) +CEXIFReader::CEXIFReader(void* pApp1Block, EImageFormat eImageFormat) : m_exposureTime(0, 0) { memset(&m_acqDate, 0, sizeof(SYSTEMTIME)); @@ -297,7 +297,11 @@ CEXIFReader::CEXIFReader(void* pApp1Block) m_pLastIFD0 = pLastIFD0; // image orientation - uint8* pTagOrientation = FindTag(pIFD0, pLastIFD0, 0x112, bLittleEndian); + uint8* pTagOrientation = NULL; + // orientation tags must be ignored for JXL, they are taken care of by the decoder + if (eImageFormat != IF_JXL) { + pTagOrientation = FindTag(pIFD0, pLastIFD0, 0x112, bLittleEndian); + } if (pTagOrientation != NULL) { m_nImageOrientation = ReadShortTag(pTagOrientation, bLittleEndian); } diff --git a/src/JPEGView/EXIFReader.h b/src/JPEGView/EXIFReader.h index 37cadbe2..98716075 100644 --- a/src/JPEGView/EXIFReader.h +++ b/src/JPEGView/EXIFReader.h @@ -38,7 +38,7 @@ class CEXIFReader { // The pApp1Block must point to the APP1 block of the EXIF data, including the APP1 block marker // The class does not take ownership of the memory (no copy made), thus the APP1 block must not be deleted // while the EXIF reader class is deleted. - CEXIFReader(void* pApp1Block); + CEXIFReader(void* pApp1Block, EImageFormat eImageFormat); ~CEXIFReader(void); // Parse date string in the EXIF date/time format diff --git a/src/JPEGView/HEIFWrapper.cpp b/src/JPEGView/HEIFWrapper.cpp index f6fd893b..a3906217 100644 --- a/src/JPEGView/HEIFWrapper.cpp +++ b/src/JPEGView/HEIFWrapper.cpp @@ -8,6 +8,7 @@ void * HeifReader::ReadImage(int &width, int &height, int &nchannels, int &frame_count, + void* &exif_chunk, bool &outOfMemory, int frame_index, const void *buffer, @@ -18,6 +19,7 @@ void * HeifReader::ReadImage(int &width, nchannels = 4; unsigned char* pPixelData = NULL; + exif_chunk = NULL; heif::Context context; context.read_from_memory_without_copy(buffer, sizebytes); @@ -69,5 +71,19 @@ void * HeifReader::ReadImage(int &width, } ICCProfileTransform::DeleteTransform(transform); + std::vector exif_blocks = handle.get_list_of_metadata_block_IDs("Exif"); + + if (!exif_blocks.empty()) { + std::vector exif = handle.get_metadata(exif_blocks[0]); + if (exif.size() > 8 && exif.size() < 65538) { + exif_chunk = malloc(exif.size()); + if (exif_chunk != NULL) { + memcpy(exif_chunk, exif.data(), exif.size()); + *((unsigned short*)exif_chunk) = _byteswap_ushort(0xFFE1); + *((unsigned short*)exif_chunk + 1) = _byteswap_ushort(exif.size() - 2); + } + } + } + return (void*)pPixelData; } diff --git a/src/JPEGView/HEIFWrapper.h b/src/JPEGView/HEIFWrapper.h index c225da7e..fa9e2570 100644 --- a/src/JPEGView/HEIFWrapper.h +++ b/src/JPEGView/HEIFWrapper.h @@ -10,6 +10,7 @@ class HeifReader int &height, // height of the image loaded. int &bpp, // BYTES (not bits) PER PIXEL. int &frame_count, // number of top-level images + void* &exif_chunk, // Pointer to Exif data (must be freed by caller) bool &outOfMemory, // set to true when no memory to read image int frame_index, // index of requested frame const void *buffer, // memory address containing heic compressed data. diff --git a/src/JPEGView/ImageLoadThread.cpp b/src/JPEGView/ImageLoadThread.cpp index fed5be88..7943555b 100644 --- a/src/JPEGView/ImageLoadThread.cpp +++ b/src/JPEGView/ImageLoadThread.cpp @@ -638,7 +638,8 @@ void CImageLoadThread::ProcessReadWEBPRequest(CRequest * request) { int nFrameCount = 1; int nFrameTimeMs = 0; int nBPP; - uint8* pPixelData = (uint8*)WebpReaderWriter::ReadImage(nWidth, nHeight, nBPP, bHasAnimation, nFrameCount, nFrameTimeMs, request->OutOfMemory, pBuffer, nFileSize); + void* pEXIFData; + uint8* pPixelData = (uint8*)WebpReaderWriter::ReadImage(nWidth, nHeight, nBPP, bHasAnimation, nFrameCount, nFrameTimeMs, pEXIFData, request->OutOfMemory, pBuffer, nFileSize); if (pPixelData && nBPP == 4) { // Multiply alpha value into each AABBGGRR pixel uint32* pImage32 = (uint32*)pPixelData; @@ -648,7 +649,8 @@ void CImageLoadThread::ProcessReadWEBPRequest(CRequest * request) { if (bHasAnimation) { m_sLastWebpFileName = sFileName; } - request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, NULL, nBPP, 0, IF_WEBP, bHasAnimation, request->FrameIndex, nFrameCount, nFrameTimeMs); + request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, pEXIFData, nBPP, 0, IF_WEBP, bHasAnimation, request->FrameIndex, nFrameCount, nFrameTimeMs); + free(pEXIFData); } else { delete[] pPixelData; @@ -710,10 +712,11 @@ void CImageLoadThread::ProcessReadPNGRequest(CRequest* request) { int nWidth, nHeight, nBPP, nFrameCount, nFrameTimeMs; bool bHasAnimation; uint8* pPixelData = NULL; + void* pEXIFData; // 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)) - pPixelData = (uint8*)PngReader::ReadImage(nWidth, nHeight, nBPP, bHasAnimation, nFrameCount, nFrameTimeMs, request->OutOfMemory, pBuffer, nFileSize); + pPixelData = (uint8*)PngReader::ReadImage(nWidth, nHeight, nBPP, bHasAnimation, nFrameCount, nFrameTimeMs, pEXIFData, request->OutOfMemory, pBuffer, nFileSize); if (pPixelData != NULL) { if (bHasAnimation) @@ -723,7 +726,8 @@ void CImageLoadThread::ProcessReadPNGRequest(CRequest* request) { for (int i = 0; i < nWidth * nHeight; i++) *pImage32++ = WebpAlphaBlendBackground(*pImage32, CSettingsProvider::This().ColorTransparency()); - request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, NULL, 4, 0, IF_PNG, bHasAnimation, request->FrameIndex, nFrameCount, nFrameTimeMs); + request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, pEXIFData, 4, 0, IF_PNG, bHasAnimation, request->FrameIndex, nFrameCount, nFrameTimeMs); + free(pEXIFData); bSuccess = true; } else { @@ -787,7 +791,8 @@ void CImageLoadThread::ProcessReadJXLRequest(CRequest* request) { if (bUseCachedDecoder || (::ReadFile(hFile, pBuffer, nFileSize, (LPDWORD)&nNumBytesRead, NULL) && nNumBytesRead == nFileSize)) { int nWidth, nHeight, nBPP, nFrameCount, nFrameTimeMs; bool bHasAnimation; - uint8* pPixelData = (uint8*)JxlReader::ReadImage(nWidth, nHeight, nBPP, bHasAnimation, nFrameCount, nFrameTimeMs, request->OutOfMemory, pBuffer, nFileSize); + void* pEXIFData; + uint8* pPixelData = (uint8*)JxlReader::ReadImage(nWidth, nHeight, nBPP, bHasAnimation, nFrameCount, nFrameTimeMs, pEXIFData, request->OutOfMemory, pBuffer, nFileSize); if (pPixelData != NULL) { if (bHasAnimation) m_sLastJxlFileName = sFileName; @@ -796,7 +801,8 @@ void CImageLoadThread::ProcessReadJXLRequest(CRequest* request) { for (int i = 0; i < nWidth * nHeight; i++) *pImage32++ = WebpAlphaBlendBackground(*pImage32, CSettingsProvider::This().ColorTransparency()); - request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, NULL, 4, 0, IF_JXL, bHasAnimation, request->FrameIndex, nFrameCount, nFrameTimeMs); + request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, pEXIFData, 4, 0, IF_JXL, bHasAnimation, request->FrameIndex, nFrameCount, nFrameTimeMs); + free(pEXIFData); } else { DeleteCachedJxlDecoder(); } @@ -858,8 +864,9 @@ void CImageLoadThread::ProcessReadAVIFRequest(CRequest* request) { if (bUseCachedDecoder || (::ReadFile(hFile, pBuffer, nFileSize, (LPDWORD)&nNumBytesRead, NULL) && nNumBytesRead == nFileSize)) { int nWidth, nHeight, nBPP, nFrameCount, nFrameTimeMs; bool bHasAnimation; + void* pEXIFData; uint8* pPixelData = (uint8*)AvifReader::ReadImage(nWidth, nHeight, nBPP, bHasAnimation, request->FrameIndex, - nFrameCount, nFrameTimeMs, request->OutOfMemory, pBuffer, nFileSize); + nFrameCount, nFrameTimeMs, pEXIFData, request->OutOfMemory, pBuffer, nFileSize); if (pPixelData != NULL) { if (bHasAnimation) m_sLastAvifFileName = sFileName; @@ -868,7 +875,8 @@ void CImageLoadThread::ProcessReadAVIFRequest(CRequest* request) { for (int i = 0; i < nWidth * nHeight; i++) *pImage32++ = WebpAlphaBlendBackground(*pImage32, CSettingsProvider::This().ColorTransparency()); - request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, NULL, 4, 0, IF_AVIF, bHasAnimation, request->FrameIndex, nFrameCount, nFrameTimeMs); + request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, pEXIFData, 4, 0, IF_AVIF, bHasAnimation, request->FrameIndex, nFrameCount, nFrameTimeMs); + free(pEXIFData); bSuccess = true; } else { DeleteCachedAvifDecoder(); @@ -920,14 +928,16 @@ void CImageLoadThread::ProcessReadHEIFRequest(CRequest* request) { int nWidth, nHeight, nBPP, nFrameCount, nFrameTimeMs; nFrameCount = 1; nFrameTimeMs = 0; - uint8* pPixelData = (uint8*)HeifReader::ReadImage(nWidth, nHeight, nBPP, nFrameCount, request->OutOfMemory, request->FrameIndex, pBuffer, nFileSize); + void* pEXIFData; + uint8* pPixelData = (uint8*)HeifReader::ReadImage(nWidth, nHeight, nBPP, nFrameCount, pEXIFData, request->OutOfMemory, request->FrameIndex, pBuffer, nFileSize); if (pPixelData != NULL) { // Multiply alpha value into each AABBGGRR pixel uint32* pImage32 = (uint32*)pPixelData; for (int i = 0; i < nWidth * nHeight; i++) *pImage32++ = WebpAlphaBlendBackground(*pImage32, CSettingsProvider::This().ColorTransparency()); - request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, NULL, nBPP, 0, IF_HEIF, false, request->FrameIndex, nFrameCount, nFrameTimeMs); + request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, pEXIFData, nBPP, 0, IF_HEIF, false, request->FrameIndex, nFrameCount, nFrameTimeMs); + free(pEXIFData); } } } catch(heif::Error he) { diff --git a/src/JPEGView/JPEGImage.cpp b/src/JPEGView/JPEGImage.cpp index 74b7ef3f..ead9a47c 100644 --- a/src/JPEGView/JPEGImage.cpp +++ b/src/JPEGView/JPEGImage.cpp @@ -83,7 +83,7 @@ CJPEGImage::CJPEGImage(int nWidth, int nHeight, void* pPixels, void* pEXIFData, m_nEXIFSize = pEXIF[2]*256 + pEXIF[3] + 2; m_pEXIFData = new char[m_nEXIFSize]; memcpy(m_pEXIFData, pEXIFData, m_nEXIFSize); - m_pEXIFReader = new CEXIFReader(m_pEXIFData); + m_pEXIFReader = new CEXIFReader(m_pEXIFData, eImageFormat); } else { m_nEXIFSize = 0; m_pEXIFData = NULL; diff --git a/src/JPEGView/JXLWrapper.cpp b/src/JPEGView/JXLWrapper.cpp index d6bccf52..b48c00dc 100644 --- a/src/JPEGView/JXLWrapper.cpp +++ b/src/JPEGView/JXLWrapper.cpp @@ -18,11 +18,13 @@ struct JxlReader::jxl_cache { int width; int height; void* transform; + std::vector exif; }; JxlReader::jxl_cache JxlReader::cache = { 0 }; // based on https://github.com/libjxl/libjxl/blob/main/examples/decode_oneshot.cc +// and https://github.com/libjxl/libjxl/blob/main/examples/decode_exif_metadata.cc bool JxlReader::DecodeJpegXlOneShot(const uint8_t* jxl, size_t size, std::vector* pixels, int& xsize, int& ysize, bool& have_animation, int& frame_count, int& frame_time, std::vector* icc_profile, bool& outOfMemory) { @@ -33,11 +35,15 @@ bool JxlReader::DecodeJpegXlOneShot(const uint8_t* jxl, size_t size, std::vector if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(cache.decoder.get(), JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | + JXL_DEC_BOX | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE)) { return false; } + if (JXL_DEC_SUCCESS != JxlDecoderSetDecompressBoxes(cache.decoder.get(), JXL_TRUE)) { + return false; + } if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(cache.decoder.get(), JxlResizableParallelRunner, @@ -53,6 +59,8 @@ bool JxlReader::DecodeJpegXlOneShot(const uint8_t* jxl, size_t size, std::vector JxlBasicInfo info; JxlPixelFormat format = { 4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 }; + const constexpr size_t kChunkSize = 65536; + size_t output_pos = 0; bool loop_check = false; for (;;) { @@ -139,6 +147,26 @@ bool JxlReader::DecodeJpegXlOneShot(const uint8_t* jxl, size_t size, std::vector JxlDecoderSubscribeEvents(cache.decoder.get(), JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE); JxlDecoderSetInput(cache.decoder.get(), cache.data, cache.data_size); JxlDecoderCloseInput(cache.decoder.get()); + } else if (status == JXL_DEC_BOX) { + if (!cache.exif.empty()) { + size_t remaining = JxlDecoderReleaseBoxBuffer(cache.decoder.get()); + cache.exif.resize(cache.exif.size() - remaining); + } else { + JxlBoxType type; + if (JXL_DEC_SUCCESS != + JxlDecoderGetBoxType(cache.decoder.get(), type, true)) { + return false; + } + if (!memcmp(type, "Exif", 4)) { + cache.exif.resize(kChunkSize); + JxlDecoderSetBoxBuffer(cache.decoder.get(), cache.exif.data(), cache.exif.size()); + } + } + } else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) { + size_t remaining = JxlDecoderReleaseBoxBuffer(cache.decoder.get()); + output_pos += kChunkSize - remaining; + cache.exif.resize(cache.exif.size() + kChunkSize); + JxlDecoderSetBoxBuffer(cache.decoder.get(), cache.exif.data() + output_pos, cache.exif.size() - output_pos); } else { return false; } @@ -151,6 +179,7 @@ void* JxlReader::ReadImage(int& width, bool& has_animation, int& frame_count, int& frame_time, + void*& exif_chunk, bool& outOfMemory, const void* buffer, int sizebytes) @@ -160,6 +189,7 @@ void* JxlReader::ReadImage(int& width, nchannels = 4; has_animation = false; unsigned char* pPixelData = NULL; + exif_chunk = NULL; std::vector pixels; @@ -182,8 +212,20 @@ void* JxlReader::ReadImage(int& width, for (uint32_t* i = (uint32_t*)pPixelData; (uint8_t*)i < pPixelData + size; i++) *i = ((*i & 0x00FF0000) >> 16) | ((*i & 0x0000FF00)) | ((*i & 0x000000FF) << 16) | ((*i & 0xFF000000)); } + + // Copy Exif data into the format understood by CEXIFReader + if (!cache.exif.empty() && cache.exif.size() > 8 && cache.exif.size() < 65532) { + exif_chunk = malloc(cache.exif.size() + 6); + if (exif_chunk != NULL) { + memcpy(exif_chunk, "\xFF\xE1\0\0Exif\0\0", 10); + *((unsigned short*)exif_chunk + 1) = _byteswap_ushort(cache.exif.size() + 4); + memcpy((uint8_t*)exif_chunk + 10, cache.exif.data() + 4, cache.exif.size() - 4); + } + } + if (!has_animation) DeleteCache(); + return pPixelData; } diff --git a/src/JPEGView/JXLWrapper.h b/src/JPEGView/JXLWrapper.h index 469e9875..f70420a2 100644 --- a/src/JPEGView/JXLWrapper.h +++ b/src/JPEGView/JXLWrapper.h @@ -13,6 +13,7 @@ class JxlReader bool& has_animation, // if the image is animated int& frame_count, // number of frames int& frame_time, // frame duration in milliseconds + void*& exif, // Pointer to Exif data (must be freed by caller) bool& outOfMemory, // set to true when no memory to read image const void* buffer, // memory address containing jxl compressed data. int sizebytes); // size of jxl compressed data diff --git a/src/JPEGView/PNGWrapper.cpp b/src/JPEGView/PNGWrapper.cpp index fe2610fe..5d04d6ff 100644 --- a/src/JPEGView/PNGWrapper.cpp +++ b/src/JPEGView/PNGWrapper.cpp @@ -107,9 +107,12 @@ void BlendOver(unsigned char** rows_dst, unsigned char** rows_src, unsigned int } #endif -void* PngReader::ReadNextFrame() +void* PngReader::ReadNextFrame(void** exif_chunk, png_uint_32* exif_size) { unsigned int j; + if (exif_chunk != NULL && exif_size != NULL) { + png_get_eXIf_1(cache.png_ptr, cache.info_ptr, exif_size, (png_bytep*)exif_chunk); + } #ifdef PNG_APNG_SUPPORTED if (png_get_valid(cache.png_ptr, cache.info_ptr, PNG_INFO_acTL)) { @@ -292,10 +295,12 @@ void* PngReader::ReadImage(int& width, bool& has_animation, int& frame_count, int& frame_time, + void*& exif_chunk, bool& outOfMemory, void* buffer, size_t sizebytes) { + exif_chunk = NULL; if (!cache.buffer) { if (sizebytes < 8) return NULL; @@ -316,10 +321,12 @@ void* PngReader::ReadImage(int& width, } + void* exif = NULL; + unsigned int exif_size = 0; bool read_two = cache.frame_index < cache.first; - void* pixels = ReadNextFrame(); + void* pixels = ReadNextFrame(&exif, &exif_size); if (pixels && read_two) - pixels = ReadNextFrame(); + pixels = ReadNextFrame(&exif, &exif_size); width = cache.width; height = cache.height; @@ -333,6 +340,15 @@ void* PngReader::ReadImage(int& width, cache.delay_den = 100; frame_time = (int)(1000.0 * cache.delay_num / cache.delay_den); + if (exif_size > 8 && exif_size < 65528 && exif != NULL) { + exif_chunk = malloc(exif_size + 10); + if (exif_chunk != NULL) { + memcpy(exif_chunk, "\xFF\xE1\0\0Exif\0\0", 10); + *((unsigned short*)exif_chunk + 1) = _byteswap_ushort(exif_size + 8); + memcpy((uint8_t*)exif_chunk + 10, exif, exif_size); + } + + } if (!has_animation) DeleteCache(); return pixels; diff --git a/src/JPEGView/PNGWrapper.h b/src/JPEGView/PNGWrapper.h index a4ef5682..0dac157d 100644 --- a/src/JPEGView/PNGWrapper.h +++ b/src/JPEGView/PNGWrapper.h @@ -11,6 +11,7 @@ class PngReader bool& has_animation, // if the image is animated int& frame_count, // number of frames int& frame_time, // frame duration in milliseconds + void*& exif_chunk, // Pointer to Exif data (must be freed by caller) bool& outOfMemory, // set to true when no memory to read image void* buffer, // memory address containing png compressed data. size_t sizebytes); // size of png compressed data @@ -24,6 +25,6 @@ class PngReader struct png_cache; static png_cache cache; static bool BeginReading(void* buffer, size_t sizebytes, bool& outOfMemory); - static void* ReadNextFrame(); + static void* ReadNextFrame(void** exif_chunk, unsigned int* exif_size); static void DeleteCacheInternal(bool free_buffer); }; diff --git a/src/JPEGView/SaveImage.cpp b/src/JPEGView/SaveImage.cpp index 736062bf..cee58d28 100644 --- a/src/JPEGView/SaveImage.cpp +++ b/src/JPEGView/SaveImage.cpp @@ -163,7 +163,7 @@ static void* CompressAndSave(LPCTSTR sFileName, CJPEGImage * pImage, memcpy(pNewStream + 2, pImage->GetEXIFData(), pImage->GetEXIFDataLength()); // copy EXIF block // Set image orientation back to normal orientation, we save the pixels as displayed - CEXIFReader exifReader(pNewStream + 2); + CEXIFReader exifReader(pNewStream + 2, IF_JPEG); exifReader.WriteImageOrientation(1); // 1 means default orientation (unrotated) if (bDeleteThumbnail) { exifReader.DeleteThumbnail(); diff --git a/src/JPEGView/WEBPWrapper.cpp b/src/JPEGView/WEBPWrapper.cpp index 88028ef6..0fb4d883 100644 --- a/src/JPEGView/WEBPWrapper.cpp +++ b/src/JPEGView/WEBPWrapper.cpp @@ -25,6 +25,7 @@ void* WebpReaderWriter::ReadImage(int& width, bool& has_animation, int& frame_count, int& frame_time, + void*& exif_chunk, bool& outOfMemory, const void* buffer, int sizebytes) @@ -34,6 +35,8 @@ void* WebpReaderWriter::ReadImage(int& width, width = height = 0; nchannels = 4; outOfMemory = false; + exif_chunk = NULL; + if (!cache.decoder || !cache.data.bytes) { if (!WebPGetInfo((const uint8_t*)buffer, sizebytes, &width, &height)) return NULL; @@ -53,9 +56,22 @@ void* WebpReaderWriter::ReadImage(int& width, WebPDemuxer* demuxer = WebPDemux(&data); WebPChunkIterator chunk_iter; void* transform = NULL; + if (WebPDemuxGetChunk(demuxer, "ICCP", 1, &chunk_iter)) transform = ICCProfileTransform::CreateTransform(chunk_iter.chunk.bytes, chunk_iter.chunk.size, ICCProfileTransform::FORMAT_BGRA); WebPDemuxReleaseChunkIterator(&chunk_iter); + if (WebPDemuxGetChunk(demuxer, "EXIF", 1, &chunk_iter)) { + WebPData exif = chunk_iter.chunk; + if (exif.size > 8 && exif.size < 65528 && exif.bytes != NULL) { + exif_chunk = malloc(exif.size + 10); + if (exif_chunk != NULL) { + memcpy(exif_chunk, "\xFF\xE1\0\0Exif\0\0", 10); + *((unsigned short*)exif_chunk + 1) = _byteswap_ushort(exif.size + 8); + memcpy((uint8_t*)exif_chunk + 10, exif.bytes, exif.size); + } + } + } + WebPDemuxReleaseChunkIterator(&chunk_iter); WebPDemuxDelete(demuxer); has_animation = features.has_animation; diff --git a/src/JPEGView/WEBPWrapper.h b/src/JPEGView/WEBPWrapper.h index f96e214d..5140f908 100644 --- a/src/JPEGView/WEBPWrapper.h +++ b/src/JPEGView/WEBPWrapper.h @@ -11,6 +11,7 @@ class WebpReaderWriter bool& has_animation, // if the image is animated int& frame_count, // number of frames int& frame_time, // frame duration in milliseconds + void*& exif, // Pointer to Exif data (must be freed by caller) bool& outOfMemory, // set to true when no memory to read image const void* buffer, // memory address containing webp compressed data. int sizebytes); // size of webp compressed data