Skip to content

Commit

Permalink
Transparency
Browse files Browse the repository at this point in the history
* Increase to 3 modes of transparency (toggled via SHIFT+V) so we've a convenient and additional inverse TransparencyColor blend. The 3 modes are:
  * Blend using TransparencyColor setting (e.g.: black)
  * Checkerboard
  * Blend using inverse of TransparencyColor (e.g.: inverse of black is white)
  • Loading branch information
sdneon committed Jun 7, 2023
1 parent c13ad54 commit e28729a
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 25 deletions.
7 changes: 7 additions & 0 deletions src/JPEGView/Helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ namespace Helpers {
ZM_FillScreen
};

// Transparency modes
enum ETransparencyMode {
TP_BLEND = 0,
TP_CHECKERBOARD,
TP_BLEND_INVERSE
};

// Transition effects for full screen slideshow
enum ETransitionEffect {
TE_None,
Expand Down
39 changes: 24 additions & 15 deletions src/JPEGView/ImageLoadThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ static EImageFormat GetBitmapFormat(Gdiplus::Bitmap* pBitmap) {
}

static CJPEGImage* ConvertGDIPlusBitmapToJPEGImage(Gdiplus::Bitmap* pBitmap, int nFrameIndex, void* pEXIFData,
__int64 nJPEGHash, bool& isOutOfMemory, bool& isAnimatedGIF, bool bUseCheckerboard = false) {
__int64 nJPEGHash, bool& isOutOfMemory, bool& isAnimatedGIF, Helpers::ETransparencyMode nTransparencyMode = Helpers::TP_BLEND) {

isOutOfMemory = false;
isAnimatedGIF = false;
Expand Down Expand Up @@ -254,14 +254,19 @@ static CJPEGImage* ConvertGDIPlusBitmapToJPEGImage(Gdiplus::Bitmap* pBitmap, int
pBmTarget = new Gdiplus::Bitmap(pBitmap->GetWidth(), pBitmap->GetHeight(), PixelFormat32bppRGB);
pBmGraphics = new Gdiplus::Graphics(pBmTarget);
COLORREF bkColor = CSettingsProvider::This().ColorTransparency();
if (!bUseCheckerboard)
if (nTransparencyMode == Helpers::TP_BLEND)
{
//Somehow unlike WebpAlphaBlendBackground, needs to reverse the colour bytes!
//Gdiplus::SolidBrush bkBrush(Gdiplus::Color(GetRValue(bkColor), GetGValue(bkColor), GetBValue(bkColor)));
Gdiplus::SolidBrush bkBrush(Gdiplus::Color(GetBValue(bkColor), GetGValue(bkColor), GetRValue(bkColor)));
pBmGraphics->FillRectangle(&bkBrush, 0, 0, pBmTarget->GetWidth(), pBmTarget->GetHeight());
}
else
else if (nTransparencyMode == Helpers::TP_BLEND_INVERSE)
{
Gdiplus::SolidBrush bkBrush(Gdiplus::Color(~GetBValue(bkColor), ~GetGValue(bkColor), ~GetRValue(bkColor)));
pBmGraphics->FillRectangle(&bkBrush, 0, 0, pBmTarget->GetWidth(), pBmTarget->GetHeight());
}
else //if (nTransparencyMode == Helpers::TP_CHECKERBOARD)
{
Gdiplus::HatchBrush bkBrush(HatchStyleLargeCheckerBoard, Gdiplus::Color(0xffc0c0c0), Gdiplus::Color(0xffffffff));
pBmGraphics->FillRectangle(&bkBrush, 0, 0, pBmTarget->GetWidth(), pBmTarget->GetHeight());
Expand Down Expand Up @@ -585,7 +590,7 @@ void CImageLoadThread::ProcessReadJPEGRequest(CRequest* request) {
Gdiplus::Bitmap* pBitmap = Gdiplus::Bitmap::FromStream(pStream, CSettingsProvider::This().UseEmbeddedColorProfiles());
bool isOutOfMemory, isAnimatedGIF;
request->Image = ConvertGDIPlusBitmapToJPEGImage(pBitmap, 0, Helpers::FindEXIFBlock(pBuffer, nFileSize),
Helpers::CalculateJPEGFileHash(pBuffer, nFileSize), isOutOfMemory, isAnimatedGIF, request->ProcessParams.UseCheckerboard);
Helpers::CalculateJPEGFileHash(pBuffer, nFileSize), isOutOfMemory, isAnimatedGIF, request->ProcessParams.TransparencyMode);
request->OutOfMemory = request->Image == NULL && isOutOfMemory;
if (request->Image != NULL) {
request->Image->SetJPEGComment(Helpers::GetJPEGComment(pBuffer, nFileSize));
Expand Down Expand Up @@ -640,11 +645,15 @@ void CImageLoadThread::ProcessReadJPEGRequest(CRequest* request) {
if (hFileBuffer) ::GlobalFree(hFileBuffer);
}

void CImageLoadThread::BlendAlpha(uint32* pImage32, int nWidth, int nHeight, bool bUseCheckerboard)
void CImageLoadThread::BlendAlpha(uint32* pImage32, int nWidth, int nHeight, Helpers::ETransparencyMode nTransparencyMode)
{
COLORREF nTranparency = CSettingsProvider::This().ColorTransparency();
if (!bUseCheckerboard)
if (nTransparencyMode != Helpers::TP_CHECKERBOARD)
{
if (nTransparencyMode == Helpers::TP_BLEND_INVERSE)
{
nTranparency = ~(nTranparency & 0xffffff);
}
for (int i = 0; i < nWidth * nHeight; ++i)
*pImage32++ = WebpAlphaBlendBackground(*pImage32, nTranparency);
} else {
Expand Down Expand Up @@ -702,7 +711,7 @@ void CImageLoadThread::ProcessReadPNGRequest(CRequest* request) {
if (bHasAnimation)
m_sLastPngFileName = sFileName;
// Multiply alpha value into each AABBGGRR pixel
BlendAlpha((uint32*)pPixelData, nWidth, nHeight, request->ProcessParams.UseCheckerboard);
BlendAlpha((uint32*)pPixelData, nWidth, nHeight, request->ProcessParams.TransparencyMode);
request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, NULL, 4, 0, IF_PNG, bHasAnimation, request->FrameIndex, nFrameCount, nFrameTimeMs);
}
else {
Expand Down Expand Up @@ -854,7 +863,7 @@ void CImageLoadThread::ProcessReadAVIFRequest(CRequest* request) {
if (bHasAnimation) {
m_sLastAvifFileName = sFileName;
}
BlendAlpha((uint32*)(rgb.pixels), m_avifDecoder->image->width, m_avifDecoder->image->height, request->ProcessParams.UseCheckerboard);
BlendAlpha((uint32*)(rgb.pixels), m_avifDecoder->image->width, m_avifDecoder->image->height, request->ProcessParams.TransparencyMode);
request->Image = new CJPEGImage(m_avifDecoder->image->width, m_avifDecoder->image->height, rgb.pixels, 0, 4, 0, IF_AVIF, bHasAnimation, request->FrameIndex, m_avifDecoder->imageCount, nFrameTimeMs);
if (!bHasAnimation) DeleteCachedAvifDecoder();
bSuccess = true;
Expand Down Expand Up @@ -917,7 +926,7 @@ void CImageLoadThread::ProcessReadBMPRequest(CRequest* request) {

void CImageLoadThread::ProcessReadTGARequest(CRequest* request) {
bool bOutOfMemory;
request->Image = CReaderTGA::ReadTgaImage(request->FileName, CSettingsProvider::This().ColorTransparency(), bOutOfMemory, request->ProcessParams.UseCheckerboard);
request->Image = CReaderTGA::ReadTgaImage(request->FileName, CSettingsProvider::This().ColorTransparency(), bOutOfMemory, request->ProcessParams.TransparencyMode);
if (bOutOfMemory) {
request->OutOfMemory = true;
}
Expand Down Expand Up @@ -990,7 +999,7 @@ void CImageLoadThread::ProcessReadWEBPRequest(CRequest* request) {
if ((bHasAnimation && Webp_Dll_AnimDecodeBGRAInto((uint8*)pBuffer, nFileSize, pPixelData, nStride * nHeight, nFrameCount, nFrameTimeMs)) ||
(!bHasAnimation && Webp_Dll_DecodeBGRAInto((uint8*)pBuffer, nFileSize, pPixelData, nStride * nHeight, nStride))) {
// Multiply alpha value into each AABBGGRR pixel
BlendAlpha((uint32*)pPixelData, nWidth, nHeight, request->ProcessParams.UseCheckerboard);
BlendAlpha((uint32*)pPixelData, nWidth, nHeight, request->ProcessParams.TransparencyMode);
request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, NULL, 4, 0, IF_WEBP, bHasAnimation, request->FrameIndex, nFrameCount, nFrameTimeMs);
}
else {
Expand Down Expand Up @@ -1066,7 +1075,7 @@ void CImageLoadThread::ProcessReadJXLRequest(CRequest* request) {
if (bHasAnimation)
m_sLastJxlFileName = sFileName;
// Multiply alpha value into each AABBGGRR pixel
BlendAlpha((uint32*)pPixelData, nWidth, nHeight, request->ProcessParams.UseCheckerboard);
BlendAlpha((uint32*)pPixelData, nWidth, nHeight, request->ProcessParams.TransparencyMode);
request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, NULL, 4, 0, IF_JXL, bHasAnimation, request->FrameIndex, nFrameCount, nFrameTimeMs);
} else {
DeleteCachedJxlDecoder();
Expand Down Expand Up @@ -1117,7 +1126,7 @@ void CImageLoadThread::ProcessReadHEIFRequest(CRequest* request) {
uint8* pPixelData = (uint8*)HeifReader::ReadImage(nWidth, nHeight, nBPP, nFrameCount, request->OutOfMemory, request->FrameIndex, pBuffer, nFileSize);
if (pPixelData != NULL) {
// Multiply alpha value into each AABBGGRR pixel
BlendAlpha((uint32*)pPixelData, nWidth, nHeight, request->ProcessParams.UseCheckerboard);
BlendAlpha((uint32*)pPixelData, nWidth, nHeight, request->ProcessParams.TransparencyMode);

request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, NULL, nBPP, 0, IF_HEIF, false, request->FrameIndex, nFrameCount, nFrameTimeMs);
}
Expand Down Expand Up @@ -1166,7 +1175,7 @@ void CImageLoadThread::ProcessReadQOIRequest(CRequest* request) {
if (pPixelData != NULL) {
if (nBPP == 4) {
// Multiply alpha value into each AABBGGRR pixel
BlendAlpha((uint32*)pPixelData, nWidth, nHeight, request->ProcessParams.UseCheckerboard);
BlendAlpha((uint32*)pPixelData, nWidth, nHeight, request->ProcessParams.TransparencyMode);
}
request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, NULL, nBPP, 0, IF_QOI, false, 0, 1, 0);
}
Expand Down Expand Up @@ -1208,7 +1217,7 @@ void CImageLoadThread::ProcessReadGDIPlusRequest(CRequest* request) {
m_sLastFileName = sFileName;
}
bool isOutOfMemory, isAnimatedGIF;
request->Image = ConvertGDIPlusBitmapToJPEGImage(pBitmap, request->FrameIndex, NULL, 0, isOutOfMemory, isAnimatedGIF, request->ProcessParams.UseCheckerboard);
request->Image = ConvertGDIPlusBitmapToJPEGImage(pBitmap, request->FrameIndex, NULL, 0, isOutOfMemory, isAnimatedGIF, request->ProcessParams.TransparencyMode);
request->OutOfMemory = request->Image == NULL && isOutOfMemory;
if (!isAnimatedGIF) {
DeleteCachedGDIBitmap();
Expand Down Expand Up @@ -1237,7 +1246,7 @@ void CImageLoadThread::ProcessReadWICRequest(CRequest* request) {
uint32 nWidth, nHeight;
unsigned char* pDIB = LoadImageWithWIC(sFileName, &alloc, &dealloc, &nWidth, &nHeight);
if (pDIB != NULL) {
BlendAlpha((uint32*)(pDIB), nWidth, nHeight, request->ProcessParams.UseCheckerboard);
BlendAlpha((uint32*)(pDIB), nWidth, nHeight, request->ProcessParams.TransparencyMode);
request->Image = new CJPEGImage(nWidth, nHeight, pDIB, NULL, 4, 0, IF_WIC, false, 0, 1, 0);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/JPEGView/ImageLoadThread.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class CImageLoadThread : public CWorkThread
void DeleteCachedJxlDecoder();
void DeleteCachedAvifDecoder();

void BlendAlpha(uint32* pImage32, int nWidth, int nHeight, bool bUseCheckerboard);
void BlendAlpha(uint32* pImage32, int nWidth, int nHeight, Helpers::ETransparencyMode TransparencyMode);
void ProcessReadJPEGRequest(CRequest * request);
void ProcessReadPNGRequest(CRequest* request);
void ProcessReadAVIFRequest(CRequest* request);
Expand Down
23 changes: 18 additions & 5 deletions src/JPEGView/MainDlg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ static EProcessingFlags _SetLandscapeModeFlags(EProcessingFlags eFlags) {
CMainDlg::CMainDlg(bool bForceFullScreen):
m_bSelectMode(false),
m_bSingleZoom(false),
m_bUseCheckerboard(true),
m_nTransparencyMode(Helpers::TP_BLEND),
m_hToastFont(0),
m_strToast(""),
m_nImageRetryCnt(0)
Expand Down Expand Up @@ -2583,9 +2583,22 @@ void CMainDlg::ExecuteCommand(int nCommand) {
AdjustContrast((nCommand == IDM_CONTRAST_INC)? CONTRAST_INC : -CONTRAST_INC);
break;
case IDM_TOGGLE_TRANSPARENCY:
m_bUseCheckerboard = !m_bUseCheckerboard;
if (m_nTransparencyMode == Helpers::TP_BLEND)
{
m_nTransparencyMode = Helpers::TP_CHECKERBOARD;
SetToast(_T("Transparency: Checkerboard"));
}
else if (m_nTransparencyMode == Helpers::TP_CHECKERBOARD)
{
m_nTransparencyMode = Helpers::TP_BLEND_INVERSE;
SetToast(_T("Transparency: Inverse Blend"));
}
else //if (m_nTransparencyMode == Helpers::TP_BLEND_INVERSE)
{
m_nTransparencyMode = Helpers::TP_BLEND;
SetToast(_T("Transparency: Blend"));
}
ReloadImage(true);
SetToast(m_bUseCheckerboard ? _T("Transparency: Checkerboard") : _T("Transparency: Blend"));
break;
case IDM_GAMMA_INC:
case IDM_GAMMA_DEC:
Expand Down Expand Up @@ -3461,15 +3474,15 @@ CProcessParams CMainDlg::CreateProcessParams(bool bNoProcessingAfterLoad) {
m_dZoomKept,
eAutoZoomMode,
m_offsetKept,
m_bUseCheckerboard,
m_nTransparencyMode,
_SetLandscapeModeParams(m_bLandscapeMode, *m_pImageProcParamsKept),
SetProcessingFlag(_SetLandscapeModeFlags(m_eProcessingFlagsKept), PFLAG_NoProcessingAfterLoad, bNoProcessingAfterLoad));
} else {
m_isUserFitToScreen = false;
CSettingsProvider& sp = CSettingsProvider::This();
return CProcessParams(nClientWidth, nClientHeight,
CMultiMonitorSupport::GetMonitorRect(m_hWnd).Size(),
CRotationParams(0), 0, -1, eAutoZoomMode, CPoint(0, 0), m_bUseCheckerboard,
CRotationParams(0), 0, -1, eAutoZoomMode, CPoint(0, 0), m_nTransparencyMode,
_SetLandscapeModeParams(m_bLandscapeMode, GetDefaultProcessingParams()),
SetProcessingFlag(_SetLandscapeModeFlags(GetDefaultProcessingFlags(m_bLandscapeMode)), PFLAG_NoProcessingAfterLoad, bNoProcessingAfterLoad));
}
Expand Down
2 changes: 1 addition & 1 deletion src/JPEGView/MainDlg.h
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ class CMainDlg : public CDialogImpl<CMainDlg>
double m_dLastImageDisplayTime;
bool m_bSelectMode, m_bSingleZoom,
m_bMinFilesize, m_bHideHidden;
bool m_bUseCheckerboard; //for transparent background
Helpers::ETransparencyMode m_nTransparencyMode; //for transparent background
bool m_bWindowBorderless;
bool m_bAlwaysOnTop;
//Toast stuff
Expand Down
6 changes: 3 additions & 3 deletions src/JPEGView/ProcessParams.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ class CProcessParams {
int nUserRotation,
double dZoom,
Helpers::EAutoZoomMode eAutoZoomMode, CPoint offsets,
bool bUseCheckerboard,
Helpers::ETransparencyMode nTransparencyMode,
const CImageProcessingParams& imageProcParams,
EProcessingFlags eProcFlags) : ImageProcParams(imageProcParams), RotationParams(rotationParams) {
UserRotation = nUserRotation;
Expand All @@ -165,7 +165,7 @@ class CProcessParams {
Zoom = dZoom;
AutoZoomMode = eAutoZoomMode;
Offsets = offsets;
UseCheckerboard = bUseCheckerboard;
TransparencyMode = nTransparencyMode;
ProcFlags = eProcFlags;
}

Expand All @@ -176,7 +176,7 @@ class CProcessParams {
int UserRotation;
double Zoom;
CPoint Offsets;
bool UseCheckerboard; //for transparent background
Helpers::ETransparencyMode TransparencyMode; //for transparent background
CImageProcessingParams ImageProcParams;
EProcessingFlags ProcFlags;
Helpers::EAutoZoomMode AutoZoomMode;
Expand Down

0 comments on commit e28729a

Please sign in to comment.