Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
sylikc committed Oct 6, 2023
2 parents d18f213 + e7562e0 commit 1a816d6
Show file tree
Hide file tree
Showing 20 changed files with 691 additions and 71 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ JPEGView is a lean, fast and highly configurable image viewer/editor with a mini
JPEGView has built-in support the following formats:

* Popular: JPEG, GIF
* Lossless: BMP, PNG, TIFF
* Lossless: BMP, PNG, TIFF, PSD
* Web: WEBP, JXL, HEIF/HEIC, AVIF
* Legacy: TGA, WDP, HDP, JXR
* Camera RAW formats:
Expand Down
1 change: 1 addition & 0 deletions src/JPEGView.Setup/Product.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\Applications\JPEGView.exe\SupportedTypes" Name=".heic" Value="" Type="string" />
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\Applications\JPEGView.exe\SupportedTypes" Name=".tga" Value="" Type="string" />
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\Applications\JPEGView.exe\SupportedTypes" Name=".qoi" Value="" Type="string" />
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\Applications\JPEGView.exe\SupportedTypes" Name=".psd" Value="" Type="string" />
<!-- csFileEndingsRAW -->
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\Applications\JPEGView.exe\SupportedTypes" Name=".pef" Value="" Type="string" />
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\Applications\JPEGView.exe\SupportedTypes" Name=".dng" Value="" Type="string" />
Expand Down
5 changes: 5 additions & 0 deletions src/JPEGView/EXIFDisplayCtl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ void CEXIFDisplayCtl::FillEXIFDataDisplay() {
}
if (pEXIFReader->GetAcquisitionTimePresent()) {
m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Acquisition date:")), pEXIFReader->GetAcquisitionTime());
} else if (pEXIFReader->GetDateTimePresent()) {
m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Date Time:")), pEXIFReader->GetDateTime());
} else {
const FILETIME* pFileTime = pFileList->CurrentModificationTime();
if (pFileTime != NULL) {
Expand Down Expand Up @@ -161,6 +163,9 @@ void CEXIFDisplayCtl::FillEXIFDataDisplay() {
if (pEXIFReader->GetISOSpeedPresent()) {
m_pEXIFDisplay->AddLine(CNLS::GetString(_T("ISO Speed:")), (int)pEXIFReader->GetISOSpeed());
}
if (pEXIFReader->GetSoftwarePresent()) {
m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Software:")), pEXIFReader->GetSoftware());
}
}
else if (pRawMetaData != NULL) {
if (pRawMetaData->GetAcquisitionTime().wYear > 1985) {
Expand Down
9 changes: 9 additions & 0 deletions src/JPEGView/EXIFReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ CEXIFReader::CEXIFReader(void* pApp1Block, EImageFormat eImageFormat)
: m_exposureTime(0, 0) {

memset(&m_acqDate, 0, sizeof(SYSTEMTIME));
memset(&m_dateTime, 0, sizeof(SYSTEMTIME));
m_bFlashFired = false;
m_bFlashFlagPresent = false;
m_dFocalLength = m_dExposureBias = m_dFNumber = UNKNOWN_DOUBLE_VALUE;
Expand Down Expand Up @@ -327,6 +328,14 @@ CEXIFReader::CEXIFReader(void* pApp1Block, EImageFormat eImageFormat)
}
}

uint8* pTagSoftware = FindTag(pIFD0, pLastIFD0, 0x0131, bLittleEndian);
ReadStringTag(m_sSoftware, pTagSoftware, pTIFFHeader, bLittleEndian);

uint8* pTagModDate = FindTag(pIFD0, pLastIFD0, 0x0132, bLittleEndian);
CString sModDate;
ReadStringTag(sModDate, pTagModDate, pTIFFHeader, bLittleEndian);
ParseDateString(m_dateTime, sModDate);

uint8* pTagEXIFIFD = FindTag(pIFD0, pLastIFD0, 0x8769, bLittleEndian);
if (pTagEXIFIFD == NULL) {
return;
Expand Down
7 changes: 7 additions & 0 deletions src/JPEGView/EXIFReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,15 @@ class CEXIFReader {
LPCTSTR GetCameraModel() { return m_sModel; }
LPCTSTR GetUserComment() { return m_sUserComment; }
LPCTSTR GetImageDescription() { return m_sImageDescription; }
LPCTSTR GetSoftware() { return m_sSoftware; }
bool GetCameraModelPresent() { return !m_sModel.IsEmpty(); }
bool GetSoftwarePresent() { return !m_sSoftware.IsEmpty(); }
// Date-time the picture was taken
const SYSTEMTIME& GetAcquisitionTime() { return m_acqDate; }
bool GetAcquisitionTimePresent() { return m_acqDate.wYear > 1600; }
// Date-time the picture was saved/modified (used by editing software)
const SYSTEMTIME& GetDateTime() { return m_dateTime; }
bool GetDateTimePresent() { return m_dateTime.wYear > 1600; }
// Exposure time
const Rational& GetExposureTime() { return m_exposureTime; }
bool GetExposureTimePresent() { return m_exposureTime.Denominator != 0; }
Expand Down Expand Up @@ -114,7 +119,9 @@ class CEXIFReader {
CString m_sModel;
CString m_sUserComment;
CString m_sImageDescription;
CString m_sSoftware;
SYSTEMTIME m_acqDate;
SYSTEMTIME m_dateTime;
Rational m_exposureTime;
double m_dExposureBias;
bool m_bFlashFired;
Expand Down
1 change: 1 addition & 0 deletions src/JPEGView/FileExtensionsDlg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ void CFileExtensionsDlg::FillFileExtensionsList() {
InsertExtension(_T("*.heif"), FormatHint(CNLS::GetString(_T("%s images")), _T("High Efficiency Image File")));
InsertExtension(_T("*.heic"), FormatHint(CNLS::GetString(_T("%s images")), _T("High Efficiency Image Container")));
InsertExtension(_T("*.qoi"), FormatHint(CNLS::GetString(_T("%s images")), _T("Quite OK Image")));
InsertExtension(_T("*.psd"), FormatHint(CNLS::GetString(_T("%s images")), _T("Photoshop Document")));
InsertExtensions(CSettingsProvider::This().FilesProcessedByWIC(), CNLS::GetString(_T("%s images (processed by Window Imaging Component - WIC)")));
InsertExtensions(CSettingsProvider::This().FileEndingsRAW(), CNLS::GetString(_T("%s camera raw images (embedded JPEGs only)")));
}
Expand Down
4 changes: 2 additions & 2 deletions src/JPEGView/FileList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ void CFileDesc::SetModificationDate(const FILETIME& lastModDate) {

// image file types supported internally (there are additional endings for RAW and WIC - these come from INI file)
// NOTE: when adding more supported filetypes, update installer to add another extension for "SupportedTypes"
static const int cnNumEndingsInternal = 14;
static const int cnNumEndingsInternal = 16;
static const TCHAR* csFileEndingsInternal[cnNumEndingsInternal] = {_T("jpg"), _T("jpeg"), _T("bmp"), _T("png"),
_T("tif"), _T("tiff"), _T("gif"), _T("webp"), _T("jxl"), _T("avif"), _T("heif"), _T("heic"), _T("tga"), _T("qoi")};
_T("tif"), _T("tiff"), _T("gif"), _T("webp"), _T("jxl"), _T("avif"), _T("heif"), _T("heic"), _T("tga"), _T("qoi"), _T("psd"), _T("psb") };
// supported camera RAW formats
static const TCHAR* csFileEndingsRAW = _T("*.pef;*.dng;*.crw;*.nef;*.cr2;*.mrw;*.rw2;*.orf;*.x3f;*.arw;*.kdc;*.nrw;*.dcr;*.sr2;*.raf");

Expand Down
2 changes: 2 additions & 0 deletions src/JPEGView/Helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,8 @@ EImageFormat GetImageFormat(LPCTSTR sFileName) {
return IF_TGA;
} else if (_tcsicmp(sEnding, _T("QOI")) == 0) {
return IF_QOI;
} else if (_tcsicmp(sEnding, _T("PSD")) == 0) {
return IF_PSD;
} else if (IsInFileEndingList(CSettingsProvider::This().FilesProcessedByWIC(), sEnding)) {
return IF_WIC;
} else if (IsInFileEndingList(CSettingsProvider::This().FileEndingsRAW(), sEnding)) {
Expand Down
28 changes: 28 additions & 0 deletions src/JPEGView/Helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,34 @@ namespace Helpers {
// Converts the system time to a string
CString SystemTimeToString(const SYSTEMTIME &time);

// pixel is ARGB, backgroundColor is BGR. Returns ARGB
static inline uint32 AlphaBlendBackground(uint32 pixel, COLORREF backgroundColor)
{
uint32 alpha = pixel & 0xFF000000;
if (alpha == 0xFF000000)
return pixel;

uint8 bg_r = GetRValue(backgroundColor);
uint8 bg_g = GetGValue(backgroundColor);
uint8 bg_b = GetBValue(backgroundColor);

if (alpha == 0) {
return (bg_r << 16) + (bg_g << 8) + (bg_b);
} else {
uint8 r = (pixel >> 16) & 0xFF;
uint8 g = (pixel >> 8) & 0xFF;
uint8 b = (pixel ) & 0xFF;
uint8 a = alpha >> 24;
uint8 one_minus_a = 255 - a;

return
0xFF000000 +
( (uint8)(((r * a + bg_r * one_minus_a) / 255.0) + 0.5) << 16) +
( (uint8)(((g * a + bg_g * one_minus_a) / 255.0) + 0.5) << 8) +
( (uint8)(((b * a + bg_b * one_minus_a) / 255.0) + 0.5) );
}
}

// Gets the image size to be used when fitting the image to screen, either using 'fit to screen'
// or 'fill with crop' method. If 'fill with crop' is used, the bLimitAR can be set to avoid
// filling when to less pixels remain visible
Expand Down
56 changes: 51 additions & 5 deletions src/JPEGView/ICCProfileTransform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,19 @@ bool ICCProfileTransform::DoTransform(void* transform, const void* inputBuffer,
return false;
cmsUInt32Number inFormat = cmsGetTransformInputFormat(transform);
int nchannels;
if (inFormat == TYPE_BGRA_8 || inFormat == TYPE_RGBA_8) {
nchannels = 4;
} else {
nchannels = 3;
switch (inFormat) {
case TYPE_BGRA_8:
case TYPE_RGBA_8:
case TYPE_ALab_8:
nchannels = 4;
break;
case TYPE_BGR_8:
case TYPE_RGB_8:
case TYPE_Lab_8:
nchannels = 3;
break;
default:
return false;
}
if (stride == 0)
stride = width * nchannels;
Expand All @@ -81,6 +90,39 @@ void ICCProfileTransform::DeleteTransform(void* transform)
cmsDeleteTransform(transform);
}

void* ICCProfileTransform::CreateLabTransform(PixelFormat format) {
cmsHTRANSFORM transform = NULL;
cmsHPROFILE hLabProfile = NULL;
try {
hLabProfile = cmsCreateLab4Profile(cmsD50_xyY());
if (sRGBProfile == NULL) {
sRGBProfile = cmsCreate_sRGBProfile();
}
} catch (...) {}

if (hLabProfile == NULL || sRGBProfile == NULL)
return NULL; // Could not create profile

// Create transform from CIELAB D50 (Photoshop "Lab mode") to sRGB
cmsUInt32Number flags = cmsFLAGS_BLACKPOINTCOMPENSATION | cmsFLAGS_COPY_ALPHA;
cmsUInt32Number inFormat, outFormat;
switch (format) {
case FORMAT_Lab:
inFormat = TYPE_Lab_8;
outFormat = TYPE_BGR_8;
break;
case FORMAT_ALab:
inFormat = TYPE_ALab_8;
outFormat = TYPE_ABGR_8;
break;
default:
cmsCloseProfile(hLabProfile);
return NULL;
}
transform = cmsCreateTransform(hLabProfile, inFormat, sRGBProfile, outFormat, INTENT_RELATIVE_COLORIMETRIC, flags);
cmsCloseProfile(hLabProfile);
return transform;
}

#else

Expand All @@ -96,4 +138,8 @@ bool ICCProfileTransform::DoTransform(void* /* transform */, const void* /* inpu

void ICCProfileTransform::DeleteTransform(void* /* transform */) { }

#endif
void* ICCProfileTransform::CreateLabTransform() {
return NULL;
}

#endif
4 changes: 4 additions & 0 deletions src/JPEGView/ICCProfileTransform.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ class ICCProfileTransform
FORMAT_RGBA,
FORMAT_BGR,
FORMAT_RGB,
FORMAT_Lab,
FORMAT_ALab,
};

// Create a transform from given ICC Profile to standard sRGB color space.
Expand All @@ -30,6 +32,8 @@ class ICCProfileTransform
// Free memory associated with the given transform
static void DeleteTransform(void* transform);

static void* CreateLabTransform(PixelFormat format);

private:
static void* sRGBProfile;
};
58 changes: 24 additions & 34 deletions src/JPEGView/ImageLoadThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#endif
#include "WEBPWrapper.h"
#include "QOIWrapper.h"
#include "PSDWrapper.h"
#include "MaxImageDef.h"


Expand All @@ -28,34 +29,6 @@ using namespace Gdiplus;
// static initializers
volatile int CImageLoadThread::m_curHandle = 0;

// pixel is ARGB, backgroundColor is BGR. Returns ARGB
static inline uint32 WebpAlphaBlendBackground(uint32 pixel, COLORREF backgroundColor)
{
uint32 alpha = pixel & 0xFF000000;
if (alpha == 0xFF000000)
return pixel;

uint8 bg_r = GetRValue(backgroundColor);
uint8 bg_g = GetGValue(backgroundColor);
uint8 bg_b = GetBValue(backgroundColor);

if (alpha == 0) {
return (bg_r << 16) + (bg_g << 8) + (bg_b);
} else {
uint8 r = (pixel >> 16) & 0xFF;
uint8 g = (pixel >> 8) & 0xFF;
uint8 b = (pixel ) & 0xFF;
uint8 a = alpha >> 24;
uint8 one_minus_a = 255 - a;

return
0xFF000000 +
( (uint8)(((r * a + bg_r * one_minus_a) / 255.0) + 0.5) << 16) +
( (uint8)(((g * a + bg_g * one_minus_a) / 255.0) + 0.5) << 8) +
( (uint8)(((b * a + bg_b * one_minus_a) / 255.0) + 0.5) );
}
}

/////////////////////////////////////////////////////////////////////////////////////////////
// static helpers
/////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -120,6 +93,8 @@ static EImageFormat GetImageFormat(LPCTSTR sFileName) {
}
} 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
Expand Down Expand Up @@ -392,6 +367,14 @@ void CImageLoadThread::ProcessRequest(CRequestBase& request) {
DeleteCachedAvifDecoder();
ProcessReadQOIRequest(&rq);
break;
case IF_PSD:
DeleteCachedGDIBitmap();
DeleteCachedWebpDecoder();
DeleteCachedPngDecoder();
DeleteCachedJxlDecoder();
DeleteCachedAvifDecoder();
ProcessReadPSDRequest(&rq);
break;
case IF_CameraRAW:
DeleteCachedGDIBitmap();
DeleteCachedWebpDecoder();
Expand Down Expand Up @@ -645,7 +628,7 @@ void CImageLoadThread::ProcessReadWEBPRequest(CRequest * request) {
// 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());
*pImage32++ = Helpers::AlphaBlendBackground(*pImage32, CSettingsProvider::This().ColorTransparency());

if (bHasAnimation) {
m_sLastWebpFileName = sFileName;
Expand Down Expand Up @@ -725,7 +708,7 @@ void CImageLoadThread::ProcessReadPNGRequest(CRequest* request) {
// 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());
*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);
Expand Down Expand Up @@ -800,7 +783,7 @@ void CImageLoadThread::ProcessReadJXLRequest(CRequest* request) {
// 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());
*pImage32++ = Helpers::AlphaBlendBackground(*pImage32, CSettingsProvider::This().ColorTransparency());

request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, pEXIFData, 4, 0, IF_JXL, bHasAnimation, request->FrameIndex, nFrameCount, nFrameTimeMs);
free(pEXIFData);
Expand Down Expand Up @@ -874,7 +857,7 @@ void CImageLoadThread::ProcessReadAVIFRequest(CRequest* request) {
// 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());
*pImage32++ = Helpers::AlphaBlendBackground(*pImage32, CSettingsProvider::This().ColorTransparency());

request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, pEXIFData, 4, 0, IF_AVIF, bHasAnimation, request->FrameIndex, nFrameCount, nFrameTimeMs);
free(pEXIFData);
Expand Down Expand Up @@ -935,7 +918,7 @@ void CImageLoadThread::ProcessReadHEIFRequest(CRequest* request) {
// 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());
*pImage32++ = Helpers::AlphaBlendBackground(*pImage32, CSettingsProvider::This().ColorTransparency());

request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, pEXIFData, nBPP, 0, IF_HEIF, false, request->FrameIndex, nFrameCount, nFrameTimeMs);
free(pEXIFData);
Expand Down Expand Up @@ -988,7 +971,7 @@ void CImageLoadThread::ProcessReadQOIRequest(CRequest* request) {
// 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());
*pImage32++ = Helpers::AlphaBlendBackground(*pImage32, CSettingsProvider::This().ColorTransparency());
}
request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, NULL, nBPP, 0, IF_QOI, false, 0, 1, 0);
}
Expand All @@ -1002,6 +985,13 @@ void CImageLoadThread::ProcessReadQOIRequest(CRequest* request) {
delete[] pBuffer;
}

void CImageLoadThread::ProcessReadPSDRequest(CRequest* request) {
request->Image = PsdReader::ReadImage(request->FileName, request->OutOfMemory);
if (request->Image == NULL && !request->OutOfMemory) {
request->Image = PsdReader::ReadThumb(request->FileName, request->OutOfMemory);
}
}

void CImageLoadThread::ProcessReadRAWRequest(CRequest * request) {
bool bOutOfMemory = false;
try {
Expand Down
1 change: 1 addition & 0 deletions src/JPEGView/ImageLoadThread.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ class CImageLoadThread : public CWorkThread
void ProcessReadAVIFRequest(CRequest* request);
void ProcessReadHEIFRequest(CRequest * request);
void ProcessReadQOIRequest(CRequest * request);
void ProcessReadPSDRequest(CRequest * request);
void ProcessReadRAWRequest(CRequest * request);
void ProcessReadGDIPlusRequest(CRequest * request);
void ProcessReadWICRequest(CRequest* request);
Expand Down
1 change: 1 addition & 0 deletions src/JPEGView/ImageProcessingTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ enum EImageFormat {
IF_HEIF,
IF_AVIF,
IF_QOI,
IF_PSD,
IF_WIC,
IF_CLIPBOARD,
IF_CameraRAW,
Expand Down
Loading

0 comments on commit 1a816d6

Please sign in to comment.