Skip to content

Commit

Permalink
Merge branch 'master' into psd-psb
Browse files Browse the repository at this point in the history
  • Loading branch information
qbnu committed Jun 22, 2024
2 parents 2f93e14 + 69bdf62 commit 4a62129
Show file tree
Hide file tree
Showing 23 changed files with 217 additions and 102 deletions.
42 changes: 42 additions & 0 deletions src/JPEGView/AVIFWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "AVIFWrapper.h"
#include "avif/avif.h"
#include "BasicProcessing.h"
#include "MaxImageDef.h"
#include "ICCProfileTransform.h"

Expand Down Expand Up @@ -46,6 +47,7 @@ void* AvifReader::ReadImage(int& width,
memcpy(cache.data, buffer, sizebytes);
cache.decoder = avifDecoderCreate();
cache.decoder->maxThreads = nthreads;
cache.decoder->strictFlags = AVIF_STRICT_DISABLED;
result = avifDecoderSetIOMemory(cache.decoder, cache.data, sizebytes);
if (result != AVIF_RESULT_OK) {
DeleteCache();
Expand Down Expand Up @@ -91,6 +93,46 @@ void* AvifReader::ReadImage(int& width,
DeleteCache();
return NULL;
}

// Handle clap, irot and imir boxes
avifTransformFlags flags = cache.decoder->image->transformFlags;
if (flags & AVIF_TRANSFORM_CLAP) {
avifCleanApertureBox* clap = &cache.decoder->image->clap;
avifCropRect crop;
avifDiagnostics diag;
if (avifCropRectConvertCleanApertureBox(&crop, clap, width, height, cache.decoder->image->yuvFormat, &diag)) {
POINT point = { crop.x, crop.y };
SIZE sz = { crop.width, crop.height };
void* pixels = CBasicProcessing::Crop32bpp(width, height, cache.rgb.pixels, CRect(point, sz));
if (pixels != NULL) {
delete[] cache.rgb.pixels;
cache.rgb.pixels = (uint8_t*)pixels;
width = crop.width;
height = crop.height;
}
}
}
if (flags & AVIF_TRANSFORM_IROT) {
int angle = 360 - cache.decoder->image->irot.angle * 90;
void* pixels = CBasicProcessing::Rotate32bpp(width, height, cache.rgb.pixels, angle);
if (pixels != NULL) {
delete[] cache.rgb.pixels;
cache.rgb.pixels = (uint8_t*)pixels;
if (angle != 180) {
int temp = width;
width = height;
height = temp;
}
}
}
if (flags & AVIF_TRANSFORM_IMIR) {
void* pixels = CBasicProcessing::Mirror32bpp(width, height, cache.rgb.pixels, cache.decoder->image->imir.axis);
if (pixels != NULL) {
delete[] cache.rgb.pixels;
cache.rgb.pixels = (uint8_t*)pixels;
}
}

avifRWData icc = cache.decoder->image->icc;
if (cache.transform == NULL)
cache.transform = ICCProfileTransform::CreateTransform(icc.data, icc.size, ICCProfileTransform::FORMAT_BGRA);
Expand Down
5 changes: 5 additions & 0 deletions src/JPEGView/BasicProcessing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,11 @@ void* CBasicProcessing::Rotate32bpp(int nWidth, int nHeight, const void* pDIBPix
return pTarget;
}

void* CBasicProcessing::Mirror32bpp(int nWidth, int nHeight, const void* pDIBPixels, bool bHorizontally) {
return bHorizontally ? CBasicProcessing::MirrorH32bpp(nWidth, nHeight, pDIBPixels) :
CBasicProcessing::MirrorV32bpp(nWidth, nHeight, pDIBPixels);
}

void* CBasicProcessing::MirrorH32bpp(int nWidth, int nHeight, const void* pDIBPixels) {
uint32* pTarget = new(std::nothrow) uint32[nWidth * nHeight];
if (pTarget == NULL) return NULL;
Expand Down
3 changes: 3 additions & 0 deletions src/JPEGView/BasicProcessing.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ class CBasicProcessing
// cases the return value is NULL
static void* Rotate32bpp(int nWidth, int nHeight, const void* pDIBPixels, int nRotationAngleCW);

// Mirror 32 bit DIB
static void* Mirror32bpp(int nWidth, int nHeight, const void* pDIBPixels, bool bHorizontally);

// Mirror 32 bit DIB horizontally
static void* MirrorH32bpp(int nWidth, int nHeight, const void* pDIBPixels);

Expand Down
2 changes: 1 addition & 1 deletion src/JPEGView/Config/JPEGView.ini.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ SlideShowEffectTime=250
; If set to true, Ctrl-S overrides the original file on disk, applying the current processings without
; showing a dialog or prompting the user to confirm.
; CAUTION: Use at your own risk! Be aware that the original image file is overridden and cannot be restored anymore!
OverrideOriginalFileWithoutSaveDialog=false
OverwriteOriginalFileWithoutSaveDialog=false

; If set to true, lossless JPEG transformations will trim the image as needed without prompting the user.
; This will remove 15 pixel rows/columns at the image borders in worst case.
Expand Down
2 changes: 1 addition & 1 deletion src/JPEGView/Config/JPEGView_ru.ini
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@ SlideShowEffectTime=250
; Если "true", то нажатие Ctrl-S перезаписывает исходный файл на диске,
; применяя текущие параметры обработки без запроса подтверждения.
; ОСТОРОЖНО: Файл перезаписывается, восстановить оригинал не удастся!
OverrideOriginalFileWithoutSaveDialog=false
OverwriteOriginalFileWithoutSaveDialog=false

; Если "true", то при преобразованиях JPEG без потерь обрезка изображения
; (когда она необходима) выполняется без запроса подтверждения.
Expand Down
2 changes: 1 addition & 1 deletion src/JPEGView/Config/JPEGView_ru.ini.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ SlideShowEffectTime=250
; Если "true", то нажатие Ctrl-S перезаписывает исходный файл на диске,
; применяя текущие параметры обработки без запроса подтверждения.
; ОСТОРОЖНО: Файл перезаписывается, восстановить оригинал не удастся!
OverrideOriginalFileWithoutSaveDialog=false
OverwriteOriginalFileWithoutSaveDialog=false

; Если "true", то при преобразованиях JPEG без потерь обрезка изображения
; (когда она необходима) выполняется без запроса подтверждения.
Expand Down
2 changes: 1 addition & 1 deletion src/JPEGView/Config/readme.html
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,7 @@ <h4>Saving Images</h4>
all image processing disabled.
This prevents that JPEGView will double-apply automatic image improvements next time the image is displayed.<br />
<b>Note:</b> When saving images, JPEGView will prompt for a new file name. If you want that Ctrl-S just overrides the
original file without showing a dialog, use the INI file setting OverrideOriginalFileWithoutSaveDialog=true.<br />
original file without showing a dialog, use the INI file setting OverwriteOriginalFileWithoutSaveDialog=true.<br />
Use this setting with care, be aware of the risk!<br />
<b>EXIF information</b><br />
EXIF information is copied to the target JPEG image if it was present in the source JPEG image. An embedded thumbnail image
Expand Down
2 changes: 1 addition & 1 deletion src/JPEGView/Config/readme_ru.html
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,7 @@ <h4>Сохранение изображений</h4>
Это препятствует двойному применению автоматических улучшений картинки при следующем её показе.<br/>
<i>Примечание 2:</i> при сохранении изображений JPEGView запрашивает новое имя файла. Если вы хотите, чтобы по&nbsp;<kbd>Ctrl+S</kbd>
просто перезаписывался исходный файл без появления диалогового окна, установите в INI-файле параметр
<code>OverrideOriginalFileWithoutSaveDialog=true</code>.<br/>
<code>OverwriteOriginalFileWithoutSaveDialog=true</code>.<br/>
Будьте осторожны с этой настройкой, так как она потенциально опасна потерей исходных файлов!<br/><br/>
<b>Информация EXIF</b><br/>
Если в исходном JPEG есть EXIF-данные, то они копируются в результирующий JPEG. При этом обновляется встроенная
Expand Down
2 changes: 1 addition & 1 deletion src/JPEGView/Config/readme_uk.html
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@ <h4>Збереження зображень</h4>
повному відключенні всієї обробки.
Це перешкоджає подвійному застосуванню автоматичних поліпшень картинки при наступному її показі.<br/>
<b>Примітка:</b> при збереженні зображень JPEGView запитує нове ім'я файлу. Якщо ви хочете, щоб натискання <kbd>Ctrl+S</kbd> просто перезаписувало
вихідний файл без появи діалогового вікна, встановіть в INI-файлі параметр <code>OverrideOriginalFileWithoutSaveDialog=true</code>.<br/>
вихідний файл без появи діалогового вікна, встановіть в INI-файлі параметр <code>OverwriteOriginalFileWithoutSaveDialog=true</code>.<br/>
Будьте обережні з цією настройкою, оскільки вона потенційно небезпечна (можлива втрата вихідних файлів)!<br/><br/>
<b>Інформація EXIF</b><br/>
EXIF-дані копіюються в результуюче JPEG-зображення, якщо вони були присутні у вихідному JPEG-зображенні. Вбудована мініатюра для попереднього перегляду
Expand Down
6 changes: 3 additions & 3 deletions src/JPEGView/EXIFReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,8 @@ CEXIFReader::CEXIFReader(void* pApp1Block, EImageFormat eImageFormat)

// image orientation
uint8* pTagOrientation = NULL;
// orientation tags must be ignored for JXL, they are taken care of by the decoder
if (eImageFormat != IF_JXL) {
// orientation tags must be ignored for JXL and HEIF/AVIF
if (eImageFormat != IF_JXL && eImageFormat != IF_HEIF && eImageFormat != IF_AVIF) {
pTagOrientation = FindTag(pIFD0, pLastIFD0, 0x112, bLittleEndian);
}
if (pTagOrientation != NULL) {
Expand Down Expand Up @@ -533,4 +533,4 @@ GPSCoordinate* CEXIFReader::ReadGPSCoordinate(uint8* pTIFFHeader, uint8* pTagLat
return NULL;

return new GPSCoordinate(reference, dDeg, dMin, dSec);
}
}
2 changes: 1 addition & 1 deletion src/JPEGView/Helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,7 @@ EImageFormat GetImageFormat(LPCTSTR sFileName) {
return IF_JXL;
} else if (_tcsicmp(sEnding, _T("AVIF")) == 0) {
return IF_AVIF;
} else if (_tcsicmp(sEnding, _T("HEIF")) == 0 || _tcsicmp(sEnding, _T("HEIC")) == 0) {
} else if (_tcsicmp(sEnding, _T("HEIF")) == 0 || _tcsicmp(sEnding, _T("HEIC")) == 0 || _tcsicmp(sEnding, _T("HIF")) == 0) {
return IF_HEIF;
} else if (_tcsicmp(sEnding, _T("TGA")) == 0) {
return IF_TGA;
Expand Down
112 changes: 66 additions & 46 deletions src/JPEGView/ImageLoadThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -62,36 +62,41 @@ static EImageFormat GetImageFormat(LPCTSTR sFileName) {
} else if ((header[0] == 0xff && header[1] == 0x0a) ||
memcmp(header, "\x00\x00\x00\x0cJXL\x20\x0d\x0a\x87\x0a", 12) == 0) {
return IF_JXL;

// Unfortunately, TIFF detection by header bytes is not reliable
// A few RAW image formats use TIFF as the container
// ex: CR2 - http://lclevy.free.fr/cr2/#key_info
// ex: DNG - https://www.adobe.com/creativecloud/file-types/image/raw/dng-file.html#dng
//
// JPEGView will fail to open these files if the following code is used
//
//} else if ((header[0] == 0x49 && header[1] == 0x49 && header[2] == 0x2a && header[3] == 0x00) ||
// (header[0] == 0x4d && header[1] == 0x4d && header[2] == 0x00 && header[3] == 0x2a)) {
// return IF_TIFF;

} else if (header[0] == 0x00 && header[1] == 0x00 && header[2] == 0x00 && memcmp(header+4, "ftyp", 4) == 0) {
} else if (!memcmp(header+4, "ftyp", 4)) {
// https://github.com/strukturag/libheif/issues/83
// https://github.com/strukturag/libheif/blob/ce1e4586b6222588c5afcd60c7ba9caa86bcc58c/libheif/heif.h#L602-L805

// AV1: avif, avis
if (!memcmp(header+8, "avi", 3))
return IF_AVIF;
// H265: heic, heix, hevc, hevx, heim, heis, hevm, hevs
if (header[8] == 'h' && header[9] == 'e')
if (!memcmp(header+8, "hei", 3) || !memcmp(header+8, "hev", 3))
return IF_HEIF;
// AV1: avif, avis
// Unspecified encoding: mif1, mif2, msf1, miaf, 1pic
return IF_AVIF; // try libavif, fallback to libheif
// Canon CR3
if (!memcmp(header+8, "crx ", 4))
return IF_CameraRAW;
} else if (header[0] == 'q' && header[1] == 'o' && header[2] == 'i' && header[3] == 'f') {
return IF_QOI;
} else if (header[0] == '8' && header[1] == 'B' && header[2] == 'P' && header[3] == 'S') {
return IF_PSD;
}

// default fallback if no matches based on magic bytes
return Helpers::GetImageFormat(sFileName);
EImageFormat eImageFormat = Helpers::GetImageFormat(sFileName);

if (eImageFormat != IF_Unknown) {
return eImageFormat;
} else if (!memcmp(header+4, "ftyp", 4)) {
// Unspecified encoding (possibly AVIF or HEIF): mif1, mif2, msf1, miaf, 1pic
return IF_AVIF;
} else if (!memcmp(header, "II*\0", 4) || !memcmp(header, "MM\0*", 4)) {
// Must be checked after file extension to avoid classifying RAW as TIFF
// A few RAW image formats use TIFF as the container
// ex: CR2 - http://lclevy.free.fr/cr2/#key_info
// ex: DNG - https://www.adobe.com/creativecloud/file-types/image/raw/dng-file.html#dng
return IF_TIFF;
}
return IF_Unknown;
}

static EImageFormat GetBitmapFormat(Gdiplus::Bitmap * pBitmap) {
Expand Down Expand Up @@ -315,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();
Expand Down Expand Up @@ -645,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;
Expand All @@ -663,36 +662,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 @@ -703,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

Expand Down Expand Up @@ -997,17 +1012,22 @@ void CImageLoadThread::ProcessReadRAWRequest(CRequest * request) {
if (fullsize == 2 || fullsize == 3) {
request->Image = RawReader::ReadImage(request->FileName, bOutOfMemory, fullsize == 2);
}
if (request->Image == NULL && fullsize == 2) {
request->Image = CReaderRAW::ReadRawImage(request->FileName, bOutOfMemory);
}
if (request->Image == NULL) {
request->Image = RawReader::ReadImage(request->FileName, bOutOfMemory, fullsize == 0 || fullsize == 3);
}
} catch (...) {
// libraw.dll not found or VC++ Runtime not installed
}
SetErrorMode(nPrevErrorMode);
#else
fullsize = fullsize == 1;
#endif

// Try with dcraw_mod
if (request->Image == NULL && fullsize != 1) {
if (request->Image == NULL && fullsize != 1 && fullsize != 2) {
request->Image = CReaderRAW::ReadRawImage(request->FileName, bOutOfMemory);
}
} catch (...) {
Expand Down
3 changes: 1 addition & 2 deletions src/JPEGView/JPEGImage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -715,8 +715,7 @@ bool CJPEGImage::Mirror(bool bHorizontally) {
}

InvalidateAllCachedPixelData();
void* pNewOriginalPixels = bHorizontally ? CBasicProcessing::MirrorH32bpp(m_nOrigWidth, m_nOrigHeight, m_pOrigPixels) :
CBasicProcessing::MirrorV32bpp(m_nOrigWidth, m_nOrigHeight, m_pOrigPixels);
void* pNewOriginalPixels = CBasicProcessing::Mirror32bpp(m_nOrigWidth, m_nOrigHeight, m_pOrigPixels, bHorizontally);
if (pNewOriginalPixels == NULL) return false;
delete[] m_pOrigPixels;
m_pOrigPixels = pNewOriginalPixels;
Expand Down
Loading

0 comments on commit 4a62129

Please sign in to comment.