diff --git a/src/JPEGView/Config/KeyMap-README.html b/src/JPEGView/Config/KeyMap-README.html index 31f754dc..00b9d113 100644 --- a/src/JPEGView/Config/KeyMap-README.html +++ b/src/JPEGView/Config/KeyMap-README.html @@ -100,6 +100,10 @@ content: "toggle show file and EXIF info box in top, left corner" } +.IDM_SHOW_FILEINFO_HISTOGRAM:before { + content: "toggle show histogram and details in EXIF info box in top, left corner" +} + .IDM_SHOW_FILENAME:before { content: "toggle show file name on top of screen" } @@ -336,6 +340,14 @@ content: "edit user configuration" } +.IDM_EDIT_GLOBAL_KEYMAP:before { + content: "edit global keymap" +} + +.IDM_EDIT_USER_KEYMAP:before { + content: "edit user keymap" +} + .IDM_BACKUP_PARAMDB:before { content: "backup parameter DB" } @@ -489,7 +501,7 @@

@@ -728,6 +740,12 @@

+ IDM_SHOW_FILEINFO_HISTOGRAM + + + + + IDM_SHOW_FILENAME @@ -1082,6 +1100,18 @@

+ IDM_EDIT_GLOBAL_KEYMAP + + + + + + IDM_EDIT_USER_KEYMAP + + + + + IDM_BACKUP_PARAMDB @@ -1255,4 +1285,4 @@

- \ No newline at end of file + diff --git a/src/JPEGView/Config/symbols.km b/src/JPEGView/Config/symbols.km index ed123970..55f03efd 100644 --- a/src/JPEGView/Config/symbols.km +++ b/src/JPEGView/Config/symbols.km @@ -32,6 +32,7 @@ #define IDM_SET_WALLPAPER_ORIG 2770 #define IDM_SET_WALLPAPER_DISPLAY 2774 #define IDM_SHOW_FILEINFO 2800 +#define IDM_SHOW_FILEINFO_HISTOGRAM 2830 #define IDM_SHOW_FILENAME 3000 #define IDM_SHOW_NAVPANEL 3100 #define IDM_NEXT 4000 @@ -89,6 +90,8 @@ #define IDM_AUTO_ZOOM_FILL_NO_ZOOM 12510 #define IDM_AUTO_ZOOM_FIT 12520 #define IDM_AUTO_ZOOM_FILL 12530 +#define IDM_EDIT_GLOBAL_KEYMAP 12590 +#define IDM_EDIT_USER_KEYMAP 12695 #define IDM_EDIT_GLOBAL_CONFIG 12600 #define IDM_EDIT_USER_CONFIG 12610 #define IDM_BACKUP_PARAMDB 12620 diff --git a/src/JPEGView/EXIFDisplay.cpp b/src/JPEGView/EXIFDisplay.cpp index ae510804..9561af71 100644 --- a/src/JPEGView/EXIFDisplay.cpp +++ b/src/JPEGView/EXIFDisplay.cpp @@ -28,7 +28,7 @@ static CRect InflateRect(const CRect& rect, float fAmount) { return r; } -CEXIFDisplay::CEXIFDisplay(HWND hWnd, INotifiyMouseCapture* pNotifyMouseCapture) : CPanel(hWnd, pNotifyMouseCapture, true, true) { +CEXIFDisplay::CEXIFDisplay(HWND hWnd, INotifiyMouseCapture* pNotifyMouseCapture) : CPanel(hWnd, pNotifyMouseCapture, false, true) { m_bShowHistogram = false; m_nGap = (int)(m_fDPIScale * 10); m_nTab1 = 0; @@ -44,8 +44,10 @@ CEXIFDisplay::CEXIFDisplay(HWND hWnd, INotifiyMouseCapture* pNotifyMouseCapture) m_nNoHistogramSize = CSize(0, 0); m_pHistogram = NULL; - AddUserPaintButton(ID_btnShowHideHistogram, &ShowHistogramTooltip, &PaintShowHistogramBtn, NULL, this, this); - AddUserPaintButton(ID_btnClose, CNLS::GetString(_T("Close")), &PaintCloseBtn, NULL, this, this); + LPCTSTR nullTooltip = NULL; // Needed because passing NULL directly to overloaded function makes it ambiguous + + AddUserPaintButton(ID_btnShowHideHistogram, nullTooltip, &PaintShowHistogramBtn, NULL, this, this); + AddUserPaintButton(ID_btnClose, nullTooltip, &PaintCloseBtn, NULL, this, this); CURLCtrl* pLinkLocation = AddURL(ID_urlLocation, _T(""), _T(""), false); pLinkLocation->SetShow(false, false); } @@ -105,8 +107,8 @@ void CEXIFDisplay::SetGPSLocation(LPCTSTR sLocation, LPCTSTR sURL) { pLinkLocation->SetShow(true, false); } -void CEXIFDisplay::AddLine(LPCTSTR sDescription, LPCTSTR sValue, bool valueIsURL) { - m_lines.push_back(TextLine(CopyStrAlloc(sDescription), CopyStrAlloc(sValue), valueIsURL)); +void CEXIFDisplay::AddLine(LPCTSTR sDescription, LPCTSTR sValue, bool valueIsURL, bool sameLine) { + m_lines.push_back(TextLine(CopyStrAlloc(sDescription), CopyStrAlloc(sValue), valueIsURL, sameLine && !m_bShowHistogram)); } void CEXIFDisplay::AddLine(LPCTSTR sDescription, double dValue, int nDigits) { @@ -171,7 +173,8 @@ CRect CEXIFDisplay::PanelRect() { m_nPrefixLength = 0; m_nTitleWidth = 0; int nTitleLength = 0; - int nMaxLength1 = 0, nMaxLength2 = 0; + int nMaxLengthColumn1 = 0, nMaxLengthColumn2 = 0; + int nLastLineLengthColumn2 = 0; CSize size; if (m_sPrefix != NULL) { ::GetTextExtentPoint32(dc, m_sPrefix, (int)_tcslen(m_sPrefix), &size); @@ -201,33 +204,40 @@ CRect CEXIFDisplay::PanelRect() { int nLen1 = 0, nLen2 = 0; std::list::iterator iter; + int nLineCountExcludingSameLineLines = 0; for (iter = m_lines.begin( ); iter != m_lines.end( ); iter++ ) { - if (iter->Desc != NULL) { + if (!(iter->SameLine)) { + nLineCountExcludingSameLineLines += 1; + } + if (iter->Desc != NULL && m_bShowHistogram) { ::GetTextExtentPoint32(dc, iter->Desc, (int)_tcslen(iter->Desc), &size); m_nLineHeight = max(m_nLineHeight, size.cy); - nMaxLength1 = max(nMaxLength1, size.cx); + nMaxLengthColumn1 = max(nMaxLengthColumn1, size.cx); } nLen2 = nLen1; nLen1 = 0; if (iter->Value != NULL) { ::GetTextExtentPoint32(dc, iter->Value, (int)_tcslen(iter->Value), &size); m_nLineHeight = max(m_nLineHeight, size.cy); - nMaxLength2 = max(nMaxLength2, size.cx); + nMaxLengthColumn2 = max(nMaxLengthColumn2, size.cx); + nLastLineLengthColumn2 = size.cx; nLen1 = size.cx; } } int nButtonWidth = (int)(m_fDPIScale * BUTTON_SIZE); - bool bNeedsExpansionForButton = (nMaxLength2 - max(nLen1, nLen2)) < nButtonWidth + m_nGap; - int nNeededWidthNoBorders = max(nTitleLength, nMaxLength1 + nMaxLength2 + m_nGap) + (bNeedsExpansionForButton ? m_nGap + nButtonWidth : 0); + int columnGap = m_bShowHistogram ? m_nGap : 0; + int nNeededWidthNoBorders = max(max(nTitleLength, nMaxLengthColumn1 + nMaxLengthColumn2 + m_nGap), nMaxLengthColumn1 + columnGap + nLastLineLengthColumn2 + m_nGap/2 + nButtonWidth); int nExpansionX = 0, nExpansionY = 0; if (m_bShowHistogram) { nExpansionX = max(0, HelpersGUI::ScaleToScreen(256) - nNeededWidthNoBorders); nExpansionY = HelpersGUI::ScaleToScreen(HISTOGRAM_HEIGHT) + m_nGap; } + + m_size = CSize(nNeededWidthNoBorders + m_nGap*2 + nExpansionX, - m_nTitleHeight + (int)m_lines.size()*m_nLineHeight + m_nGap * 2 + nExpansionY); + m_nTitleHeight + nLineCountExcludingSameLineLines*m_nLineHeight + m_nGap * 2 + nExpansionY); if (m_sComment != NULL) { CRect rectComment(0, 0, m_size.cx - m_nGap*2, HelpersGUI::ScaleToScreen(200)); @@ -237,7 +247,10 @@ CRect CEXIFDisplay::PanelRect() { } m_nNoHistogramSize = CSize(m_size.cx - nExpansionX, m_size.cy - nExpansionY); - m_nTab1 = nMaxLength1 + m_nGap; + m_nTab1 = nMaxLengthColumn1 + m_nGap; + if (!m_bShowHistogram) { + m_nTab1 = 0; + } } return CRect(m_pos, m_size); } @@ -289,7 +302,10 @@ void CEXIFDisplay::OnPaint(CDC & dc, const CPoint& offset) { std::list::iterator iter; for (iter = m_lines.begin( ); iter != m_lines.end( ); iter++ ) { - if (iter->Desc != NULL) { + if (iter->SameLine) { + nRunningY -= m_nLineHeight; + } + if (iter->Desc != NULL && m_bShowHistogram) { ::TextOut(dc, nX + m_nGap, nRunningY, iter->Desc, (int)_tcslen(iter->Desc)); } if (iter->Value != NULL) { diff --git a/src/JPEGView/EXIFDisplay.h b/src/JPEGView/EXIFDisplay.h index a8f7352f..6224fb22 100644 --- a/src/JPEGView/EXIFDisplay.h +++ b/src/JPEGView/EXIFDisplay.h @@ -25,7 +25,7 @@ class CEXIFDisplay : public CPanel void AddTitle(LPCTSTR sTitle); void SetComment(LPCTSTR sComment); void SetGPSLocation(LPCTSTR sLocation, LPCTSTR sURL); - void AddLine(LPCTSTR sDescription, LPCTSTR sValue, bool valueIsURL = false); + void AddLine(LPCTSTR sDescription, LPCTSTR sValue, bool valueIsURL = false, bool sameLine = false); void AddLine(LPCTSTR sDescription, double dValue, int nDigits); void AddLine(LPCTSTR sDescription, int nValue); void AddLine(LPCTSTR sDescription, const SYSTEMTIME &time); // time is in local time @@ -49,15 +49,17 @@ class CEXIFDisplay : public CPanel private: struct TextLine { - TextLine(LPCTSTR desc, LPCTSTR value, bool valueIsURL = false) { + TextLine(LPCTSTR desc, LPCTSTR value, bool valueIsURL = false, bool sameLine = false) { Desc = desc; Value = value; ValueIsURL = valueIsURL; + SameLine = sameLine; } LPCTSTR Desc; LPCTSTR Value; bool ValueIsURL; + bool SameLine; }; bool m_bShowHistogram; diff --git a/src/JPEGView/EXIFDisplayCtl.cpp b/src/JPEGView/EXIFDisplayCtl.cpp index 65cb4ae4..05c87c2f 100644 --- a/src/JPEGView/EXIFDisplayCtl.cpp +++ b/src/JPEGView/EXIFDisplayCtl.cpp @@ -19,9 +19,9 @@ static int GetFileNameHeight(HDC dc) { static CString CreateGPSString(GPSCoordinate* latitude, GPSCoordinate* longitude) { const int BUFF_SIZE = 96; TCHAR buff[BUFF_SIZE]; - _stprintf_s(buff, BUFF_SIZE, _T("%.0f° %.0f' %.0f'' %s / %.0f° %.0f' %.0f'' %s"), - latitude->Degrees, latitude->Minutes, latitude->Seconds, latitude->GetReference(), - longitude->Degrees, longitude->Minutes, longitude->Seconds, longitude->GetReference()); + _stprintf_s(buff, BUFF_SIZE, _T("%s%.0f°%.0f′%.0f″ %s%.0f°%.0f′%.0f″"), + latitude->GetReference(), latitude->Degrees, latitude->Minutes, latitude->Seconds, + longitude->GetReference(), longitude->Degrees, longitude->Minutes, longitude->Seconds); return CString(buff); } @@ -57,7 +57,8 @@ CEXIFDisplayCtl::CEXIFDisplayCtl(CMainDlg* pMainDlg, CPanel* pImageProcPanel) : CButtonCtrl* pCloseBtn = m_pEXIFDisplay->GetControl(CEXIFDisplay::ID_btnClose); pCloseBtn->SetButtonPressedHandler(&OnClose, this); pCloseBtn->SetShow(false); - m_pEXIFDisplay->SetShowHistogram(CSettingsProvider::This().ShowHistogram()); + m_bShowHistogram = CSettingsProvider::This().ShowHistogram(); + m_pEXIFDisplay->SetShowHistogram(m_bShowHistogram); } CEXIFDisplayCtl::~CEXIFDisplayCtl() { @@ -80,6 +81,18 @@ void CEXIFDisplayCtl::SetActive(bool bActive) { SetVisible(bActive); } +void CEXIFDisplayCtl::SetShowHistogram(bool bShowHistogram) { + m_bShowHistogram = bShowHistogram; + if (m_bShowHistogram) { + ::OutputDebugString(_T("show!")); + } + else { + ::OutputDebugString(_T("hide!")); + } + m_pEXIFDisplay->SetShowHistogram(m_bShowHistogram); + InvalidateMainDlg(); +} + void CEXIFDisplayCtl::AfterNewImageLoaded() { m_pEXIFDisplay->ClearTexts(); m_pEXIFDisplay->SetHistogram(NULL); @@ -96,6 +109,25 @@ void CEXIFDisplayCtl::OnPrePaintMainDlg(HDC hPaintDC) { } } +CString CEXIFDisplayCtl::FormatRational(Rational rational) { + CString sFormatted; + if (rational.Denominator == 1) { + sFormatted.Format(_T("%d"), rational.Numerator); + } + else if (rational.Numerator > 9) { + if (rational.Numerator * 3 < rational.Denominator) { + sFormatted.Format(_T("1/%d"), rational.Denominator/ rational.Numerator); + } + else { + sFormatted.Format(_T("%g"), double(rational.Numerator) / rational.Denominator); + } + } + else { + sFormatted.Format(_T("%d/%d"), rational.Numerator, rational.Denominator); + } + return sFormatted; +} + void CEXIFDisplayCtl::FillEXIFDataDisplay() { m_pEXIFDisplay->ClearTexts(); @@ -114,8 +146,40 @@ void CEXIFDisplayCtl::FillEXIFDataDisplay() { LPCTSTR sComment = NULL; m_pEXIFDisplay->AddPrefix(sPrefix); m_pEXIFDisplay->AddTitle(sFileTitle); - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Image width:")), CurrentImage()->OrigWidth()); - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Image height:")), CurrentImage()->OrigHeight()); + + CString sFormattedSize; + sFormattedSize.Format(_T("%d × %d"), CurrentImage()->OrigWidth(), CurrentImage()->OrigHeight()); + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Size:")), sFormattedSize); + + CString secondColPadding = _T(""); + if (!m_pEXIFDisplay->GetShowHistogram()) { + for (int i = 0; i < (max(2 * sFormattedSize.GetLength(), 18) + 3); i++) { + secondColPadding += _T(' '); + }; + } + + CString sFileSize = _T(""); + if (!CurrentImage()->IsClipboardImage() && pFileList->Current() != NULL) { + HANDLE hFile = ::CreateFile(pFileList->Current(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hFile != INVALID_HANDLE_VALUE) { + __int64 fileSize = 0; + ::GetFileSizeEx(hFile, (PLARGE_INTEGER)&fileSize); + ::CloseHandle(hFile); + if (fileSize > 0) { + const TCHAR* units[] = { _T("Bytes"), _T("KiB"), _T("MiB"), _T("GiB") }; + double value = fileSize; + int exponent = 0; + while (value >= 1024 && exponent < sizeof(units) / sizeof(units[0]) - 1) { + value /= 1024.0; + exponent++; + } + sFileSize.Format(_T("%s%.1f %s"), secondColPadding, value, units[exponent]); + } + } + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("File size:")), sFileSize, false, true); + } + + bool bShowMoreDetails = m_pEXIFDisplay->GetShowHistogram(); if (!CurrentImage()->IsClipboardImage()) { CEXIFReader* pEXIFReader = CurrentImage()->GetEXIFReader(); CRawMetadata* pRawMetaData = CurrentImage()->GetRawMetadata(); @@ -125,85 +189,113 @@ void CEXIFDisplayCtl::FillEXIFDataDisplay() { sComment = pEXIFReader->GetImageDescription(); } if (pEXIFReader->GetAcquisitionTimePresent()) { - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Acquisition date:")), pEXIFReader->GetAcquisitionTime()); + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Acquired:")), pEXIFReader->GetAcquisitionTime()); } else if (pEXIFReader->GetDateTimePresent()) { - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Exif Date Time:")), pEXIFReader->GetDateTime()); + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Exif date:")), pEXIFReader->GetDateTime()); } else { const FILETIME* pFileTime = pFileList->CurrentModificationTime(); if (pFileTime != NULL) { - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Modification date:")), *pFileTime); - } - } - if (pEXIFReader->IsGPSInformationPresent()) { - CString sGPSLocation = CreateGPSString(pEXIFReader->GetGPSLatitude(), pEXIFReader->GetGPSLongitude()); - m_pEXIFDisplay->SetGPSLocation(sGPSLocation, CreateGPSURL(pEXIFReader->GetGPSLatitude(), pEXIFReader->GetGPSLongitude())); - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Location:")), sGPSLocation, true); - if (pEXIFReader->IsGPSAltitudePresent()) { - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Altitude (m):")), pEXIFReader->GetGPSAltitude(), 0); + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Modified:")), *pFileTime); } } - if (pEXIFReader->GetCameraModelPresent()) { - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Camera model:")), pEXIFReader->GetCameraModel()); + + if (pEXIFReader->GetISOSpeedPresent()) { + CString sFormattedIso; + sFormattedIso.Format(_T("ISO %d"), (int)pEXIFReader->GetISOSpeed()); + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("ISO speed:")), sFormattedIso); } if (pEXIFReader->GetExposureTimePresent()) { - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Exposure time (s):")), pEXIFReader->GetExposureTime()); - } - if (pEXIFReader->GetExposureBiasPresent()) { - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Exposure bias (EV):")), pEXIFReader->GetExposureBias(), 2); - } - if (pEXIFReader->GetFlashFiredPresent()) { - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Flash fired:")), pEXIFReader->GetFlashFired() ? CNLS::GetString(_T("yes")) : CNLS::GetString(_T("no"))); + Rational exposure = pEXIFReader->GetExposureTime(); + CString sFormattedExposureTime; + sFormattedExposureTime.Format(_T("%s%s sec"), secondColPadding, FormatRational(exposure)); + bool bExposureTimeSameLine = pEXIFReader->GetISOSpeedPresent(); + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Exposure time:")), sFormattedExposureTime, false, bExposureTimeSameLine); } if (pEXIFReader->GetFocalLengthPresent()) { - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Focal length (mm):")), pEXIFReader->GetFocalLength(), 1); + CString sFormattedFocalLength; + sFormattedFocalLength.Format(_T("%g mm"), pEXIFReader->GetFocalLength()); + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Focal length:")), sFormattedFocalLength); } if (pEXIFReader->GetFNumberPresent()) { - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("F-Number:")), pEXIFReader->GetFNumber(), 1); + CString sFormattedFNumber; + sFormattedFNumber.Format(_T("%s𝑓/%g"), secondColPadding, pEXIFReader->GetFNumber()); + bool bFNumberSameLine = pEXIFReader->GetFocalLengthPresent(); + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Aperture:")), sFormattedFNumber, false, bFNumberSameLine); } - if (pEXIFReader->GetISOSpeedPresent()) { - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("ISO Speed:")), (int)pEXIFReader->GetISOSpeed()); + if (pEXIFReader->IsGPSInformationPresent()) { + CString sGPSLocation = CreateGPSString(pEXIFReader->GetGPSLatitude(), pEXIFReader->GetGPSLongitude()); + m_pEXIFDisplay->SetGPSLocation(sGPSLocation, CreateGPSURL(pEXIFReader->GetGPSLatitude(), pEXIFReader->GetGPSLongitude())); + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Location:")), sGPSLocation, true); } - if (pEXIFReader->GetSoftwarePresent()) { - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Software:")), pEXIFReader->GetSoftware()); + if (bShowMoreDetails) { + if (pEXIFReader->IsGPSAltitudePresent()) { + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Altitude (m):")), pEXIFReader->GetGPSAltitude(), 0); + } + if (pEXIFReader->GetExposureBiasPresent() && (pEXIFReader->GetExposureBias() < -0.00001 || pEXIFReader->GetExposureBias() > 0.00001)) { + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Exposure bias (EV):")), pEXIFReader->GetExposureBias(), 2); + } + if (pEXIFReader->GetFlashFiredPresent()) { + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Flash fired:")), pEXIFReader->GetFlashFired() ? CNLS::GetString(_T("yes")) : CNLS::GetString(_T("no"))); + } + if (pEXIFReader->GetCameraModelPresent()) { + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Camera:")), pEXIFReader->GetCameraModel()); + } + if (pEXIFReader->GetLensModelPresent()) { + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Lens:")), pEXIFReader->GetLensModel()); + } + if (pEXIFReader->GetSoftwarePresent()) { + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Software:")), pEXIFReader->GetSoftware()); + } } } else if (pRawMetaData != NULL) { if (pRawMetaData->GetAcquisitionTime().wYear > 1985) { - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Acquisition date:")), pRawMetaData->GetAcquisitionTime()); + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Acquired:")), pRawMetaData->GetAcquisitionTime()); } else { const FILETIME* pFileTime = pFileList->CurrentModificationTime(); if (pFileTime != NULL) { - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Modification date:")), *pFileTime); + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Modified:")), *pFileTime); } } - if (pRawMetaData->IsGPSInformationPresent()) { - CString sGPSLocation = CreateGPSString(pRawMetaData->GetGPSLatitude(), pRawMetaData->GetGPSLongitude()); - m_pEXIFDisplay->SetGPSLocation(sGPSLocation, CreateGPSURL(pRawMetaData->GetGPSLatitude(), pRawMetaData->GetGPSLongitude())); - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Location:")), sGPSLocation, true); - if (pRawMetaData->IsGPSAltitudePresent()) { - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Altitude (m):")), pRawMetaData->GetGPSAltitude(), 0); - } - } - if (pRawMetaData->GetManufacturer()[0] != 0) { - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Camera model:")), CString(pRawMetaData->GetManufacturer()) + _T(" ") + pRawMetaData->GetModel()); + if (pRawMetaData->GetIsoSpeed() > 0.0) { + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("ISO Speed:")), (int)pRawMetaData->GetIsoSpeed()); } if (pRawMetaData->GetExposureTime() > 0.0) { double exposureTime = pRawMetaData->GetExposureTime(); - Rational rational = (exposureTime < 1.0) ? Rational(1, Helpers::RoundToInt(1.0 / exposureTime)) : Rational(Helpers::RoundToInt(exposureTime), 1); - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Exposure time (s):")), rational); - } - if (pRawMetaData->IsFlashFired()) { - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Flash fired:")), CNLS::GetString(_T("yes"))); + Rational exposureRational = (exposureTime < 1.0) ? Rational(1, Helpers::RoundToInt(1.0 / exposureTime)) : Rational(Helpers::RoundToInt(exposureTime), 1); + CString sFormattedExposureTime; + sFormattedExposureTime.Format(_T("%s%s sec"), secondColPadding, FormatRational(exposureRational)); + bool bExposureTimeSameLine = pRawMetaData->GetIsoSpeed() > 0.0; + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Exposure time:")), sFormattedExposureTime, false, bExposureTimeSameLine); } if (pRawMetaData->GetFocalLength() > 0.0) { - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Focal length (mm):")), pRawMetaData->GetFocalLength(), 1); + CString sFormattedFocalLength; + sFormattedFocalLength.Format(_T("%g mm"), pRawMetaData->GetFocalLength()); + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Focal length:")), sFormattedFocalLength); } if (pRawMetaData->GetAperture() > 0.0) { - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("F-Number:")), pRawMetaData->GetAperture(), 1); + CString sFormattedFNumber; + sFormattedFNumber.Format(_T("%s𝑓/%g"), secondColPadding, pRawMetaData->GetAperture()); + bool bApertureSameLine = pRawMetaData->GetFocalLength() > 0.0; + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Aperture:")), sFormattedFNumber, false, bApertureSameLine); } - if (pRawMetaData->GetIsoSpeed() > 0.0) { - m_pEXIFDisplay->AddLine(CNLS::GetString(_T("ISO Speed:")), (int)pRawMetaData->GetIsoSpeed()); + if (pRawMetaData->IsGPSInformationPresent()) { + CString sGPSLocation = CreateGPSString(pRawMetaData->GetGPSLatitude(), pRawMetaData->GetGPSLongitude()); + m_pEXIFDisplay->SetGPSLocation(sGPSLocation, CreateGPSURL(pRawMetaData->GetGPSLatitude(), pRawMetaData->GetGPSLongitude())); + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Location:")), sGPSLocation, true); + } + + if (bShowMoreDetails) { + if (pRawMetaData->IsGPSAltitudePresent()) { + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Altitude (m):")), pRawMetaData->GetGPSAltitude(), 0); + } + if (pRawMetaData->GetManufacturer()[0] != 0) { + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Camera model:")), CString(pRawMetaData->GetManufacturer()) + _T(" ") + pRawMetaData->GetModel()); + } + if (pRawMetaData->IsFlashFired()) { + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Flash fired:")), CNLS::GetString(_T("yes"))); + } } } else { @@ -214,6 +306,18 @@ void CEXIFDisplayCtl::FillEXIFDataDisplay() { } } + if (CurrentImage()->IsAnimation()) { + if (CurrentImage()->NumberOfFrames() != NULL) { + CString formattedFrameInfo; + CString padding = ""; + if (CurrentImage()->NumberOfFrames() >= 10 && CurrentImage()->FrameIndex() < 10) { + padding = " "; + } + formattedFrameInfo.Format(_T("Frame %s%d of %d (%d ms)"), padding, CurrentImage()->FrameIndex(), CurrentImage() ->NumberOfFrames(), CurrentImage()->FrameTimeMs()); + m_pEXIFDisplay->AddLine(CNLS::GetString(_T("Animation:")), formattedFrameInfo); + } + } + 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(); } diff --git a/src/JPEGView/EXIFDisplayCtl.h b/src/JPEGView/EXIFDisplayCtl.h index 2d89d695..4d7360d4 100644 --- a/src/JPEGView/EXIFDisplayCtl.h +++ b/src/JPEGView/EXIFDisplayCtl.h @@ -1,6 +1,7 @@ #pragma once #include "PanelController.h" +#include "EXIFReader.h" class CEXIFDisplay; @@ -11,21 +12,26 @@ class CEXIFDisplayCtl : public CPanelController CEXIFDisplayCtl(CMainDlg* pMainDlg, CPanel* pImageProcPanel); virtual ~CEXIFDisplayCtl(); - virtual float DimFactor() { return 0.5f; } + virtual float DimFactor() { return 0.65f; } virtual bool IsVisible(); virtual bool IsActive() { return m_bVisible; } + virtual bool GetShowHistogram() { return m_bShowHistogram; } virtual void SetVisible(bool bVisible); virtual void SetActive(bool bActive); + virtual void SetShowHistogram(bool bShowHistogram); virtual void AfterNewImageLoaded(); virtual bool OnMouseMove(int nX, int nY); virtual void OnPrePaintMainDlg(HDC hPaintDC); + virtual CString CEXIFDisplayCtl::FormatRational(Rational rational); + private: bool m_bVisible; + bool m_bShowHistogram; CEXIFDisplay* m_pEXIFDisplay; CPanel* m_pImageProcPanel; int m_nFileNameHeight; diff --git a/src/JPEGView/EXIFReader.cpp b/src/JPEGView/EXIFReader.cpp index cc32f50d..2761a3ff 100644 --- a/src/JPEGView/EXIFReader.cpp +++ b/src/JPEGView/EXIFReader.cpp @@ -2,6 +2,7 @@ #include "EXIFReader.h" #include "ImageProcessingTypes.h" #include "Helpers.h" +#include double CEXIFReader::UNKNOWN_DOUBLE_VALUE = 283740261.192864; @@ -284,14 +285,14 @@ CEXIFReader::CEXIFReader(void* pApp1Block, EImageFormat eImageFormat) m_bLittleEndian = bLittleEndian; uint8* pIFD0 = pTIFFHeader + ReadUInt(pTIFFHeader + 4, bLittleEndian); - if (pIFD0 - m_pApp1 >= nApp1Size) { + if (pIFD0 - m_pApp1 > nApp1Size) { return; } // Read IFD0 uint16 nNumTags = ReadUShort(pIFD0, bLittleEndian); pIFD0 += 2; uint8* pLastIFD0 = pIFD0 + nNumTags*12; - if (pLastIFD0 - m_pApp1 + 4 >= nApp1Size) { + if (pLastIFD0 - m_pApp1 + 4 > nApp1Size) { return; } uint32 nOffsetIFD1 = ReadUInt(pLastIFD0, bLittleEndian); @@ -352,13 +353,13 @@ CEXIFReader::CEXIFReader(void* pApp1Block, EImageFormat eImageFormat) return; } uint8* pEXIFIFD = pTIFFHeader + nOffsetEXIF; - if (pEXIFIFD - m_pApp1 >= nApp1Size) { + if (pEXIFIFD - m_pApp1 > nApp1Size) { return; } nNumTags = ReadUShort(pEXIFIFD, bLittleEndian); pEXIFIFD += 2; uint8* pLastEXIF = pEXIFIFD + nNumTags*12; - if (pLastEXIF - m_pApp1 >= nApp1Size) { + if (pLastEXIF - m_pApp1 > nApp1Size) { return; } uint8* pTagAcquisitionDate = FindTag(pEXIFIFD, pLastEXIF, 0x9003, bLittleEndian); @@ -397,19 +398,24 @@ CEXIFReader::CEXIFReader(void* pApp1Block, EImageFormat eImageFormat) m_sUserComment = ""; } + uint8* pTagLensModel = FindTag(pEXIFIFD, pLastEXIF, 0xa434, bLittleEndian); + CString sLensModel; + ReadStringTag(sLensModel, pTagLensModel, pTIFFHeader, bLittleEndian); + m_sLensModel = sLensModel; + // https://exiv2.org/tags.html // uint8* pTagXPComment = FindTag(pIFD0, pLastIFD0, 0x9c9c, bLittleEndian); // this is the XPComment tag to resolve this issue https://github.com/sylikc/jpegview/issues/72 , but I'm not sure how to decode it if (nOffsetIFD1 != 0) { m_pIFD1 = pTIFFHeader + nOffsetIFD1; - if (m_pIFD1 - m_pApp1 >= nApp1Size || m_pIFD1 - m_pApp1 < 0) { + if (m_pIFD1 - m_pApp1 > nApp1Size || m_pIFD1 - m_pApp1 < 0) { return; } nNumTags = ReadUShort(m_pIFD1, bLittleEndian); m_pIFD1 += 2; uint8* pLastIFD1 = m_pIFD1 + nNumTags*12; - if (pLastIFD1 - m_pApp1 >= nApp1Size) { + if (pLastIFD1 - m_pApp1 > nApp1Size) { return; } m_pLastIFD1 = pLastIFD1; @@ -485,13 +491,13 @@ void CEXIFReader::ReadGPSData(uint8* pTIFFHeader, uint8* pTagGPSIFD, int nApp1Si return; } uint8* pGPSIFD = pTIFFHeader + nOffsetGPS; - if (pGPSIFD - m_pApp1 >= nApp1Size) { + if (pGPSIFD - m_pApp1 > nApp1Size) { return; } int nNumTags = ReadUShort(pGPSIFD, bLittleEndian); pGPSIFD += 2; uint8* pLastGPS = pGPSIFD + nNumTags * 12; - if (pLastGPS - m_pApp1 >= nApp1Size) { + if (pLastGPS - m_pApp1 > nApp1Size) { return; } diff --git a/src/JPEGView/EXIFReader.h b/src/JPEGView/EXIFReader.h index f13264e5..530a8ce5 100644 --- a/src/JPEGView/EXIFReader.h +++ b/src/JPEGView/EXIFReader.h @@ -58,8 +58,10 @@ class CEXIFReader { LPCTSTR GetUserComment() { return m_sUserComment; } LPCTSTR GetImageDescription() { return m_sImageDescription; } LPCTSTR GetSoftware() { return m_sSoftware; } + LPCTSTR GetLensModel() { return m_sLensModel; } bool GetCameraModelPresent() { return !m_sModel.IsEmpty(); } bool GetSoftwarePresent() { return !m_sSoftware.IsEmpty(); } + bool GetLensModelPresent() { return !m_sLensModel.IsEmpty(); } // Date-time the picture was taken const SYSTEMTIME& GetAcquisitionTime() { return m_acqDate; } bool GetAcquisitionTimePresent() { return m_acqDate.wYear > 1600; } @@ -120,6 +122,7 @@ class CEXIFReader { CString m_sUserComment; CString m_sImageDescription; CString m_sSoftware; + CString m_sLensModel; SYSTEMTIME m_acqDate; SYSTEMTIME m_dateTime; Rational m_exposureTime; diff --git a/src/JPEGView/FileOpenDialog.cpp b/src/JPEGView/FileOpenDialog.cpp index f9414481..33270bb1 100644 --- a/src/JPEGView/FileOpenDialog.cpp +++ b/src/JPEGView/FileOpenDialog.cpp @@ -1,68 +1,28 @@ #include "StdAfx.h" #include "FileOpenDialog.h" -#include "NLS.h" -#include "Helpers.h" -#include "SettingsProvider.h" -#include "MultiMonitorSupport.h" -#include "dlgs.h" -bool CFileOpenDialog::m_bSized = false; - -// Command code to set the thumbnail view on the file open dialog -const unsigned int ODM_VIEW_THUMBS= 0x702d; - -CFileOpenDialog::CFileOpenDialog(HWND parentWindow, LPCTSTR sInitialFileName, LPCTSTR sFileEndings, bool bFullScreen) : -CFileDialog(TRUE, _T("jpg"), sInitialFileName, - OFN_EXPLORER | OFN_ENABLESIZING | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_PATHMUSTEXIST, - Helpers::CReplacePipe(CString(CNLS::GetString(_T("Images"))) + _T(" (") + sFileEndings + _T(")|") + sFileEndings + _T("|") + - CNLS::GetString(_T("All Files")) + _T("|*.*|")), parentWindow) { - m_bFullScreen = bFullScreen; -} - -LRESULT CFileOpenDialog::OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { - if (!m_bSized) { - HWND wndParent = this->GetParent(); - // moving the window out of the visible range prevents flickering - ::SetWindowPos(wndParent, 0, 20000, 20000, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOREDRAW); - } - - // Will be processed after the dialog is really initialized - ::PostMessage(m_hWnd, MYWM_POSTINIT, 0, 0); - - return 0; -} - -LRESULT CFileOpenDialog::OnPostInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { - HWND wndParent = this->GetParent(); // real dialog is parent of us - - SizeDialog(); - - // Get the shell window hosting the list view of the files and send a WM_COMMAND to it to - // switch to thumbnail view - // http://msdn.microsoft.com/msdnmag/issues/04/03/CQA/ - HWND hLst2 = ::GetDlgItem(wndParent, lst2); - if (hLst2 != NULL) { - ::SendMessage(hLst2, WM_COMMAND, ODM_VIEW_THUMBS, 0); - } - return 0; +FileOpenDialog::FileOpenDialog(LPCTSTR sInitialFileName, LPCTSTR sFileEndings) { + COMDLG_FILTERSPEC fileTypes[] = { + { _T("Images"), sFileEndings }, + { L"All Files", L"*.*" } + }; + GetPtr()->SetFileTypes(_countof(fileTypes), fileTypes); + GetPtr()->SetOptions(FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST); + + if (sInitialFileName != NULL) { + PIDLIST_ABSOLUTE pidl; + SHILCreateFromPath(sInitialFileName, &pidl, NULL); + IShellItem* pItem; + SHCreateShellItem(NULL, NULL, pidl, &pItem); + IShellItem* pParent; + pItem->GetParent(&pParent); + + HRESULT hr = GetPtr()->SetFolder(pParent); + } } -void CFileOpenDialog::SizeDialog() { - if (!m_bSized || m_bFullScreen) { - HWND wndParent = this->GetParent(); // real dialog is parent of us - - CSettingsProvider& sp = CSettingsProvider::This(); - CRect monitorRect = CMultiMonitorSupport::GetMonitorRect(sp.DisplayMonitor()); - int nScreenX = monitorRect.Width(); - int nScreenY = monitorRect.Height(); - int nSizeX = nScreenX*90/100; - int nSizeY = nScreenY*90/100; - ::MoveWindow(wndParent, monitorRect.left + (nScreenX - nSizeX) / 2, monitorRect.top + (nScreenY - nSizeY) / 2, - nSizeX, nSizeY, TRUE); - if (!m_bFullScreen) { - m_bSized = true; - } - } -} - - +CString FileOpenDialog::GetFilePathStr() { + WCHAR szPath[MAX_PATH]; + GetFilePath(szPath, MAX_PATH); + return CString(szPath); +} \ No newline at end of file diff --git a/src/JPEGView/FileOpenDialog.h b/src/JPEGView/FileOpenDialog.h index 32fcf3ec..5ec44624 100644 --- a/src/JPEGView/FileOpenDialog.h +++ b/src/JPEGView/FileOpenDialog.h @@ -1,24 +1,8 @@ #pragma once +#include "StdAfx.h" -#define MYWM_POSTINIT WM_USER+1 - -class CFileOpenDialog : public CFileDialog -{ +class FileOpenDialog : public CShellFileOpenDialogImpl { public: - // The file extensions must be separated by pipe (|) - CFileOpenDialog(HWND parentWindow, LPCTSTR sInitialFileName, LPCTSTR sFileEndings, bool bFullScreen); - - BEGIN_MSG_MAP(CFileOpenDialog) - MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) - MESSAGE_HANDLER(MYWM_POSTINIT, OnPostInitDialog) - END_MSG_MAP() - - LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/); - LRESULT OnPostInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/); - -private: - void SizeDialog(); - - bool m_bFullScreen; - static bool m_bSized; + FileOpenDialog(LPCTSTR sInitialFileName, LPCTSTR sFileEndings); + CString GetFilePathStr(); }; diff --git a/src/JPEGView/GUIControls.cpp b/src/JPEGView/GUIControls.cpp index 1f8b154b..eda4c6be 100644 --- a/src/JPEGView/GUIControls.cpp +++ b/src/JPEGView/GUIControls.cpp @@ -466,7 +466,7 @@ void CButtonCtrl::Draw(CDC & dc, CRect position, bool bBlack) { hPen = ::CreatePen(PS_SOLID, 1, (m_bDragging && m_bHighlight) ? CSettingsProvider::This().ColorHighlight() : m_bActive ? CSettingsProvider::This().ColorSelected() : CSettingsProvider::This().ColorGUI()); hOldPen = dc.SelectPen(hPen); } - HelpersGUI::DrawRectangle(dc, position); + // HelpersGUI::DrawRectangle(dc, position); if (m_sText.GetLength() > 0) { HelpersGUI::SelectDefaultGUIFont(dc); dc.SetBkMode(TRANSPARENT); diff --git a/src/JPEGView/ImageLoadThread.cpp b/src/JPEGView/ImageLoadThread.cpp index 54580b53..95fc968c 100644 --- a/src/JPEGView/ImageLoadThread.cpp +++ b/src/JPEGView/ImageLoadThread.cpp @@ -118,7 +118,7 @@ static EImageFormat GetBitmapFormat(Gdiplus::Bitmap * pBitmap) { } static CJPEGImage* ConvertGDIPlusBitmapToJPEGImage(Gdiplus::Bitmap* pBitmap, int nFrameIndex, void* pEXIFData, - __int64 nJPEGHash, bool &isOutOfMemory, bool &isAnimatedGIF) { + __int64 nJPEGHash, COLORREF colorTransparency, bool &isOutOfMemory, bool &isAnimatedGIF) { isOutOfMemory = false; isAnimatedGIF = false; @@ -174,7 +174,7 @@ static CJPEGImage* ConvertGDIPlusBitmapToJPEGImage(Gdiplus::Bitmap* pBitmap, int if (bHasAlphaChannel) { pBmTarget = new Gdiplus::Bitmap(pBitmap->GetWidth(), pBitmap->GetHeight(), PixelFormat32bppRGB); pBmGraphics = new Gdiplus::Graphics(pBmTarget); - COLORREF bkColor = CSettingsProvider::This().ColorTransparency(); + COLORREF bkColor = colorTransparency; Gdiplus::SolidBrush bkBrush(Gdiplus::Color(GetRValue(bkColor), GetGValue(bkColor), GetBValue(bkColor))); pBmGraphics->FillRectangle(&bkBrush, 0, 0, pBmTarget->GetWidth(), pBmTarget->GetHeight()); pBmGraphics->DrawImage(pBitmap, 0, 0, pBmTarget->GetWidth(), pBmTarget->GetHeight()); @@ -229,8 +229,8 @@ CImageLoadThread::~CImageLoadThread(void) { DeleteCachedAvifDecoder(); } -int CImageLoadThread::AsyncLoad(LPCTSTR strFileName, int nFrameIndex, const CProcessParams & processParams, HWND targetWnd, HANDLE eventFinished) { - CRequest* pRequest = new CRequest(strFileName, nFrameIndex, targetWnd, processParams, eventFinished); +int CImageLoadThread::AsyncLoad(LPCTSTR strFileName, int nFrameIndex, const CProcessParams & processParams, COLORREF colorTransparency, HWND targetWnd, HANDLE eventFinished) { + CRequest* pRequest = new CRequest(strFileName, nFrameIndex, targetWnd, processParams, colorTransparency, eventFinished); ProcessAsync(pRequest); @@ -504,7 +504,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); + Helpers::CalculateJPEGFileHash(pBuffer, nFileSize), request->ColorTransparency, isOutOfMemory, isAnimatedGIF); request->OutOfMemory = request->Image == NULL && isOutOfMemory; if (request->Image != NULL) { request->Image->SetJPEGComment(Helpers::GetJPEGComment(pBuffer, nFileSize)); @@ -569,7 +569,7 @@ void CImageLoadThread::ProcessReadBMPRequest(CRequest * request) { void CImageLoadThread::ProcessReadTGARequest(CRequest * request) { bool bOutOfMemory; - request->Image = CReaderTGA::ReadTgaImage(request->FileName, CSettingsProvider::This().ColorTransparency(), bOutOfMemory); + request->Image = CReaderTGA::ReadTgaImage(request->FileName, request->ColorTransparency, bOutOfMemory); if (bOutOfMemory) { request->OutOfMemory = true; } @@ -625,7 +625,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++ = Helpers::AlphaBlendBackground(*pImage32, CSettingsProvider::This().ColorTransparency()); + *pImage32++ = Helpers::AlphaBlendBackground(*pImage32, request->ColorTransparency); if (bHasAnimation) { m_sLastWebpFileName = sFileName; @@ -710,7 +710,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++ = Helpers::AlphaBlendBackground(*pImage32, CSettingsProvider::This().ColorTransparency()); + *pImage32++ = Helpers::AlphaBlendBackground(*pImage32, request->ColorTransparency); request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, pEXIFData, 4, 0, IF_PNG, bHasAnimation, request->FrameIndex, nFrameCount, nFrameTimeMs); } else { @@ -721,7 +721,7 @@ void CImageLoadThread::ProcessReadPNGRequest(CRequest* request) { 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->Image = ConvertGDIPlusBitmapToJPEGImage(pBitmap, 0, pEXIFData, 0, request->ColorTransparency, isOutOfMemory, isAnimatedGIF); request->OutOfMemory = request->Image == NULL && isOutOfMemory; pStream->Release(); delete pBitmap; @@ -795,7 +795,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++ = Helpers::AlphaBlendBackground(*pImage32, CSettingsProvider::This().ColorTransparency()); + *pImage32++ = Helpers::AlphaBlendBackground(*pImage32, request->ColorTransparency); request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, pEXIFData, 4, 0, IF_JXL, bHasAnimation, request->FrameIndex, nFrameCount, nFrameTimeMs); free(pEXIFData); @@ -869,7 +869,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++ = Helpers::AlphaBlendBackground(*pImage32, CSettingsProvider::This().ColorTransparency()); + *pImage32++ = Helpers::AlphaBlendBackground(*pImage32, request->ColorTransparency); request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, pEXIFData, 4, 0, IF_AVIF, bHasAnimation, request->FrameIndex, nFrameCount, nFrameTimeMs); free(pEXIFData); @@ -929,7 +929,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++ = Helpers::AlphaBlendBackground(*pImage32, CSettingsProvider::This().ColorTransparency()); + *pImage32++ = Helpers::AlphaBlendBackground(*pImage32, request->ColorTransparency); request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, pEXIFData, nBPP, 0, IF_HEIF, false, request->FrameIndex, nFrameCount, nFrameTimeMs); free(pEXIFData); @@ -989,7 +989,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++ = Helpers::AlphaBlendBackground(*pImage32, CSettingsProvider::This().ColorTransparency()); + *pImage32++ = Helpers::AlphaBlendBackground(*pImage32, request->ColorTransparency); } request->Image = new CJPEGImage(nWidth, nHeight, pPixelData, NULL, nBPP, 0, IF_QOI, false, 0, 1, 0); } @@ -1055,7 +1055,7 @@ void CImageLoadThread::ProcessReadGDIPlusRequest(CRequest * request) { m_sLastFileName = sFileName; } bool isOutOfMemory, isAnimatedGIF; - request->Image = ConvertGDIPlusBitmapToJPEGImage(pBitmap, request->FrameIndex, NULL, 0, isOutOfMemory, isAnimatedGIF); + request->Image = ConvertGDIPlusBitmapToJPEGImage(pBitmap, request->FrameIndex, NULL, 0, request->ColorTransparency, isOutOfMemory, isAnimatedGIF); request->OutOfMemory = request->Image == NULL && isOutOfMemory; if (!isAnimatedGIF) { DeleteCachedGDIBitmap(); diff --git a/src/JPEGView/ImageLoadThread.h b/src/JPEGView/ImageLoadThread.h index e2f2c13b..7375852e 100644 --- a/src/JPEGView/ImageLoadThread.h +++ b/src/JPEGView/ImageLoadThread.h @@ -38,7 +38,7 @@ class CImageLoadThread : public CWorkThread // received or the event has been signaled. // The file to load is given by its filename (with path) and the frame index (for multiframe images). The // frame index needs to be zero when the image only has one frame. - int AsyncLoad(LPCTSTR strFileName, int nFrameIndex, const CProcessParams & processParams, HWND targetWnd, HANDLE eventFinished); + int AsyncLoad(LPCTSTR strFileName, int nFrameIndex, const CProcessParams & processParams, COLORREF colorTransparency, HWND targetWnd, HANDLE eventFinished); // Get loaded image, CImageData::Image is null if not (yet) available - use handle returned by AsyncLoad(). // Call after having received the WM_IMAGE_LOAD_COMPLETED message to retrieve the loaded image. @@ -56,7 +56,7 @@ class CImageLoadThread : public CWorkThread // Request for loading an image class CRequest : public CRequestBase { public: - CRequest(LPCTSTR strFileName, int nFrameIndex, HWND wndTarget, const CProcessParams& processParams, HANDLE eventFinished) + CRequest(LPCTSTR strFileName, int nFrameIndex, HWND wndTarget, const CProcessParams& processParams, COLORREF colorTransparency, HANDLE eventFinished) : CRequestBase(eventFinished), ProcessParams(processParams) { FileName = strFileName; FrameIndex = nFrameIndex; @@ -65,6 +65,7 @@ class CImageLoadThread : public CWorkThread Image = NULL; OutOfMemory = false; ExceptionError = false; + ColorTransparency = colorTransparency; } CString FileName; @@ -73,6 +74,7 @@ class CImageLoadThread : public CWorkThread int RequestHandle; CJPEGImage* Image; CProcessParams ProcessParams; + COLORREF ColorTransparency; bool OutOfMemory; // load caused an out of memory condition bool ExceptionError; // an unhandled exception caused the load to fail }; diff --git a/src/JPEGView/JPEGImage.cpp b/src/JPEGView/JPEGImage.cpp index 1ec23f41..da94e86c 100644 --- a/src/JPEGView/JPEGImage.cpp +++ b/src/JPEGView/JPEGImage.cpp @@ -1,4 +1,4 @@ -#include "StdAfx.h" +#include "StdAfx.h" #include "JPEGImage.h" #include "BasicProcessing.h" #include "XMMImage.h" @@ -68,6 +68,8 @@ CJPEGImage::CJPEGImage(int nWidth, int nHeight, void* pPixels, void* pEXIFData, : m_rotationParams{ 0 }, m_fColorCorrectionFactorsNull{ 0 } { + m_nSourceOriginalChannels = nChannels; + if (nChannels == 3 || nChannels == 4) { m_pOrigPixels = pPixels; m_nOriginalChannels = nChannels; @@ -199,6 +201,15 @@ bool CJPEGImage::CanUseLosslessJPEGTransformations() { (m_nOrigHeight % tjMCUHeight[m_eJPEGChromoSampling]) == 0; } +CString CJPEGImage::GetBlockSizeFormatted() { + if (m_eImageFormat == IF_JPEG) { + CString formatted; + formatted.Format(_T("%d × %d"), tjMCUWidth[m_eJPEGChromoSampling], tjMCUHeight[m_eJPEGChromoSampling]); + return formatted; + } + return _T(""); +} + void CJPEGImage::TrimRectToMCUBlockSize(CRect& rect) { int nBlockWidth = tjMCUWidth[m_eJPEGChromoSampling]; rect.left = rect.left & ~(nBlockWidth - 1); @@ -600,7 +611,10 @@ void* CJPEGImage::Resample(CSize fullTargetSize, CSize clippingSize, CPoint targ if (fullTargetSize.cx > 65535 || fullTargetSize.cy > 65535) return NULL; - if (GetProcessingFlag(eProcFlags, PFLAG_HighQualityResampling) && + // MOD: no HQ resampling on upsample. Should really be a config option. + bool bUseHQResampling = GetProcessingFlag(eProcFlags, PFLAG_HighQualityResampling) && eResizeType == DownSample; + + if (bUseHQResampling && !(eResizeType == NoResize && (filter == Filter_Downsampling_Best_Quality || filter == Filter_Downsampling_No_Aliasing))) { if (SupportsSIMD(cpu)) { if (eResizeType == UpSample) { diff --git a/src/JPEGView/JPEGImage.h b/src/JPEGView/JPEGImage.h index 538b2c11..a352ea1e 100644 --- a/src/JPEGView/JPEGImage.h +++ b/src/JPEGView/JPEGImage.h @@ -179,6 +179,8 @@ class CJPEGImage { // Checks if this is a JPEG image and if the dimension of this image is dividable by the JPEG block size used. bool CanUseLosslessJPEGTransformations(); + CString GetBlockSizeFormatted(); + // Trims the given rectangle to MCU block size of this image (allowing lossless JPEG transformations) void TrimRectToMCUBlockSize(CRect& rect); @@ -206,6 +208,9 @@ class CJPEGImage { // returns the number of channels in the OriginalPixels (3 or 4, corresponding to 24 bpp and 32 bpp) int OriginalChannels() const { return m_nOriginalChannels; } + // returns the number of channels in the OriginalPixels (3 or 4, corresponding to 24 bpp and 32 bpp) + int SourceOriginalChannels() const { return m_nSourceOriginalChannels; } + // raw access to DIB pixels with no LUT applied - do not delete or store the returned pointer // note that this DIB can be NULL due to optimization if currently only the processed DIB is maintained void* DIBPixels() { return m_pDIBPixels; } @@ -357,6 +362,7 @@ class CJPEGImage { int m_nOrigWidth, m_nOrigHeight; // these may changes by rotation int m_nInitOrigWidth, m_nInitOrigHeight; // original width of image when constructed (before any rotation and crop) int m_nOriginalChannels; + int m_nSourceOriginalChannels; __int64 m_nPixelHash; EImageFormat m_eImageFormat; TJSAMP m_eJPEGChromoSampling; diff --git a/src/JPEGView/JPEGProvider.cpp b/src/JPEGView/JPEGProvider.cpp index 7ea45801..87c0450f 100644 --- a/src/JPEGView/JPEGProvider.cpp +++ b/src/JPEGView/JPEGProvider.cpp @@ -7,6 +7,12 @@ #include "ProcessParams.h" #include "BasicProcessing.h" +#include + +int READ_AHEAD_RANGE_IN_DIRECTION = 2; +int READ_AHEAD_RANGE_OPPOSITE_DIRECTION = 1; +int READ_AHEAD_RANGE_KEEP_EXTRA = 4; + CJPEGProvider::CJPEGProvider(HWND handlerWnd, int nNumThreads, int nNumBuffers) { m_hHandlerWnd = handlerWnd; m_nNumThread = nNumThreads; @@ -32,8 +38,9 @@ CJPEGProvider::~CJPEGProvider(void) { } CJPEGImage* CJPEGProvider::RequestImage(CFileList* pFileList, EReadAheadDirection eDirection, - LPCTSTR strFileName, int nFrameIndex, const CProcessParams & processParams, + LPCTSTR strFileName, int nFrameIndex, const CProcessParams & processParams, COLORREF colorTransparency, bool& bOutOfMemory, bool& bExceptionError) { + if (strFileName == NULL) { bOutOfMemory = false; bExceptionError = false; @@ -48,13 +55,17 @@ CJPEGImage* CJPEGProvider::RequestImage(CFileList* pFileList, EReadAheadDirectio m_eOldDirection = eDirection; if (pRequest == NULL) { + ::OutputDebugString(_T("RequestImage cache miss for ")); ::OutputDebugString(strFileName); ::OutputDebugString(_T("\n")); // no request pending for this file, add to request queue and start async - pRequest = StartNewRequest(strFileName, nFrameIndex, processParams); + pRequest = StartNewRequest(strFileName, nFrameIndex, processParams, colorTransparency); + + /* // wait with read ahead when direction changed - maybe user just wants to re-see last image if (!bDirectionChanged && eDirection != NONE) { // start parallel if more than one thread - StartNewRequestBundle(pFileList, eDirection, processParams, m_nNumThread - 1, NULL); + StartNewRequestBundle(pFileList, eDirection, processParams, colorTransparency, 3, NULL); } + */ } // wait for request if not yet ready @@ -91,21 +102,22 @@ CJPEGImage* CJPEGProvider::RequestImage(CFileList* pFileList, EReadAheadDirectio bWasOutOfMemory = true; if (FreeAllPossibleMemory()) { DeleteElement(pRequest); - pRequest = StartRequestAndWaitUntilReady(strFileName, nFrameIndex, processParams); + pRequest = StartRequestAndWaitUntilReady(strFileName, nFrameIndex, processParams, colorTransparency); } } // cleanup stuff no longer used - RemoveUnusedImages(bRemoveAlsoActiveRequests); - ClearOldestInactiveRequest(); + RemoveUnusedImages(pFileList, eDirection, false, pRequest); + MarkOldestRequestsAsInactive(); // check if we shall start new requests (don't start another request if we are short of memory!) - if (m_requestList.size() < (unsigned int)m_nNumBuffers && !bDirectionChanged && !bWasOutOfMemory && eDirection != NONE) { - StartNewRequestBundle(pFileList, eDirection, processParams, m_nNumThread, pRequest); + if (!bWasOutOfMemory) { + StartNewPreloadRequestBundle(pFileList, eDirection, processParams, colorTransparency, pRequest); } bOutOfMemory = pRequest->OutOfMemory; bExceptionError = pRequest->ExceptionError; + return pRequest->Image; } @@ -205,44 +217,66 @@ CJPEGProvider::CImageRequest* CJPEGProvider::FindRequest(LPCTSTR strFileName, in return NULL; } -CJPEGProvider::CImageRequest* CJPEGProvider::StartRequestAndWaitUntilReady(LPCTSTR sFileName, int nFrameIndex, const CProcessParams & processParams) { - CImageRequest* pRequest = StartNewRequest(sFileName, nFrameIndex, processParams); +CJPEGProvider::CImageRequest* CJPEGProvider::StartRequestAndWaitUntilReady(LPCTSTR sFileName, int nFrameIndex, const CProcessParams & processParams, COLORREF colorTransparency) { + CImageRequest* pRequest = StartNewRequest(sFileName, nFrameIndex, processParams, colorTransparency); ::WaitForSingleObject(pRequest->EventFinished, INFINITE); GetLoadedImageFromWorkThread(pRequest); return pRequest; } -void CJPEGProvider::StartNewRequestBundle(CFileList* pFileList, EReadAheadDirection eDirection, - const CProcessParams & processParams, int nNumRequests, CImageRequest* pLastReadyRequest) { - if (nNumRequests == 0 || pFileList == NULL) { - return; - } - for (int i = 0; i < nNumRequests; i++) { +std::list> CJPEGProvider::GetReadAheadFileList(CFileList* pFileList, EReadAheadDirection eDirection, CImageRequest* pLastReadyRequest, int extraPerDirection) { + // TODO priority + std::list> filesWithFrameIndex{}; + for (int i = 0; i < READ_AHEAD_RANGE_IN_DIRECTION + extraPerDirection; i++) { bool bSwitchImage = true; int nFrameIndex = (pLastReadyRequest != NULL) ? Helpers::GetFrameIndex(pLastReadyRequest->Image, eDirection == FORWARD, true, bSwitchImage) : 0; LPCTSTR sFileName = bSwitchImage ? pFileList->PeekNextPrev(i + 1, eDirection == FORWARD, eDirection == TOGGLE) : pFileList->Current(); - if (sFileName != NULL && FindRequest(sFileName, nFrameIndex) == NULL) { + if (sFileName != NULL) { + filesWithFrameIndex.push_back({ sFileName, nFrameIndex }); + } + } + for (int i = 0; i < READ_AHEAD_RANGE_OPPOSITE_DIRECTION + extraPerDirection; i++) { + bool bSwitchImage = true; + int nFrameIndex = (pLastReadyRequest != NULL) ? Helpers::GetFrameIndex(pLastReadyRequest->Image, eDirection == BACKWARD, true, bSwitchImage) : 0; + LPCTSTR sFileName = bSwitchImage ? pFileList->PeekNextPrev(i + 1, eDirection == BACKWARD, eDirection == TOGGLE) : pFileList->Current(); + if (sFileName != NULL) { + filesWithFrameIndex.push_back({ sFileName, nFrameIndex }); + } + } + return filesWithFrameIndex; +} + +void CJPEGProvider::StartNewPreloadRequestBundle(CFileList* pFileList, EReadAheadDirection eDirection, const CProcessParams & processParams, COLORREF colorTransparency, void* pLastReadyRequestRaw) { + if (pFileList == NULL) { + return; + } + CImageRequest* pLastReadyRequest = reinterpret_cast(pLastReadyRequestRaw); + + ::OutputDebugString(_T("StartNewRequestBundle\n")); + + std::list> filesWithFrameIndex = GetReadAheadFileList(pFileList, eDirection, pLastReadyRequest); + + for (auto [sFileName, nFrameIndex] : filesWithFrameIndex) { + if (FindRequest(sFileName, nFrameIndex) == NULL) { if (GetProcessingFlag(PFLAG_NoProcessingAfterLoad, processParams.ProcFlags)) { // The read ahead threads need this flag to be deleted - we can speculatively process the image with good hit rate CProcessParams paramsCopied = processParams; paramsCopied.ProcFlags = SetProcessingFlag(paramsCopied.ProcFlags, PFLAG_NoProcessingAfterLoad, false); - StartNewRequest(sFileName, nFrameIndex, paramsCopied); + StartNewRequest(sFileName, nFrameIndex, paramsCopied, colorTransparency); } else { - StartNewRequest(sFileName, nFrameIndex, processParams); + StartNewRequest(sFileName, nFrameIndex, processParams, colorTransparency); } } } } -CJPEGProvider::CImageRequest* CJPEGProvider::StartNewRequest(LPCTSTR sFileName, int nFrameIndex, const CProcessParams & processParams) { -#ifdef DEBUG - ::OutputDebugString(_T("Start new request: ")); ::OutputDebugString(sFileName); ::OutputDebugString(_T("\n")); -#endif +CJPEGProvider::CImageRequest* CJPEGProvider::StartNewRequest(LPCTSTR sFileName, int nFrameIndex, const CProcessParams & processParams, COLORREF colorTransparency) { CImageRequest* pRequest = new CImageRequest(sFileName, nFrameIndex); + ::OutputDebugString(_T("StartNewRequest: ")); ::OutputDebugString(sFileName); ::OutputDebugString(_T("\n")); m_requestList.push_back(pRequest); pRequest->HandlingThread = SearchThreadForNewRequest(); pRequest->Handle = pRequest->HandlingThread->AsyncLoad(pRequest->FileName, nFrameIndex, - processParams, m_hHandlerWnd, pRequest->EventFinished); + processParams, colorTransparency, m_hHandlerWnd, pRequest->EventFinished); return pRequest; } @@ -285,50 +319,44 @@ CImageLoadThread* CJPEGProvider::SearchThreadForNewRequest(void) { return (pBestOccupiedThread == NULL) ? m_pWorkThreads[0] : pBestOccupiedThread; } -void CJPEGProvider::RemoveUnusedImages(bool bRemoveAlsoActiveRequests) { + +void CJPEGProvider::RemoveUnusedImages(CFileList* pFileList, EReadAheadDirection eDirection, bool removeAll, void* pLastReadyRequestRaw) { + ::OutputDebugString(_T("RemoveUnused\n")); + CImageRequest* pLastReadyRequest = reinterpret_cast(pLastReadyRequestRaw); + int keepExtraPerDirection = 4; + std::list> readAheadRange = GetReadAheadFileList(pFileList, eDirection, reinterpret_cast(pLastReadyRequest), keepExtraPerDirection); + std::list::iterator iter; + auto compare = [](std::tuple a, std::tuple b) { return lstrcmp(std::get<0>(a), std::get<0>(b)) && std::get<1>(a) == std::get<1>(b); }; bool bRemoved = false; - int nTimeStampToRemove = -2; - do { + do { // Wrapped in a loop because we have to break iterating when deleting element, as it breaks the iterator. bRemoved = false; - int nSmallestTimeStamp = INT_MAX; - std::list::iterator iter; - for (iter = m_requestList.begin( ); iter != m_requestList.end( ); iter++ ) { - if ((*iter)->InUse == false && (*iter)->Ready && ((*iter)->IsActive == false || bRemoveAlsoActiveRequests || IsDestructivelyProcessed((*iter)->Image))) { - // search element with smallest timestamp - if ((*iter)->AccessTimeStamp < nSmallestTimeStamp) { - nSmallestTimeStamp = (*iter)->AccessTimeStamp; + for (iter = m_requestList.begin(); iter != m_requestList.end(); iter++) { + bool isInReadAheadRange = false; + for (auto [fileName, frameIndex] : readAheadRange) { + if ((*iter)->FileName == fileName && (*iter)->FrameIndex == frameIndex) { + isInReadAheadRange = true; + break; } - // remove the readahead images - if we get here with read ahead, the strategy was wrong and - // the read ahead image is not used. - if ((*iter)->AccessTimeStamp == nTimeStampToRemove || IsDestructivelyProcessed((*iter)->Image)) { -#ifdef DEBUG - ::OutputDebugString(_T("Delete request: ")); ::OutputDebugString((*iter)->FileName); ::OutputDebugString(_T("\n")); -#endif + } + bool isCurrentImage = pLastReadyRequest != NULL && ((*iter)->FileName == pLastReadyRequest->FileName && (*iter)->FrameIndex == pLastReadyRequest->FrameIndex); + if (!isCurrentImage && !(*iter)->InUse) { + if (removeAll || (!isInReadAheadRange && !isCurrentImage && !(*iter)->IsActive)) { + ::OutputDebugString(_T("Deleting from cache: ")); ::OutputDebugString((*iter)->FileName); ::OutputDebugString(_T("\n")); DeleteElementAt(iter); bRemoved = true; break; } } } - nTimeStampToRemove = -2; - // Make one buffer free for next readahead (except when bRemoveAlsoActiveRequests) - int nMaxListSize = bRemoveAlsoActiveRequests ? (unsigned int)m_nNumBuffers : (unsigned int)m_nNumBuffers - 1; - if (m_requestList.size() > (unsigned int)nMaxListSize) { - // remove element with smallest timestamp - if (nSmallestTimeStamp < INT_MAX) { - bRemoved = true; - nTimeStampToRemove = nSmallestTimeStamp; - } - } - } while (bRemoved); // repeat until no element could be removed anymore + } while (bRemoved); // repeat until no element was removed anymore } -void CJPEGProvider::ClearOldestInactiveRequest() { +void CJPEGProvider::MarkOldestRequestsAsInactive() { if (m_requestList.size() >= (unsigned int)m_nNumBuffers) { int nFirstHandle = INT_MAX; CImageRequest* pFirstRequest = NULL; std::list::iterator iter; - for (iter = m_requestList.begin( ); iter != m_requestList.end( ); iter++ ) { + for (iter = m_requestList.begin(); iter != m_requestList.end(); iter++) { if ((*iter)->IsActive) { // mark very old requests for removal if (CImageLoadThread::GetCurHandleValue() - (*iter)->Handle > m_nNumBuffers) { @@ -342,7 +370,7 @@ void CJPEGProvider::ClearOldestInactiveRequest() { } if (pFirstRequest != NULL) { pFirstRequest->IsActive = false; - ClearOldestInactiveRequest(); + MarkOldestRequestsAsInactive(); } } } diff --git a/src/JPEGView/JPEGProvider.h b/src/JPEGView/JPEGProvider.h index 23f4d1cd..5207c8df 100644 --- a/src/JPEGView/JPEGProvider.h +++ b/src/JPEGView/JPEGProvider.h @@ -1,5 +1,7 @@ #pragma once +#include + class CJPEGImage; class CImageLoadThread; class CFileList; @@ -40,7 +42,7 @@ class CJPEGProvider // blocks until the image is ready. If not specified otherwise, a read-ahead request for the next image is // created automatically so that the next image will be ready immediately when requested in the future. CJPEGImage* RequestImage(CFileList* pFileList, EReadAheadDirection eDirection, LPCTSTR strFileName, int nFrameIndex, - const CProcessParams & processParams, bool& bOutOfMemory, bool& bExceptionError); + const CProcessParams & processParams, COLORREF colorTransparency, bool& bOutOfMemory, bool& bExceptionError); // Notifies that the specified image is no longer used and its memory can be freed. // The CJPEGProvider class may decide to keep the image cached. @@ -67,6 +69,11 @@ class CJPEGProvider // message was received. void OnImageLoadCompleted(int nHandle); + void RemoveUnusedImages(CFileList* pFileList, EReadAheadDirection eDirection, bool removeAll, void* pLastReadyRequest); + void MarkOldestRequestsAsInactive(); + + void StartNewPreloadRequestBundle(CFileList* pFileList, EReadAheadDirection eDirection, const CProcessParams& processParams, COLORREF colorTransparency, void* pLastReadyRequest); + private: // stores a request for loading and processing a JPEG image struct CImageRequest { @@ -117,12 +124,10 @@ class CJPEGProvider bool WaitForAsyncRequest(int nHandle, int nMessage); void GetLoadedImageFromWorkThread(CImageRequest* pRequest); CImageLoadThread* SearchThreadForNewRequest(void); - void RemoveUnusedImages(bool bRemoveAlsoReadAhead); - CImageRequest* StartRequestAndWaitUntilReady(LPCTSTR sFileName, int nFrameIndex, const CProcessParams & processParams); - CImageRequest* StartNewRequest(LPCTSTR sFileName, int nFrameIndex, const CProcessParams & processParams); - void StartNewRequestBundle(CFileList* pFileList, EReadAheadDirection eDirection, const CProcessParams & processParams, int nNumRequests, CImageRequest* pLastReadyRequest); + CImageRequest* StartRequestAndWaitUntilReady(LPCTSTR sFileName, int nFrameIndex, const CProcessParams & processParams, COLORREF colorTransparency); + CImageRequest* StartNewRequest(LPCTSTR sFileName, int nFrameIndex, const CProcessParams & processParams, COLORREF colorTransparency); + std::list> CJPEGProvider::GetReadAheadFileList(CFileList* pFileList, EReadAheadDirection eDirection, CImageRequest* pLastReadyRequest, int extraPerDirection = 0); CImageRequest* FindRequest(LPCTSTR strFileName, int nFrameIndex); - void ClearOldestInactiveRequest(); void DeleteElementAt(std::list::iterator iteratorAt); // also deletes the request and the image in the request void DeleteElement(CImageRequest* pRequest); bool IsDestructivelyProcessed(CJPEGImage* pImage); diff --git a/src/JPEGView/JPEGView.cpp b/src/JPEGView/JPEGView.cpp index 159868a6..b34a2621 100644 --- a/src/JPEGView/JPEGView.cpp +++ b/src/JPEGView/JPEGView.cpp @@ -7,6 +7,8 @@ #include "MainDlg.h" #include "SettingsProvider.h" +#include + #ifdef DEBUG #include #endif @@ -186,6 +188,7 @@ int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lp ::SetUnhandledExceptionFilter(CrashHandler); #endif + auto start = std::chrono::high_resolution_clock::now(); // use the locale from the operating system, however for numeric input/output always use 'C' locale // with the OS locale the INI file can not be read correctly (due to the different decimal point characters) @@ -242,6 +245,11 @@ int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lp dlgMain.SetStartupInfo(sStartupFile, nAutostartSlideShow, eSorting, eTransitionEffect, nTransitionTime, bAutoExit, nDisplayMonitor); try { + auto afterInit = std::chrono::high_resolution_clock::now(); + CString durationStr; + long double durationLongDouble = std::chrono::duration_cast(afterInit - start).count(); + durationStr.Format(_T("init took %g ms\n"), durationLongDouble); + ::OutputDebugString(durationStr); nRet = (int)dlgMain.DoModal(); if (CSettingsProvider::This().StickyWindowSize() && !dlgMain.IsFullScreenMode()) { CSettingsProvider::This().SaveStickyWindowRect(dlgMain.WindowRectOnClose()); diff --git a/src/JPEGView/JPEGView.rc b/src/JPEGView/JPEGView.rc index d215cb7d..148727be 100644 --- a/src/JPEGView/JPEGView.rc +++ b/src/JPEGView/JPEGView.rc @@ -275,182 +275,193 @@ POPUPMENU MENU BEGIN POPUP "PopupMenu" BEGIN - MENUITEM "Stop slide show/movie\t0xff", IDM_STOP_MOVIE + MENUITEM "Stop slideshow\t0xff", IDM_STOP_MOVIE MENUITEM SEPARATOR - MENUITEM "Open image...", IDM_OPEN - POPUP "Open image with" + POPUP "Open with" BEGIN - MENUITEM "_empty_", IDM_ABOUT + MENUITEM "_empty_", IDM_ABOUT END - MENUITEM "Save processed image...\t0x5de", IDM_SAVE - MENUITEM "Reload image", IDM_RELOAD - MENUITEM "Open containing folder in Explorer", IDM_EXPLORE - MENUITEM "Print image...", IDM_PRINT - MENUITEM "Batch rename/copy...", IDM_BATCH_COPY - POPUP "Set modification date" - BEGIN - MENUITEM "to current date", IDM_TOUCH_IMAGE - MENUITEM "to EXIF date", IDM_TOUCH_IMAGE_EXIF - MENUITEM "to EXIF date (all files in folder)", IDM_TOUCH_IMAGE_EXIF_FOLDER - END - POPUP "Set as desktop wallpaper" - BEGIN - MENUITEM "Use original image", IDM_SET_WALLPAPER_ORIG - MENUITEM "Use processed image as displayed", IDM_SET_WALLPAPER_DISPLAY - END - MENUITEM SEPARATOR - MENUITEM "Copy to clipboard", IDM_COPY - MENUITEM "Copy original size image", IDM_COPY_FULL - MENUITEM "Copy file path", IDM_COPY_PATH - MENUITEM "Paste from clipboard", IDM_PASTE - MENUITEM SEPARATOR - MENUITEM "Show picture info (EXIF)", IDM_SHOW_FILEINFO - MENUITEM "Show filename", IDM_SHOW_FILENAME - MENUITEM "Show navigation panel", IDM_SHOW_NAVPANEL + MENUITEM "Image info", IDM_SHOW_FILEINFO + MENUITEM "Show in Explorer", IDM_EXPLORE MENUITEM SEPARATOR - MENUITEM "Next image", IDM_NEXT - MENUITEM "Previous image", IDM_PREV - POPUP "Navigation" + MENUITEM "Print image...", IDM_PRINT //HIDDEN + POPUP "Set as desktop wallpaper" BEGIN - MENUITEM "Loop folder", IDM_LOOP_FOLDER - MENUITEM "Loop recursively", IDM_LOOP_RECURSIVELY - MENUITEM "Loop siblings", IDM_LOOP_SIBLINGS + MENUITEM "Use original image", IDM_SET_WALLPAPER_ORIG + MENUITEM "Use processed image as displayed", IDM_SET_WALLPAPER_DISPLAY END - POPUP "Display order" + POPUP "Copy / Paste" BEGIN - MENUITEM "Modification date", IDM_SORT_MOD_DATE - MENUITEM "Creation date", IDM_SORT_CREATION_DATE - MENUITEM "File name", IDM_SORT_NAME - MENUITEM "File size", IDM_SORT_SIZE - MENUITEM "Random", IDM_SORT_RANDOM + MENUITEM "Copy full size image", IDM_COPY_FULL + MENUITEM "Copy current size image", IDM_COPY + MENUITEM "Copy file path", IDM_COPY_PATH MENUITEM SEPARATOR - MENUITEM "Ascending", IDM_SORT_ASCENDING - MENUITEM "Descending", IDM_SORT_DESCENDING + MENUITEM "Paste from clipboard", IDM_PASTE END - POPUP "Play folder as slideshow/movie" + MENUITEM "Show filename", IDM_SHOW_FILENAME // HIDDEN + MENUITEM "Show navigation panel", IDM_SHOW_NAVPANEL // HIDDEN + POPUP "Navigation" BEGIN - MENUITEM "Slideshow", IDM_SLIDESHOW_START - MENUITEM "Waiting time\t1 sec", IDM_SLIDESHOW_1 - MENUITEM "Waiting time\t2 sec", IDM_SLIDESHOW_2 - MENUITEM "Waiting time\t3 sec", IDM_SLIDESHOW_3 - MENUITEM "Waiting time\t4 sec", IDM_SLIDESHOW_4 - MENUITEM "Waiting time\t5 sec", IDM_SLIDESHOW_5 - MENUITEM "Waiting time\t7 sec", IDM_SLIDESHOW_7 - MENUITEM "Waiting time\t10 sec", IDM_SLIDESHOW_10 - MENUITEM "Waiting time\t20 sec", IDM_SLIDESHOW_20 - POPUP "Transition Effect" + MENUITEM "Next image", IDM_NEXT + MENUITEM "Previous image", IDM_PREV + MENUITEM SEPARATOR + MENUITEM "Open image...", IDM_OPEN + MENUITEM "Reload image", IDM_RELOAD + MENUITEM SEPARATOR + POPUP "Loop" BEGIN - MENUITEM "None", IDM_EFFECT_NONE - MENUITEM SEPARATOR - MENUITEM "Blend", IDM_EFFECT_BLEND - MENUITEM SEPARATOR - MENUITEM "Slide from right", IDM_EFFECT_SLIDE_RL - MENUITEM "Slide from left", IDM_EFFECT_SLIDE_LR - MENUITEM "Slide from top", IDM_EFFECT_SLIDE_TB - MENUITEM "Slide from bottom", IDM_EFFECT_SLIDE_BT - MENUITEM SEPARATOR - MENUITEM "Roll from right", IDM_EFFECT_ROLL_RL - MENUITEM "Roll from left", IDM_EFFECT_ROLL_LR - MENUITEM "Roll from top", IDM_EFFECT_ROLL_TB - MENUITEM "Roll from bottom", IDM_EFFECT_ROLL_BT - MENUITEM SEPARATOR - MENUITEM "Scroll from right", IDM_EFFECT_SCROLL_RL - MENUITEM "Scroll from left", IDM_EFFECT_SCROLL_LR - MENUITEM "Scroll from top", IDM_EFFECT_SCROLL_TB - MENUITEM "Scroll from bottom", IDM_EFFECT_SCROLL_BT + MENUITEM "Loop folder", IDM_LOOP_FOLDER + MENUITEM "Loop recursively", IDM_LOOP_RECURSIVELY + MENUITEM "Loop siblings", IDM_LOOP_SIBLINGS END - POPUP "Transition Speed" + POPUP "Display order" BEGIN - MENUITEM "Very fast", IDM_EFFECTTIME_VERY_FAST - MENUITEM "Fast", IDM_EFFECTTIME_FAST - MENUITEM "Normal", IDM_EFFECTTIME_NORMAL - MENUITEM "Slow", IDM_EFFECTTIME_SLOW - MENUITEM "Very slow", IDM_EFFECTTIME_VERY_SLOW + MENUITEM "Modification date", IDM_SORT_MOD_DATE + MENUITEM "Creation date", IDM_SORT_CREATION_DATE + MENUITEM "File name", IDM_SORT_NAME + MENUITEM "File size", IDM_SORT_SIZE + MENUITEM "Random", IDM_SORT_RANDOM + MENUITEM SEPARATOR + MENUITEM "Ascending", IDM_SORT_ASCENDING + MENUITEM "Descending", IDM_SORT_DESCENDING + END + POPUP "Slideshow" + BEGIN + MENUITEM "Slideshow", IDM_SLIDESHOW_START + MENUITEM "Waiting time\t1 sec", IDM_SLIDESHOW_1 + MENUITEM "Waiting time\t2 sec", IDM_SLIDESHOW_2 + MENUITEM "Waiting time\t3 sec", IDM_SLIDESHOW_3 + MENUITEM "Waiting time\t4 sec", IDM_SLIDESHOW_4 + MENUITEM "Waiting time\t5 sec", IDM_SLIDESHOW_5 + MENUITEM "Waiting time\t7 sec", IDM_SLIDESHOW_7 + MENUITEM "Waiting time\t10 sec", IDM_SLIDESHOW_10 + MENUITEM "Waiting time\t20 sec", IDM_SLIDESHOW_20 + POPUP "Transition Effect" + BEGIN + MENUITEM "None", IDM_EFFECT_NONE + MENUITEM SEPARATOR + MENUITEM "Blend", IDM_EFFECT_BLEND + MENUITEM SEPARATOR + MENUITEM "Slide from right", IDM_EFFECT_SLIDE_RL + MENUITEM "Slide from left", IDM_EFFECT_SLIDE_LR + MENUITEM "Slide from top", IDM_EFFECT_SLIDE_TB + MENUITEM "Slide from bottom", IDM_EFFECT_SLIDE_BT + MENUITEM SEPARATOR + MENUITEM "Roll from right", IDM_EFFECT_ROLL_RL + MENUITEM "Roll from left", IDM_EFFECT_ROLL_LR + MENUITEM "Roll from top", IDM_EFFECT_ROLL_TB + MENUITEM "Roll from bottom", IDM_EFFECT_ROLL_BT + MENUITEM SEPARATOR + MENUITEM "Scroll from right", IDM_EFFECT_SCROLL_RL + MENUITEM "Scroll from left", IDM_EFFECT_SCROLL_LR + MENUITEM "Scroll from top", IDM_EFFECT_SCROLL_TB + MENUITEM "Scroll from bottom", IDM_EFFECT_SCROLL_BT + END + POPUP "Transition Speed" + BEGIN + MENUITEM "Very fast", IDM_EFFECTTIME_VERY_FAST + MENUITEM "Fast", IDM_EFFECTTIME_FAST + MENUITEM "Normal", IDM_EFFECTTIME_NORMAL + MENUITEM "Slow", IDM_EFFECTTIME_SLOW + MENUITEM "Very slow", IDM_EFFECTTIME_VERY_SLOW + END + MENUITEM SEPARATOR + MENUITEM "Movie", IDM_MOVIE_START_FPS + MENUITEM "Playback speed\t5 fps", IDM_MOVIE_5_FPS + MENUITEM "Playback speed\t10 fps", IDM_MOVIE_10_FPS + MENUITEM "Playback speed\t25 fps", IDM_MOVIE_25_FPS + MENUITEM "Playback speed\t30 fps", IDM_MOVIE_30_FPS + MENUITEM "Playback speed\t50 fps", IDM_MOVIE_50_FPS + MENUITEM "Playback speed\t100 fps", IDM_MOVIE_100_FPS END - MENUITEM SEPARATOR - MENUITEM "Movie", IDM_MOVIE_START_FPS - MENUITEM "Playback speed\t5 fps", IDM_MOVIE_5_FPS - MENUITEM "Playback speed\t10 fps", IDM_MOVIE_10_FPS - MENUITEM "Playback speed\t25 fps", IDM_MOVIE_25_FPS - MENUITEM "Playback speed\t30 fps", IDM_MOVIE_30_FPS - MENUITEM "Playback speed\t50 fps", IDM_MOVIE_50_FPS - MENUITEM "Playback speed\t100 fps", IDM_MOVIE_100_FPS END - MENUITEM SEPARATOR - POPUP "Transform image" + POPUP "Zoom" BEGIN - MENUITEM "Rotate +90", IDM_ROTATE_90 - MENUITEM "Rotate -90", IDM_ROTATE_270 - MENUITEM "Rotate...", IDM_ROTATE - MENUITEM "Change size...", IDM_CHANGESIZE - MENUITEM "Perspective correction...", IDM_PERSPECTIVE + MENUITEM "Fit to screen", IDM_FIT_TO_SCREEN + MENUITEM "Fill with crop", IDM_FILL_WITH_CROP + MENUITEM "Span all screens", IDM_SPAN_SCREENS + MENUITEM "Full screen mode", IDM_FULL_SCREEN_MODE + MENUITEM "Fit window to image", IDM_FIT_WINDOW_TO_IMAGE MENUITEM SEPARATOR - MENUITEM "Mirror horizontally", IDM_MIRROR_H - MENUITEM "Mirror vertically", IDM_MIRROR_V - END - POPUP "Lossless JPEG transformations" - BEGIN - MENUITEM "Rotate +90\t0x24B9", IDM_ROTATE_90_LOSSLESS - MENUITEM "Rotate -90\t0x24C3", IDM_ROTATE_270_LOSSLESS - MENUITEM "Rotate 180", IDM_ROTATE_180_LOSSLESS + MENUITEM "Hide window title bar", IDM_HIDE_TITLE_BAR + MENUITEM "Set window always on top", IDM_ALWAYS_ON_TOP + MENUITEM SEPARATOR + MENUITEM "400 %", IDM_ZOOM_400 + MENUITEM "200 %", IDM_ZOOM_200 + MENUITEM "100 %\t0x3a98", IDM_ZOOM_100 + MENUITEM "50 %", IDM_ZOOM_50 + MENUITEM "25 %", IDM_ZOOM_25 MENUITEM SEPARATOR - MENUITEM "Mirror horizontally", IDM_MIRROR_H_LOSSLESS - MENUITEM "Mirror vertically", IDM_MIRROR_V_LOSSLESS + POPUP "Auto zoom mode" + BEGIN + MENUITEM "Fit to screen (no zoom)", IDM_AUTO_ZOOM_FIT_NO_ZOOM + MENUITEM "Fill with crop (no zoom)", IDM_AUTO_ZOOM_FILL_NO_ZOOM + MENUITEM "Fit to screen", IDM_AUTO_ZOOM_FIT + MENUITEM "Fill with crop", IDM_AUTO_ZOOM_FILL + END END - MENUITEM "Auto correction", IDM_AUTO_CORRECTION - MENUITEM "Local density correction", IDM_LDC - MENUITEM "Keep parameters", IDM_KEEP_PARAMETERS - MENUITEM SEPARATOR - MENUITEM "Save parameters to DB", IDM_SAVE_PARAM_DB - MENUITEM "Clear parameters from DB", IDM_CLEAR_PARAM_DB - MENUITEM SEPARATOR - POPUP "Zoom" + POPUP "Modify image" BEGIN - MENUITEM "Fit to screen", IDM_FIT_TO_SCREEN - MENUITEM "Fill with crop", IDM_FILL_WITH_CROP - MENUITEM "Span all screens", IDM_SPAN_SCREENS - MENUITEM "Full screen mode", IDM_FULL_SCREEN_MODE - MENUITEM "Fit window to image", IDM_FIT_WINDOW_TO_IMAGE + MENUITEM "Save processed image...\t0x5de", IDM_SAVE MENUITEM SEPARATOR - MENUITEM "Hide window title bar", IDM_HIDE_TITLE_BAR - MENUITEM "Set window always on top", IDM_ALWAYS_ON_TOP + POPUP "Transform" + BEGIN + MENUITEM "Rotate +90", IDM_ROTATE_90 + MENUITEM "Rotate -90", IDM_ROTATE_270 + MENUITEM "Rotate...", IDM_ROTATE + MENUITEM "Change size...", IDM_CHANGESIZE + MENUITEM "Perspective correction...", IDM_PERSPECTIVE + MENUITEM SEPARATOR + MENUITEM "Mirror horizontally", IDM_MIRROR_H + MENUITEM "Mirror vertically", IDM_MIRROR_V + END + POPUP "Transform (JPEG lossless)" + BEGIN + MENUITEM "Rotate +90\t0x24B9", IDM_ROTATE_90_LOSSLESS + MENUITEM "Rotate -90\t0x24C3", IDM_ROTATE_270_LOSSLESS + MENUITEM "Rotate 180", IDM_ROTATE_180_LOSSLESS + MENUITEM SEPARATOR + MENUITEM "Mirror horizontally", IDM_MIRROR_H_LOSSLESS + MENUITEM "Mirror vertically", IDM_MIRROR_V_LOSSLESS + END + MENUITEM SEPARATOR + MENUITEM "Batch rename/copy...", IDM_BATCH_COPY + POPUP "Set modification date" + BEGIN + MENUITEM "to current date", IDM_TOUCH_IMAGE + MENUITEM "to EXIF date", IDM_TOUCH_IMAGE_EXIF + MENUITEM "to EXIF date (all files in folder)", IDM_TOUCH_IMAGE_EXIF_FOLDER + END MENUITEM SEPARATOR - MENUITEM "400 %", IDM_ZOOM_400 - MENUITEM "200 %", IDM_ZOOM_200 - MENUITEM "100 %\t0x3a98", IDM_ZOOM_100 - MENUITEM "50 %", IDM_ZOOM_50 - MENUITEM "25 %", IDM_ZOOM_25 + MENUITEM "Auto correction", IDM_AUTO_CORRECTION + MENUITEM "Local density correction", IDM_LDC + MENUITEM "Keep parameters", IDM_KEEP_PARAMETERS + MENUITEM SEPARATOR + MENUITEM "Save parameters to DB", IDM_SAVE_PARAM_DB + MENUITEM "Clear parameters from DB", IDM_CLEAR_PARAM_DB END - POPUP "Auto zoom mode" + POPUP "User commands" BEGIN - MENUITEM "Fit to screen (no zoom)", IDM_AUTO_ZOOM_FIT_NO_ZOOM - MENUITEM "Fill with crop (no zoom)", IDM_AUTO_ZOOM_FILL_NO_ZOOM - MENUITEM "Fit to screen", IDM_AUTO_ZOOM_FIT - MENUITEM "Fill with crop", IDM_AUTO_ZOOM_FILL + MENUITEM "_empty_", IDM_ABOUT END MENUITEM SEPARATOR POPUP "Settings/Admin" BEGIN - MENUITEM "Edit global settings...", IDM_EDIT_GLOBAL_CONFIG - MENUITEM "Edit user settings...", IDM_EDIT_USER_CONFIG + MENUITEM "Edit user settings...", IDM_EDIT_USER_CONFIG + MENUITEM "Edit user keymap...", IDM_EDIT_USER_KEYMAP + MENUITEM "Edit global settings...", IDM_EDIT_GLOBAL_CONFIG + MENUITEM "Edit global keymap...", IDM_EDIT_GLOBAL_KEYMAP MENUITEM "Update user settings...", IDM_UPDATE_USER_CONFIG MENUITEM "Manage 'Open image with' menu...", IDM_MANAGE_OPEN_WITH_MENU MENUITEM SEPARATOR - MENUITEM "Set current parameters as default values...", IDM_SAVE_PARAMETERS - MENUITEM SEPARATOR MENUITEM "Set as default viewer...", IDM_SET_AS_DEFAULT_VIEWER MENUITEM SEPARATOR + MENUITEM "Set current parameters as default values...", IDM_SAVE_PARAMETERS MENUITEM "Backup parameter DB...", IDM_BACKUP_PARAMDB MENUITEM "Restore parameter DB...", IDM_RESTORE_PARAMDB + MENUITEM SEPARATOR + MENUITEM "About JPEGView...", IDM_ABOUT END - MENUITEM SEPARATOR - POPUP "User commands" - BEGIN - MENUITEM "_empty_", IDM_ABOUT - END - MENUITEM SEPARATOR - MENUITEM "About JPEGView...", IDM_ABOUT - MENUITEM SEPARATOR MENUITEM "Exit\t0xffff", IDM_EXIT END END diff --git a/src/JPEGView/JPEGView.vcxproj b/src/JPEGView/JPEGView.vcxproj index 6801186b..d4a193e7 100644 --- a/src/JPEGView/JPEGView.vcxproj +++ b/src/JPEGView/JPEGView.vcxproj @@ -26,22 +26,22 @@ Application Unicode - v142 + v143 Application Unicode - v142 + v143 Application Unicode - v142 + v143 Application Unicode - v142 + v143 @@ -330,6 +330,7 @@ + @@ -386,7 +387,6 @@ - @@ -423,6 +423,7 @@ + @@ -477,7 +478,6 @@ - diff --git a/src/JPEGView/JPEGView.vcxproj.filters b/src/JPEGView/JPEGView.vcxproj.filters index 8a484a5d..ba4167a1 100644 --- a/src/JPEGView/JPEGView.vcxproj.filters +++ b/src/JPEGView/JPEGView.vcxproj.filters @@ -192,9 +192,6 @@ Source Files\Dialogs - - Source Files\Dialogs - Source Files\Dialogs @@ -291,6 +288,9 @@ Source Files\Image Types + + Source Files\Dialogs + @@ -452,9 +452,6 @@ Header Files\Dialogs - - Header Files\Dialogs - Header Files\Dialogs @@ -563,6 +560,9 @@ Header Files\Image Types + + Header Files\Dialogs + diff --git a/src/JPEGView/KeyMap.cpp b/src/JPEGView/KeyMap.cpp index 098d43db..aee914a6 100644 --- a/src/JPEGView/KeyMap.cpp +++ b/src/JPEGView/KeyMap.cpp @@ -153,6 +153,24 @@ static CString _GetKeyShortcutName(int nShortcut) { return sKeyDesc; } +CString CKeyMap::UserKeymapFileName() { + return CString(Helpers::JPEGViewAppDataPath()) + KEYMAP_USER_FILE_NAME; +} + +CString CKeyMap::GlobalKeymapFileName() { + // Not the template, but the keymap file in exe dir. + return CString(CSettingsProvider::This().GetEXEPath()) + KEYMAP_USER_FILE_NAME; +} + +bool CKeyMap::ExistsUserKeyMap() { + return ::GetFileAttributes(UserKeymapFileName()) != INVALID_FILE_ATTRIBUTES; +} + +void CKeyMap::CopyUserKeymapFromTemplate() { + CString sTemplateFileName = CString(CSettingsProvider::This().GetEXEPath()) + KEYMAP_DEFAULT_FILE_NAME; + ::CopyFile(sTemplateFileName, UserKeymapFileName(), TRUE); +} + // Similar to SettingsProvider, attemps to load the keymap from various locations // before settling for the default // diff --git a/src/JPEGView/KeyMap.h b/src/JPEGView/KeyMap.h index 2ab1b072..d4d1973e 100644 --- a/src/JPEGView/KeyMap.h +++ b/src/JPEGView/KeyMap.h @@ -26,6 +26,11 @@ class CKeyMap // gets the shortcut key name, e.g. 'Ctrl+P' static CString GetShortcutKey(int combinedKeyCode); + + static CString UserKeymapFileName(); + static CString GlobalKeymapFileName(); + static bool ExistsUserKeyMap(); + static void CopyUserKeymapFromTemplate(); private: // key is the key code, value the command ID diff --git a/src/JPEGView/MainDlg.cpp b/src/JPEGView/MainDlg.cpp index 2721016a..bfe84bb5 100644 --- a/src/JPEGView/MainDlg.cpp +++ b/src/JPEGView/MainDlg.cpp @@ -1,4 +1,4 @@ -// MainDlg.cpp : implementation of the CMainDlg class +// MainDlg.cpp : implementation of the CMainDlg class // ///////////////////////////////////////////////////////////////////////////// @@ -6,6 +6,7 @@ #include "resource.h" #include #include +#include #include "MainDlg.h" #include "HelpDlg.h" @@ -66,7 +67,7 @@ static const double CONTRAST_INC = 0.03; // increment for contrast value static const double SHARPEN_INC = 0.05; // increment for sharpen value static const double LDC_INC = 0.1; // increment for LDC (lighten shadows and darken highlights) static const int NUM_THREADS = 1; // number of readahead threads to use -static const int READ_AHEAD_BUFFERS = 2; // number of readahead buffers to use (NUM_THREADS+1 is a good choice) +static const int READ_AHEAD_BUFFERS = 10; // number of readahead buffers to use static const int ZOOM_TIMEOUT = 200; // refinement done after this many milliseconds static const int ZOOM_TEXT_TIMEOUT = 1000; // zoom label disappears after this many milliseconds @@ -307,6 +308,7 @@ void CMainDlg::SetStartupInfo(LPCTSTR sStartupFile, int nAutostartSlideShow, Hel } LRESULT CMainDlg::OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { + auto before = std::chrono::high_resolution_clock::now(); UpdateWindowTitle(); // set the scaling of the screen (DPI) compared to 96 DPI (design value) @@ -382,10 +384,16 @@ LRESULT CMainDlg::OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam // create thread pool for processing requests on multiple CPU cores CProcessingThreadPool::This().CreateThreadPoolThreads(); + + if (sp.DefaultMaximized() && !m_bFullScreenMode) { + // maximize *before* loading images, so that the post-loading getDIB already gets the right dimensions. + this->ShowWindow(SW_MAXIMIZE); + } + // create JPEG provider and request first image - do no processing yet if not in fullscreen mode (as we do not know the size yet) m_pJPEGProvider = new CJPEGProvider(m_hWnd, NUM_THREADS, READ_AHEAD_BUFFERS); m_pCurrentImage = m_pJPEGProvider->RequestImage(m_pFileList, CJPEGProvider::FORWARD, - m_pFileList->Current(), 0, CreateProcessParams(!m_bFullScreenMode), m_bOutOfMemoryLastImage, m_bExceptionErrorLastImage); + m_pFileList->Current(), 0, CreateProcessParams(!m_bFullScreenMode), CSettingsProvider::This().ColorTransparency(m_bFullScreenMode), m_bOutOfMemoryLastImage, m_bExceptionErrorLastImage); if (m_pCurrentImage != NULL && m_pCurrentImage->IsAnimation()) { StartAnimation(); } @@ -396,15 +404,12 @@ LRESULT CMainDlg::OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam if (!m_bFullScreenMode) { // Window mode, set correct window size SetCurrentWindowStyle(); - if (!IsAdjustWindowToImage()) { + if (!IsAdjustWindowToImage() && !sp.DefaultMaximized()) { CRect windowRect = CMultiMonitorSupport::GetDefaultWindowRect(); this->SetWindowPos(HWND_TOP, windowRect.left, windowRect.top, windowRect.Width(), windowRect.Height(), SWP_NOZORDER | SWP_NOCOPYBITS); } else { AdjustWindowToImage(true); } - if (sp.DefaultMaximized()) { - this->ShowWindow(SW_MAXIMIZE); - } } else { PrefetchDIB(m_monitorRect); SetWindowLong(GWL_STYLE, WS_VISIBLE); @@ -428,6 +433,12 @@ LRESULT CMainDlg::OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam this->DragAcceptFiles(); + auto afterInitDialog = std::chrono::high_resolution_clock::now(); + CString durationStr; + long double durationLongDouble = std::chrono::duration_cast(afterInitDialog - before).count(); + durationStr.Format(_T("initDialog took %g ms\n"), durationLongDouble); + // ::OutputDebugString(durationStr); + return TRUE; } @@ -459,7 +470,7 @@ LRESULT CMainDlg::OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, B CRect imageProcessingArea = m_pImageProcPanelCtl->PanelRect(); CRectF visRectZoomNavigator(0.0f, 0.0f, 1.0f, 1.0f); CBrush backBrush; - backBrush.CreateSolidBrush(CSettingsProvider::This().ColorBackground()); + backBrush.CreateSolidBrush(CSettingsProvider::This().ColorBackground(m_bFullScreenMode)); #ifdef DEBUG CString a; a.Format(_T("client rect w/h pix: %d %d = %d\n"), m_clientRect.Width(), m_clientRect.Height(), m_clientRect.Width() * m_clientRect.Height()); @@ -469,7 +480,7 @@ LRESULT CMainDlg::OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, B std::list excludedClippingRects; // Panels are handled over memory DCs to eliminate flickering - CPaintMemDCMgr memDCMgr(dc); + CPaintMemDCMgr memDCMgr(this, dc); if (m_pCurrentImage == NULL) { m_pPanelMgr->OnPrePaint(dc); @@ -520,7 +531,7 @@ LRESULT CMainDlg::OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, B visRectZoomNavigator = m_pZoomNavigatorCtl->GetVisibleRect(newSize, clippedSize, offsetsInImage); excludedClippingRects.push_back(m_pZoomNavigatorCtl->PanelRect()); } - + m_pPanelMgr->OnPrePaint(dc); m_pPanelMgr->PrepareMemDCMgr(memDCMgr, excludedClippingRects); memDCMgr.ExcludeFromClippingRegion(dc, excludedClippingRects); @@ -534,7 +545,7 @@ LRESULT CMainDlg::OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, B } if (m_bZoomMode) m_offsets = unlimitedOffsets; } - + // Restore the old clipping region by adding the excluded rectangles again memDCMgr.IncludeIntoClippingRegion(dc, excludedClippingRects); @@ -584,7 +595,7 @@ LRESULT CMainDlg::OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, B } void CMainDlg::PaintToDC(CDC& dc) { - COLORREF backColor = CSettingsProvider::This().ColorBackground(); + COLORREF backColor = CSettingsProvider::This().ColorBackground(m_bFullScreenMode); if (backColor == 0) backColor = RGB(0, 0, 1); // these f**ing nVidia drivers have a bug when blending pure black CBrush backBrush; @@ -620,7 +631,7 @@ void CMainDlg::PaintToDC(CDC& dc) { if (m_pEXIFDisplayCtl->IsVisible()) { m_pEXIFDisplayCtl->OnPrePaintMainDlg(dc); - BlendBlackRect(dc, *m_pEXIFDisplayCtl->GetPanel(), 0.5f); + BlendBlackRect(dc, *m_pEXIFDisplayCtl->GetPanel(), 0.5f); m_pEXIFDisplayCtl->OnPaintPanel(dc, CPoint(0, 0)); } @@ -647,6 +658,7 @@ void CMainDlg::BlendBlackRect(CDC & targetDC, CPanel& panel, float fBlendFactor) void CMainDlg::DisplayErrors(CJPEGImage* pCurrentImage, const CRect& clientRect, CDC& dc) { dc.SetTextColor(CSettingsProvider::This().ColorGUI()); + HelpersGUI::SelectDefaultGUIFont(dc); if (m_sStartupFile.IsEmpty() && m_pCurrentImage == NULL) { CRect rectText(0, clientRect.Height()/2 - HelpersGUI::ScaleToScreen(40), clientRect.Width(), clientRect.Height()); if (m_isBeforeFileSelected) { @@ -753,6 +765,11 @@ LRESULT CMainDlg::OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, bool bEatenByPanel = isCropping ? false : m_pPanelMgr->OnMouseLButton(MouseEvent_BtnDown, pointClicked.x, pointClicked.y); if (!bEatenByPanel) { + if (m_pCurrentImage == NULL && !isCropping) { + OpenFileWithDialog(false, false); + return 0; + } + bool bCtrl = (::GetKeyState(VK_CONTROL) & 0x8000) != 0; bool bShift = (::GetKeyState(VK_SHIFT) & 0x8000) != 0; @@ -840,18 +857,21 @@ LRESULT CMainDlg::OnRButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam* return 0; } -LRESULT CMainDlg::OnMButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/) { - this->SetCapture(); - if (HandleMouseButtonByKeymap(VK_MBUTTON)) { - return 0; - } - StartDragging(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), false); +LRESULT CMainDlg::OnMButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled) { + // MOD: I don’t want dragging with middle click, instead normal handling so even fast-pased clicks are handled + bHandled = HandleMouseButtonByKeymap(VK_MBUTTON); return 0; } -LRESULT CMainDlg::OnMButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/) { - EndDragging(); - ::ReleaseCapture(); +LRESULT CMainDlg::OnMButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) { + bHandled = HandleMouseButtonByKeymap(VK_MBUTTON, false); + return 0; +} + + +LRESULT CMainDlg::OnMButtonDblClk(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled) { + // MOD: Also have to handle MButtonDblClk so even fast-pased clicks are handled (I have it set to a toggle, and I want to toggle fast) + bHandled = HandleMouseButtonByKeymap(VK_MBUTTON, true); return 0; } @@ -965,7 +985,7 @@ LRESULT CMainDlg::OnKeyDown(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOO bHandled = true; m_pFileList->SetNavigationMode(Helpers::NM_LoopSubDirectories); GotoImage(POS_Next); - } else if (wParam >= '1' && wParam <= '9' && (!bShift || bCtrl)) { + } else if (wParam >= '1' && wParam <= '9' && (bShift || bCtrl)) { // Start the slideshow bHandled = true; int nValue = (int)wParam - '1' + 1; @@ -1137,16 +1157,20 @@ LRESULT CMainDlg::OnContextMenu(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, HMENU hMenuTrackPopup = ::GetSubMenu(hMenu, 0); HelpersGUI::TranslateMenuStrings(hMenuTrackPopup, m_pKeyMap); - + + int deletedMenusToplevel = 0; + if (m_pEXIFDisplayCtl->IsActive()) ::CheckMenuItem(hMenuTrackPopup, IDM_SHOW_FILEINFO, MF_CHECKED); if (m_bShowFileName) ::CheckMenuItem(hMenuTrackPopup, IDM_SHOW_FILENAME, MF_CHECKED); if (m_pNavPanelCtl->IsActive()) ::CheckMenuItem(hMenuTrackPopup, IDM_SHOW_NAVPANEL, MF_CHECKED); if (m_bAutoContrast) ::CheckMenuItem(hMenuTrackPopup, IDM_AUTO_CORRECTION, MF_CHECKED); if (m_bLDC) ::CheckMenuItem(hMenuTrackPopup, IDM_LDC, MF_CHECKED); if (m_bKeepParams) ::CheckMenuItem(hMenuTrackPopup, IDM_KEEP_PARAMETERS, MF_CHECKED); + HMENU hMenuNavigation = ::GetSubMenu(hMenuTrackPopup, SUBMENU_POS_NAVIGATION); - ::CheckMenuItem(hMenuNavigation, m_pFileList->GetNavigationMode()*10 + IDM_LOOP_FOLDER, MF_CHECKED); - HMENU hMenuOrdering = ::GetSubMenu(hMenuTrackPopup, SUBMENU_POS_DISPLAY_ORDER); + HMENU hMenuNavigationLoop = ::GetSubMenu(hMenuNavigation, SUBMENU_POS_NAVIGATION_LOOP); + ::CheckMenuItem(hMenuNavigationLoop, m_pFileList->GetNavigationMode()*10 + IDM_LOOP_FOLDER, MF_CHECKED); + HMENU hMenuOrdering = ::GetSubMenu(hMenuNavigation, SUBMENU_POS_DISPLAY_ORDER); ::CheckMenuItem(hMenuOrdering, (m_pFileList->GetSorting() == Helpers::FS_LastModTime) ? IDM_SORT_MOD_DATE : (m_pFileList->GetSorting() == Helpers::FS_CreationTime) ? IDM_SORT_CREATION_DATE : @@ -1158,7 +1182,7 @@ LRESULT CMainDlg::OnContextMenu(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, ::EnableMenuItem(hMenuOrdering, IDM_SORT_ASCENDING, MF_BYCOMMAND | MF_GRAYED); ::EnableMenuItem(hMenuOrdering, IDM_SORT_DESCENDING, MF_BYCOMMAND | MF_GRAYED); } - HMENU hMenuMovie = ::GetSubMenu(hMenuTrackPopup, SUBMENU_POS_MOVIE); + HMENU hMenuMovie = ::GetSubMenu(hMenuNavigation, SUBMENU_POS_SLIDESHOW); if (!m_bMovieMode) ::EnableMenuItem(hMenuMovie, IDM_STOP_MOVIE, MF_BYCOMMAND | MF_GRAYED); HMENU hMenuZoom = ::GetSubMenu(hMenuTrackPopup, SUBMENU_POS_ZOOM); if (m_bSpanVirtualDesktop) ::CheckMenuItem(hMenuZoom, IDM_SPAN_SCREENS, MF_CHECKED); @@ -1166,19 +1190,14 @@ LRESULT CMainDlg::OnContextMenu(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, if (m_bWindowBorderless) ::CheckMenuItem(hMenuZoom, IDM_HIDE_TITLE_BAR, MF_CHECKED); if (m_bAlwaysOnTop) ::CheckMenuItem(hMenuZoom, IDM_ALWAYS_ON_TOP, MF_CHECKED); if (IsAdjustWindowToImage() && IsImageExactlyFittingWindow()) ::CheckMenuItem(hMenuZoom, IDM_FIT_WINDOW_TO_IMAGE, MF_CHECKED); - HMENU hMenuAutoZoomMode = ::GetSubMenu(hMenuTrackPopup, SUBMENU_POS_AUTOZOOMMODE); + HMENU hMenuAutoZoomMode = ::GetSubMenu(hMenuZoom, SUBMENU_POS_AUTOZOOMMODE); ::CheckMenuItem(hMenuAutoZoomMode, GetAutoZoomMode() * 10 + IDM_AUTO_ZOOM_FIT_NO_ZOOM, MF_CHECKED); HMENU hMenuSettings = ::GetSubMenu(hMenuTrackPopup, SUBMENU_POS_SETTINGS); - HMENU hMenuModDate = ::GetSubMenu(hMenuTrackPopup, SUBMENU_POS_MODDATE); + HMENU hMenuModify = ::GetSubMenu(hMenuTrackPopup, SUBMENU_POS_MODIFY); + HMENU hMenuModDate = ::GetSubMenu(hMenuModify, SUBMENU_POS_MODIFY_MODDATE); HMENU hMenuUserCommands = ::GetSubMenu(hMenuTrackPopup, SUBMENU_POS_USER_COMMANDS); HMENU hMenuOpenWithCommands = ::GetSubMenu(hMenuTrackPopup, SUBMENU_POS_OPENWITH); - HMENU hMenuWallpaper = ::GetSubMenu(hMenuTrackPopup, SUBMENU_POS_WALLPAPER); - if (!HelpersGUI::CreateUserCommandsMenu(hMenuUserCommands)) { - ::DeleteMenu(hMenuTrackPopup, SUBMENU_POS_USER_COMMANDS + 1, MF_BYPOSITION); - ::DeleteMenu(hMenuTrackPopup, SUBMENU_POS_USER_COMMANDS, MF_BYPOSITION); - ::DeleteMenu(hMenuTrackPopup, SUBMENU_POS_USER_COMMANDS - 1, MF_BYPOSITION); - } if (!m_bFullScreenMode) { // Transition effect and speed only available in full screen mode ::DeleteMenu(hMenuMovie, 9, MF_BYPOSITION); @@ -1199,7 +1218,8 @@ LRESULT CMainDlg::OnContextMenu(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, ::EnableMenuItem(hMenuMovie, IDM_MOVIE_START_FPS, MF_BYCOMMAND | MF_GRAYED); if (!CSettingsProvider::This().AllowEditGlobalSettings()) { - ::DeleteMenu(hMenuSettings, 0, MF_BYPOSITION); + ::DeleteMenu(hMenuSettings, IDM_EDIT_GLOBAL_CONFIG, MF_BYCOMMAND); + ::DeleteMenu(hMenuSettings, IDM_EDIT_GLOBAL_KEYMAP, MF_BYCOMMAND); } bool bCanPaste = ::IsClipboardFormatAvailable(CF_DIB); @@ -1207,22 +1227,22 @@ LRESULT CMainDlg::OnContextMenu(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, bool bCanDoLosslessJPEGTransform = (m_pCurrentImage != NULL) && m_pCurrentImage->GetImageFormat() == IF_JPEG && !m_pCurrentImage->IsDestructivelyProcessed(); - if (!bCanDoLosslessJPEGTransform) ::EnableMenuItem(hMenuTrackPopup, SUBMENU_POS_TRANSFORM_LOSSLESS, MF_BYPOSITION | MF_GRAYED); + if (!bCanDoLosslessJPEGTransform) ::EnableMenuItem(hMenuModify, SUBMENU_POS_TRANSFORM_LOSSLESS, MF_BYPOSITION | MF_GRAYED); if (m_pCurrentImage == NULL) { ::EnableMenuItem(hMenuTrackPopup, IDM_SAVE, MF_BYCOMMAND | MF_GRAYED); ::EnableMenuItem(hMenuTrackPopup, IDM_RELOAD, MF_BYCOMMAND | MF_GRAYED); //::EnableMenuItem(hMenuTrackPopup, IDM_EXPLORE, MF_BYCOMMAND | MF_GRAYED); // can still show path to an image which could not be loaded. If file doesn't exist, nothing happens anyways - ::EnableMenuItem(hMenuTrackPopup, IDM_PRINT, MF_BYCOMMAND | MF_GRAYED); + ::EnableMenuItem(hMenuTrackPopup, IDM_PRINT, MF_BYCOMMAND | MF_DISABLED); ::EnableMenuItem(hMenuTrackPopup, IDM_COPY, MF_BYCOMMAND | MF_GRAYED); ::EnableMenuItem(hMenuTrackPopup, IDM_COPY_FULL, MF_BYCOMMAND | MF_GRAYED); ::EnableMenuItem(hMenuTrackPopup, IDM_COPY_PATH, MF_BYCOMMAND | MF_GRAYED); ::EnableMenuItem(hMenuTrackPopup, IDM_SAVE_PARAM_DB, MF_BYCOMMAND | MF_GRAYED); ::EnableMenuItem(hMenuTrackPopup, IDM_CLEAR_PARAM_DB, MF_BYCOMMAND | MF_GRAYED); ::EnableMenuItem(hMenuTrackPopup, SUBMENU_POS_ZOOM, MF_BYPOSITION | MF_GRAYED); - ::EnableMenuItem(hMenuTrackPopup, SUBMENU_POS_MODDATE, MF_BYPOSITION | MF_GRAYED); - ::EnableMenuItem(hMenuTrackPopup, SUBMENU_POS_TRANSFORM, MF_BYPOSITION | MF_GRAYED); - ::EnableMenuItem(hMenuTrackPopup, SUBMENU_POS_WALLPAPER, MF_BYPOSITION | MF_GRAYED); + ::EnableMenuItem(hMenuModify, SUBMENU_POS_MODIFY_MODDATE, MF_BYPOSITION | MF_GRAYED); + ::EnableMenuItem(hMenuModify, SUBMENU_POS_TRANSFORM, MF_BYPOSITION | MF_GRAYED); + // ::EnableMenuItem(hMenuTrackPopup, SUBMENU_POS_WALLPAPER, MF_BYPOSITION | MF_GRAYED); } else { if (m_bKeepParams || m_pCurrentImage->IsClipboardImage() || CParameterDB::This().FindEntry(m_pCurrentImage->GetPixelHash()) == NULL) @@ -1239,14 +1259,15 @@ LRESULT CMainDlg::OnContextMenu(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, ::EnableMenuItem(hMenuModDate, IDM_TOUCH_IMAGE_EXIF, MF_BYCOMMAND | MF_GRAYED); } int windowsVersion = Helpers::GetWindowsVersion(); - if (m_pCurrentImage->IsClipboardImage() || (windowsVersion < 600 && m_pCurrentImage->GetImageFormat() != IF_WindowsBMP) || + /*if (m_pCurrentImage->IsClipboardImage() || (windowsVersion < 600 && m_pCurrentImage->GetImageFormat() != IF_WindowsBMP) || (windowsVersion < 602 && !(m_pCurrentImage->GetImageFormat() == IF_WindowsBMP || m_pCurrentImage->GetImageFormat() == IF_JPEG)) || !m_pCurrentImage->IsGDIPlusFormat()) { ::EnableMenuItem(hMenuWallpaper, IDM_SET_WALLPAPER_ORIG, MF_BYCOMMAND | MF_GRAYED); - } + }*/ } if (!HelpersGUI::CreateOpenWithCommandsMenu(hMenuOpenWithCommands) || m_pCurrentImage == NULL) { - ::DeleteMenu(hMenuTrackPopup, SUBMENU_POS_OPENWITH, MF_BYPOSITION); + ::DeleteMenu(hMenuTrackPopup, SUBMENU_POS_OPENWITH - deletedMenusToplevel, MF_BYPOSITION); + deletedMenusToplevel += 1; } if (m_bMovieMode) { ::EnableMenuItem(hMenuTrackPopup, IDM_SAVE, MF_BYCOMMAND | MF_GRAYED); @@ -1259,7 +1280,25 @@ LRESULT CMainDlg::OnContextMenu(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, } else { // Delete the 'Stop movie' menu entry if no movie is playing ::DeleteMenu(hMenuTrackPopup, 0, MF_BYPOSITION); - ::DeleteMenu(hMenuTrackPopup, 0, MF_BYPOSITION); + ::DeleteMenu(hMenuTrackPopup, 0, MF_BYPOSITION); // the separator + deletedMenusToplevel += 2; + } + // Hide some menus I never use + ::DeleteMenu(hMenuTrackPopup, IDM_PRINT, MF_BYCOMMAND); + deletedMenusToplevel += 1; + ::DeleteMenu(hMenuTrackPopup, IDM_UPDATE_USER_CONFIG, MF_BYCOMMAND); // not in toplevel + + ::DeleteMenu(hMenuTrackPopup, SUBMENU_POS_WALLPAPER - deletedMenusToplevel, MF_BYPOSITION); + deletedMenusToplevel += 1; + + ::DeleteMenu(hMenuTrackPopup, IDM_SHOW_FILENAME, MF_BYCOMMAND); + ::DeleteMenu(hMenuTrackPopup, IDM_SHOW_NAVPANEL, MF_BYCOMMAND); + deletedMenusToplevel += 2; + + if (!HelpersGUI::CreateUserCommandsMenu(hMenuUserCommands)) { + // User commands are empty, delete the menu + ::DeleteMenu(hMenuTrackPopup, SUBMENU_POS_USER_COMMANDS - deletedMenusToplevel, MF_BYPOSITION); + deletedMenusToplevel += 1; } int nMenuCmd = TrackPopupMenu(CPoint(nX, nY), hMenuTrackPopup); @@ -1444,6 +1483,17 @@ void CMainDlg::ExecuteCommand(int nCommand) { m_pEXIFDisplayCtl->SetActive(!m_pEXIFDisplayCtl->IsActive()); m_pNavPanelCtl->GetNavPanel()->GetBtnShowInfo()->SetActive(m_pEXIFDisplayCtl->IsActive()); break; + case IDM_SHOW_FILEINFO_HISTOGRAM: + if (m_pCurrentImage == NULL) { + return; + } + if (m_pEXIFDisplayCtl->IsVisible()) { + m_pEXIFDisplayCtl->SetShowHistogram(!m_pEXIFDisplayCtl->GetShowHistogram()); + } else { + m_pEXIFDisplayCtl->SetActive(true); + m_pEXIFDisplayCtl->SetShowHistogram(true); + } + break; case IDM_SHOW_FILENAME: m_bShowFileName = !m_bShowFileName; this->Invalidate(FALSE); @@ -1632,35 +1682,36 @@ void CMainDlg::ExecuteCommand(int nCommand) { case IDM_MIRROR_V_LOSSLESS: if (m_pCurrentImage != NULL && m_pCurrentImage->GetImageFormat() == IF_JPEG) { bool bCanTransformWithoutCrop = m_pCurrentImage->CanUseLosslessJPEGTransformations(); - bool bAskIfToCrop = !(bCanTransformWithoutCrop ||sp.CropWithoutPromptLosslessJPEG()); + bool bAskConfirm = !(bCanTransformWithoutCrop ||sp.CropWithoutPromptLosslessJPEG()) || nCommand == IDM_ROTATE_90_LOSSLESS_CONFIRM || nCommand == IDM_ROTATE_270_LOSSLESS_CONFIRM; bool bPerformTransformation = true; bool bCrop = false; MouseOn(); - if (bAskIfToCrop) { - bCrop = IDYES == ::MessageBox(m_hWnd, CString(CNLS::GetString(_T("Image width and height must be dividable by the JPEG block size (8 or 16) for lossless transformations!"))) + _T("\n") + - CNLS::GetString(_T("The transformation can be applied if the image is cropped to the next matching size but this will remove some border pixels.")) + _T("\n") + - CNLS::GetString(_T("Crop the image and apply transformation? Cropping cannot be undone!")) + _T("\n\n") + - CNLS::GetString(_T("Note: Set the key 'CropWithoutPromptLosslessJPEG=true' in the INI file to always crop without showing this message.")), - CNLS::GetString(_T("Lossless JPEG transformations")), MB_YESNO | MB_ICONEXCLAMATION | MB_DEFBUTTON2); - } - if (!bAskIfToCrop || bCrop) { - if (!bAskIfToCrop && (nCommand == IDM_ROTATE_90_LOSSLESS_CONFIRM || nCommand == IDM_ROTATE_270_LOSSLESS_CONFIRM)) { - LPCTSTR sConfirmMsg = (nCommand == IDM_ROTATE_90_LOSSLESS_CONFIRM) ? - CNLS::GetString(_T("Rotate current file on disk lossless by 90 deg (W/H must be multiple of 16)")) : - CNLS::GetString(_T("Rotate current file on disk lossless by 270 deg (W/H must be multiple of 16)")); - bPerformTransformation = IDYES == ::MessageBox(m_hWnd, - sConfirmMsg, CNLS::GetString(_T("Confirm")), MB_YESNOCANCEL | MB_ICONWARNING); + if (bAskConfirm) { + CString sLosslessOrCropMessage; + if (bCanTransformWithoutCrop) { + sLosslessOrCropMessage = _T("✓ Transformation will be fully lossless.\n"); } - if (bPerformTransformation) { - CJPEGLosslessTransform::EResult eResult = - CJPEGLosslessTransform::PerformTransformation(m_pFileList->Current(), m_pFileList->Current(), HelpersGUI::CommandIdToLosslessTransformation(nCommand), bCrop || sp.CropWithoutPromptLosslessJPEG()); - if (eResult != CJPEGLosslessTransform::Success) { - ::MessageBox(m_hWnd, CString(CNLS::GetString(_T("Performing the lossless transformation failed!"))) + - _T("\n") + CNLS::GetString(_T("Reason:")) + _T(" ") + HelpersGUI::LosslessTransformationResultToString(eResult), - CNLS::GetString(_T("Lossless JPEG transformations")), MB_OK | MB_ICONWARNING); - } else { - ReloadImage(false); // reload current image - } + else { + sLosslessOrCropMessage.Format(_T("Image will be cropped to match JPEG block size %s\n"), m_pCurrentImage->GetBlockSizeFormatted()); + } + CString confirm90; + confirm90.Format(_T("↻ Rotate %s on disk by 90° clockwise?\n%s"), CurrentFileName(true), sLosslessOrCropMessage); + CString confirm270; + confirm270.Format(_T("↺ Rotate %s on disk by 90° counterclockwise?\n%s"), CurrentFileName(true), sLosslessOrCropMessage); + LPCTSTR sConfirmMsg = (nCommand == IDM_ROTATE_90_LOSSLESS_CONFIRM) ? confirm90 : confirm270; + long icon = bCanTransformWithoutCrop? MB_ICONQUESTION : MB_ICONWARNING; + bPerformTransformation = IDOK == ::MessageBox(m_hWnd, + sConfirmMsg, CNLS::GetString(_T("Confirm")), MB_OKCANCEL | icon); + } + if (bPerformTransformation) { + CJPEGLosslessTransform::EResult eResult = + CJPEGLosslessTransform::PerformTransformation(m_pFileList->Current(), m_pFileList->Current(), HelpersGUI::CommandIdToLosslessTransformation(nCommand), bCrop || sp.CropWithoutPromptLosslessJPEG()); + if (eResult != CJPEGLosslessTransform::Success) { + ::MessageBox(m_hWnd, CString(CNLS::GetString(_T("Performing the lossless transformation failed!"))) + + _T("\n") + CNLS::GetString(_T("Reason:")) + _T(" ") + HelpersGUI::LosslessTransformationResultToString(eResult), + CNLS::GetString(_T("Lossless JPEG transformations")), MB_OK | MB_ICONERROR); + } else { + ReloadImage(false); // reload current image } } } @@ -1724,10 +1775,27 @@ void CMainDlg::ExecuteCommand(int nCommand) { break; case IDM_TOGGLE_FIT_TO_SCREEN_100_PERCENTS: case IDM_TOGGLE_FILL_WITH_CROP_100_PERCENTS: - if (fabs(m_dZoom - 1) < 0.01) { - ResetZoomToFitScreen(nCommand == IDM_TOGGLE_FILL_WITH_CROP_100_PERCENTS, true, true); - } else { - ResetZoomTo100Percents(m_bMouseOn); + if (m_pCurrentImage != NULL) { + double dZoomForFitToScreen = GetZoomFactorForFitToScreen(nCommand == IDM_TOGGLE_FILL_WITH_CROP_100_PERCENTS, true); + if (dZoomForFitToScreen < 1.0) { // The image is larger than the screen. Always go to fit-to-screen, unless we’re already there. + if (abs(dZoomForFitToScreen - m_dZoom) < 0.001) { + ResetZoomTo100Percents(m_bMouseOn); + } + else { + ResetZoomToFitScreen(nCommand == IDM_TOGGLE_FILL_WITH_CROP_100_PERCENTS, true, true); + } + } + else { // The image is smaller than the screen. Always go to 100%, unless we’re already there. + if (abs(m_dZoom - 1.0) < 0.01) { + ResetZoomToFitScreen(nCommand == IDM_TOGGLE_FILL_WITH_CROP_100_PERCENTS, true, true); + // ResetToFit does not usually show timer. In this zoom-in case let’s show it. + m_bInZooming = true; + StartLowQTimer(ZOOM_TIMEOUT); + } + else { + ResetZoomTo100Percents(m_bMouseOn); + } + } } break; case IDM_SPAN_SCREENS: @@ -1746,49 +1814,7 @@ void CMainDlg::ExecuteCommand(int nCommand) { } break; case IDM_FULL_SCREEN_MODE: - m_bFullScreenMode = !m_bFullScreenMode; - m_dZoomAtResizeStart = 1.0; - if (!m_bFullScreenMode) { - CRect windowRect; - - // restore hidden title bar if enabled - SetCurrentWindowStyle(); - - HICON hIconSmall = (HICON)::LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_MAINFRAME), - IMAGE_ICON, ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR); - SetIcon(hIconSmall, FALSE); - CRect defaultWindowRect = CMultiMonitorSupport::GetDefaultWindowRect(); - double dZoom = -1; - windowRect = sp.ExplicitWindowRect() ? - defaultWindowRect : - Helpers::GetWindowRectMatchingImageSize( - m_hWnd, - CSize(MIN_WND_WIDTH, MIN_WND_HEIGHT), - defaultWindowRect.Size(), - dZoom, m_pCurrentImage, false, true, m_bWindowBorderless); - this->SetWindowPos(HWND_TOP, windowRect.left, windowRect.top, windowRect.Width(), windowRect.Height(), SWP_NOZORDER | SWP_NOCOPYBITS); - this->MouseOn(); - m_bSpanVirtualDesktop = false; - } else { - if (!IsZoomed() && sp.ExplicitWindowRect()) { - // Save the old window rect to be able to restore it - CRect rect; - GetWindowRect(&rect); - CMultiMonitorSupport::SetDefaultWindowRect(rect); - } - HMONITOR hMonitor = ::MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST); - MONITORINFO monitorInfo; - monitorInfo.cbSize = sizeof(MONITORINFO); - if (::GetMonitorInfo(hMonitor, &monitorInfo)) { - CRect monitorRect(&(monitorInfo.rcMonitor)); - this->SetWindowLongW(GWL_STYLE, WS_VISIBLE); - this->SetWindowPos(HWND_TOP, monitorRect.left, monitorRect.top, monitorRect.Width(), monitorRect.Height(), SWP_NOZORDER | SWP_NOCOPYBITS); - } - this->MouseOn(); - } - m_dZoom = -1; - StartLowQTimer(ZOOM_TIMEOUT); - this->SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOCOPYBITS | SWP_FRAMECHANGED); + ToggleFullScreen(sp); break; case IDM_HIDE_TITLE_BAR: if (!m_bFullScreenMode) { @@ -1891,7 +1917,10 @@ void CMainDlg::ExecuteCommand(int nCommand) { } case IDM_EDIT_GLOBAL_CONFIG: case IDM_EDIT_USER_CONFIG: - EditINIFile(nCommand == IDM_EDIT_GLOBAL_CONFIG); + EditSettingsFile(nCommand == IDM_EDIT_GLOBAL_CONFIG); + break; + case IDM_EDIT_USER_KEYMAP: + EditKeymapFile(nCommand == IDM_EDIT_GLOBAL_CONFIG); break; case IDM_UPDATE_USER_CONFIG: if (::MessageBox(m_hWnd, CString(CNLS::GetString(_T("Update user settings with new settings from settings template file?"))) + _T('\n') + @@ -1937,7 +1966,11 @@ void CMainDlg::ExecuteCommand(int nCommand) { CleanupAndTerminate(); else StopAnimation(); // stop any running animation - } else { + } + else if (m_bFullScreenMode) { + ToggleFullScreen(sp); + } + else { CleanupAndTerminate(); } break; @@ -2111,6 +2144,71 @@ void CMainDlg::ExecuteCommand(int nCommand) { } } +void CMainDlg::ToggleFullScreen(CSettingsProvider& sp) { + m_bFullScreenMode = !m_bFullScreenMode; + m_dZoomAtResizeStart = 1.0; + bool colorTransparencyChanged = sp.ColorTransparency(true) != sp.ColorTransparency(false); + if (colorTransparencyChanged) { + if (m_pCurrentImage->SourceOriginalChannels() == 4) { + // Image has alpha channel. Reload it to blend it with changed ColorTransparency. + ReloadImage(false); + } + // Clear cache because the other images may have alpha so they need to be re-blended too. + m_pJPEGProvider->RemoveUnusedImages(m_pFileList, CJPEGProvider::FORWARD, true, nullptr); + } + + if (!m_bFullScreenMode) { + CRect windowRect; + + // restore hidden title bar if enabled + SetCurrentWindowStyle(); + + HICON hIconSmall = (HICON)::LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_MAINFRAME), + IMAGE_ICON, ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR); + SetIcon(hIconSmall, FALSE); + CRect defaultWindowRect = CMultiMonitorSupport::GetDefaultWindowRect(); + double dZoom = -1; + windowRect = sp.ExplicitWindowRect() ? + defaultWindowRect : + Helpers::GetWindowRectMatchingImageSize( + m_hWnd, + CSize(MIN_WND_WIDTH, MIN_WND_HEIGHT), + defaultWindowRect.Size(), + dZoom, m_pCurrentImage, false, true, m_bWindowBorderless); + if (sp.DefaultMaximized()) { + this->ShowWindow(SW_MAXIMIZE); + } + else { + this->SetWindowPos(HWND_TOP, windowRect.left, windowRect.top, windowRect.Width(), windowRect.Height(), SWP_NOZORDER | SWP_NOCOPYBITS); + } + this->MouseOn(); + m_bSpanVirtualDesktop = false; + } + else { + if (!IsZoomed() && sp.ExplicitWindowRect()) { + // Save the old window rect to be able to restore it + CRect rect; + GetWindowRect(&rect); + CMultiMonitorSupport::SetDefaultWindowRect(rect); + } + HMONITOR hMonitor = ::MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO monitorInfo; + monitorInfo.cbSize = sizeof(MONITORINFO); + if (::GetMonitorInfo(hMonitor, &monitorInfo)) { + CRect monitorRect(&(monitorInfo.rcMonitor)); + this->SetWindowLongW(GWL_STYLE, WS_VISIBLE); + this->SetWindowPos(HWND_TOP, monitorRect.left, monitorRect.top, monitorRect.Width(), monitorRect.Height(), SWP_NOZORDER | SWP_NOCOPYBITS); + } + this->MouseOn(); + } + m_dZoom = -1; + StartLowQTimer(ZOOM_TIMEOUT); + this->SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOCOPYBITS | SWP_FRAMECHANGED); + if (colorTransparencyChanged) { + m_pJPEGProvider->StartNewPreloadRequestBundle(m_pFileList, CJPEGProvider::FORWARD, CreateProcessParams(!m_bFullScreenMode), sp.ColorTransparency(m_bFullScreenMode), nullptr); + } +} + // Setting window styles have gotten out of hand with the addition of no title bar // instead of each call trying to figure out the logic, consolidate it to one function LONG CMainDlg::SetCurrentWindowStyle() { @@ -2136,10 +2234,11 @@ bool CMainDlg::OpenFileWithDialog(bool bFullScreen, bool bAfterStartup) { StopMovieMode(); StopAnimation(); MouseOn(); - CFileOpenDialog dlgOpen(this->m_hWnd, m_pFileList->Current(), CFileList::GetSupportedFileEndings(), bFullScreen); + FileOpenDialog dlgOpen(m_pFileList->Current(), CFileList::GetSupportedFileEndings()); if (IDOK == dlgOpen.DoModal(this->m_hWnd)) { m_isBeforeFileSelected = false; - OpenFile(dlgOpen.m_szFileName, bAfterStartup); + CString filePath = dlgOpen.GetFilePathStr(); + OpenFile(filePath, bAfterStartup); return true; } m_isBeforeFileSelected = false; @@ -2161,6 +2260,7 @@ void CMainDlg::OpenFile(LPCTSTR sFileName, bool bAfterStartup) { m_pJPEGProvider->ClearAllRequests(); m_pCurrentImage = m_pJPEGProvider->RequestImage(m_pFileList, CJPEGProvider::FORWARD, m_pFileList->Current(), 0, CreateProcessParams(!m_bFullScreenMode && (bAfterStartup || IsAdjustWindowToImage())), + CSettingsProvider::This().ColorTransparency(m_bFullScreenMode), m_bOutOfMemoryLastImage, m_bExceptionErrorLastImage); m_nLastLoadError = GetLoadErrorAfterOpenFile(); if (bAfterStartup) CheckIfApplyAutoFitWndToImage(false); @@ -2409,6 +2509,8 @@ void CMainDlg::GotoImage(EImagePosition ePos) { } void CMainDlg::GotoImage(EImagePosition ePos, int nFlags) { + auto before = std::chrono::high_resolution_clock::now(); + // Timer handling for slideshows if (ePos == POS_Next || ePos == POS_NextSlideShow) { if (m_nCurrentTimeout > 0) { @@ -2535,6 +2637,7 @@ void CMainDlg::GotoImage(EImagePosition ePos, int nFlags) { } else { m_pCurrentImage = m_pJPEGProvider->RequestImage(m_pFileList, (ePos == POS_AwayFromCurrent) ? CJPEGProvider::NONE : eDirection, m_pFileList->Current(), nFrameIndex, procParams, + CSettingsProvider::This().ColorTransparency(m_bFullScreenMode), m_bOutOfMemoryLastImage, m_bExceptionErrorLastImage); m_nLastLoadError = (m_pCurrentImage == NULL) ? ((m_pFileList->Current() == NULL) ? HelpersGUI::FileLoad_NoFilesInDirectory : HelpersGUI::FileLoad_LoadError) : HelpersGUI::FileLoad_Ok; } @@ -2564,7 +2667,7 @@ void CMainDlg::GotoImage(EImagePosition ePos, int nFlags) { AdjustWindowToImage(false); } - if (((nFlags & NO_UPDATE_WINDOW) == 0) && !(ePos == POS_NextSlideShow && UseSlideShowTransitionEffect())) { + if (((nFlags & NO_UPDATE_WINDOW) == 0) && !(ePos == POS_NextSlideShow && UseSlideShowTransitionEffect())) { this->Invalidate(FALSE); // this will force to wait until really redrawn, preventing to process images but do not show them this->UpdateWindow(); @@ -2575,6 +2678,12 @@ void CMainDlg::GotoImage(EImagePosition ePos, int nFlags) { MSG msg; while (::PeekMessage(&msg, this->m_hWnd, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)); } + + auto afterGotoImage = std::chrono::high_resolution_clock::now(); + CString durationStr2; + long double durationLongDouble2 = std::chrono::duration_cast(afterGotoImage - before).count(); + durationStr2.Format(_T("GotoImage total took %g ms\n"), durationLongDouble2); + ::OutputDebugString(durationStr2); } void CMainDlg::ReloadImage(bool keepParameters, bool updateWindow) { @@ -3201,36 +3310,56 @@ CRect CMainDlg::GetZoomTextRect(CRect imageProcessingArea) { nEndX, imageProcessingArea.bottom - HelpersGUI::ScaleToScreen(nZoomTextRectBottomOffset)); } -void CMainDlg::EditINIFile(bool bGlobalINI) { +void CMainDlg::EditSettingsFile(bool bGlobalINI) { LPCTSTR sINIFileName = bGlobalINI ? CSettingsProvider::This().GetGlobalINIFileName() : CSettingsProvider::This().GetUserINIFileName(); if (!bGlobalINI) { if (!CSettingsProvider::This().ExistsUserINI()) { // No user INI file, ask if global INI shall be copied if (IDYES == ::MessageBox(m_hWnd, CNLS::GetString(_T("No user INI file exists yet. Create user INI file from INI file template?")), _T(JPEGVIEW_TITLE), MB_YESNO | MB_ICONQUESTION)) { CSettingsProvider::This().CopyUserINIFromTemplate(); - } else { + } + else { return; } } } + EditTextFile(sINIFileName); +} +void CMainDlg::EditKeymapFile(bool bGlobalKeymap) { + if (!bGlobalKeymap) { + if (!CKeyMap::ExistsUserKeyMap()) { + // No user keymap file, ask if global keymap shall be copied + if (IDYES == ::MessageBox(m_hWnd, CNLS::GetString(_T("No user keymap file exists yet. Create user keymap file from keymap file template?")), _T(JPEGVIEW_TITLE), MB_YESNO | MB_ICONQUESTION)) { + CKeyMap::CopyUserKeymapFromTemplate(); + } + else { + return; + } + } + } + EditTextFile(bGlobalKeymap ? CKeyMap::GlobalKeymapFileName() : CKeyMap::UserKeymapFileName()); +} + +void CMainDlg::EditTextFile(LPCTSTR sFileName) { Helpers::EIniEditor iniEditor = CSettingsProvider::This().IniEditor(); CString command; LPCTSTR argument; if (iniEditor == Helpers::INI_Notepad) { command = _T("notepad.exe"); - argument = sINIFileName; + argument = sFileName; } else if (iniEditor == Helpers::INI_System) { - command = sINIFileName; + command = sFileName; argument = NULL; } else { command = CSettingsProvider::This().CustomIniEditor(); command.Replace(_T("%exepath%"), CSettingsProvider::This().GetEXEPath()); - argument = sINIFileName; + argument = sFileName; } ::ShellExecute(m_hWnd, _T("open"), command, argument, NULL, SW_SHOW); } + void CMainDlg::UpdateWindowTitle() { bool bShowFullPathInTitle = CSettingsProvider::This().ShowFullPathInTitle(); LPCTSTR sCurrentFileName = CurrentFileName(!bShowFullPathInTitle); diff --git a/src/JPEGView/MainDlg.h b/src/JPEGView/MainDlg.h index f7c629e8..ff9ad2ae 100644 --- a/src/JPEGView/MainDlg.h +++ b/src/JPEGView/MainDlg.h @@ -3,10 +3,12 @@ #pragma once +#include "resource.h" #include "MessageDef.h" #include "ProcessParams.h" #include "Helpers.h" #include "CropCtl.h" +#include "SettingsProvider.h" class CFileList; class CJPEGProvider; @@ -72,6 +74,7 @@ class CMainDlg : public CDialogImpl MESSAGE_HANDLER(WM_RBUTTONUP, OnRButtonUp) MESSAGE_HANDLER(WM_MBUTTONDOWN, OnMButtonDown) MESSAGE_HANDLER(WM_MBUTTONUP, OnMButtonUp) + MESSAGE_HANDLER(WM_MBUTTONDBLCLK, OnMButtonDblClk) MESSAGE_HANDLER(WM_XBUTTONDOWN, OnXButtonDown) MESSAGE_HANDLER(WM_XBUTTONDBLCLK, OnXButtonDown) MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove) @@ -115,6 +118,7 @@ class CMainDlg : public CDialogImpl LRESULT OnLButtonDblClk(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/); LRESULT OnMButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/); LRESULT OnMButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/); + LRESULT OnMButtonDblClk(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/); LRESULT OnXButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/); LRESULT OnMouseMove(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/); LRESULT OnMouseWheel(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/); @@ -335,6 +339,7 @@ class CMainDlg : public CDialogImpl bool m_bSelectZoom; // keeps track of select-to-zoom mode when CTRL+SHIFT+LMouse void ExploreFile(); + void ToggleFullScreen(CSettingsProvider& sp); bool OpenFileWithDialog(bool bFullScreen, bool bAfterStartup); void OpenFile(LPCTSTR sFileName, bool bAfterStartup); bool SaveImage(bool bFullSize); @@ -365,7 +370,9 @@ class CMainDlg : public CDialogImpl CRect ScreenToDIB(const CSize& sizeDIB, const CRect& rect); void ToggleMonitor(); CRect GetZoomTextRect(CRect imageProcessingArea); - void EditINIFile(bool bGlobalINI); + void EditSettingsFile(bool bGlobalINI); + void EditKeymapFile(bool bGlobalKeymap); + void EditTextFile(LPCTSTR sFileName); int GetLoadErrorAfterOpenFile(); void CheckIfApplyAutoFitWndToImage(bool bInInitDialog); void PrefetchDIB(const CRect& clientRect); diff --git a/src/JPEGView/NavigationPanelCtl.cpp b/src/JPEGView/NavigationPanelCtl.cpp index a0d4f4d2..b2af48a3 100644 --- a/src/JPEGView/NavigationPanelCtl.cpp +++ b/src/JPEGView/NavigationPanelCtl.cpp @@ -241,11 +241,11 @@ void CNavigationPanelCtl::DoNavPanelAnimation() { if (pDIBData != NULL) { if (m_pMemDCAnimation == NULL) { m_pMemDCAnimation = new CDC(); - m_hOffScreenBitmapAnimation = CPaintMemDCMgr::PrepareRectForMemDCPainting(*m_pMemDCAnimation, screenDC, rectNavPanel); + m_hOffScreenBitmapAnimation = CPaintMemDCMgr::PrepareRectForMemDCPainting(*m_pMemDCAnimation, screenDC, rectNavPanel, m_pMainDlg->IsFullScreenMode()); } CBrush backBrush; - backBrush.CreateSolidBrush(CSettingsProvider::This().ColorBackground()); + backBrush.CreateSolidBrush(CSettingsProvider::This().ColorBackground(m_pMainDlg->IsFullScreenMode())); m_pMemDCAnimation->FillRect(CRect(0, 0, rectNavPanel.Width(), rectNavPanel.Height()), backBrush); BITMAPINFO bmInfo{ 0 }; diff --git a/src/JPEGView/PaintMemDCMgr.cpp b/src/JPEGView/PaintMemDCMgr.cpp index 8dd0cf9b..b7745032 100644 --- a/src/JPEGView/PaintMemDCMgr.cpp +++ b/src/JPEGView/PaintMemDCMgr.cpp @@ -3,8 +3,10 @@ #include "EXIFDisplay.h" #include "SettingsProvider.h" #include "PaintMemDCMgr.h" +#include "MainDlg.h" -CPaintMemDCMgr::CPaintMemDCMgr(CPaintDC& paintDC) : m_paintDC(paintDC) { +CPaintMemDCMgr::CPaintMemDCMgr(CMainDlg* pMainDlg, CPaintDC& paintDC) : m_paintDC(paintDC) { + m_pMainDlg = pMainDlg; m_nNumElems = 0; for (int i = 0; i < MAX_REGIONS_CPaintMemDCMgr; i++) { m_managedRegions[i].MemoryDC = CDC(); @@ -37,9 +39,9 @@ void CPaintMemDCMgr::IncludeIntoClippingRegion(CDC & paintDC, const std::listIsFullScreenMode()); m_nNumElems++; return displayRect; diff --git a/src/JPEGView/PaintMemDCMgr.h b/src/JPEGView/PaintMemDCMgr.h index 797e8b0a..667cea40 100644 --- a/src/JPEGView/PaintMemDCMgr.h +++ b/src/JPEGView/PaintMemDCMgr.h @@ -2,6 +2,7 @@ // forward declarations class CPanel; +class CMainDlg; #define MAX_REGIONS_CPaintMemDCMgr 16 @@ -9,7 +10,7 @@ class CPanel; // This eliminates flickering. class CPaintMemDCMgr { public: - CPaintMemDCMgr(CPaintDC& paintDC); + CPaintMemDCMgr(CMainDlg* pMainDlg, CPaintDC& paintDC); ~CPaintMemDCMgr(); CPaintDC& GetPaintDC() { return m_paintDC; } @@ -19,7 +20,7 @@ class CPaintMemDCMgr { static void IncludeIntoClippingRegion(CDC & paintDC, const std::list& listExcludedRects); // Prepares a memory DC of given size by creating the backing store bitmap and clearing it - static HBITMAP PrepareRectForMemDCPainting(CDC & memDC, CDC & paintDC, const CRect& rect); + static HBITMAP PrepareRectForMemDCPainting(CDC & memDC, CDC & paintDC, const CRect& rect, const bool isFullscreen); // Blits the DIB data section to target DC using dimming (blending with a black bitmap) static void BitBltBlended(CDC & dc, CDC & paintDC, const CSize& dcSize, void* pDIBData, BITMAPINFO* pbmInfo, @@ -50,4 +51,6 @@ class CPaintMemDCMgr { CPaintDC& m_paintDC; int m_nNumElems; CManagedRegion m_managedRegions[MAX_REGIONS_CPaintMemDCMgr]; + + CMainDlg* m_pMainDlg; }; \ No newline at end of file diff --git a/src/JPEGView/SettingsProvider.cpp b/src/JPEGView/SettingsProvider.cpp index 3ba29504..cd599a18 100644 --- a/src/JPEGView/SettingsProvider.cpp +++ b/src/JPEGView/SettingsProvider.cpp @@ -222,12 +222,14 @@ CSettingsProvider::CSettingsProvider(void) { } m_colorBackground = GetColor(_T("BackgroundColor"), 0); + m_colorBackgroundFullscreen = GetColor(_T("BackgroundColorFullscreen"), m_colorBackground); m_colorGUI = GetColor(_T("GUIColor"), RGB(243, 242, 231)); m_colorHighlight = GetColor(_T("HighlightColor"), RGB(255, 205, 0)); m_colorSelected = GetColor(_T("SelectionColor"), RGB(255, 205, 0)); m_colorSlider = GetColor(_T("SliderColor"), RGB(255, 0, 80)); m_colorFileName = GetColor(_T("FileNameColor"), m_colorGUI); m_colorTransparency = GetColor(_T("TransparencyColor"), m_colorBackground); + m_colorTransparencyFullscreen = GetColor(_T("TransparencyColorFullscreen"), m_colorBackgroundFullscreen); m_defaultGUIFont = GetString(_T("DefaultGUIFont"), _T("Default")); m_fileNameFont = GetString(_T("FileNameFont"), _T("Default")); diff --git a/src/JPEGView/SettingsProvider.h b/src/JPEGView/SettingsProvider.h index bce046ca..29f3f21f 100644 --- a/src/JPEGView/SettingsProvider.h +++ b/src/JPEGView/SettingsProvider.h @@ -101,13 +101,13 @@ class CSettingsProvider bool DefaultMaximized() { return m_bDefaultMaximized; } bool ExplicitWindowRect() { return m_bExplicitWindowRect; } CSize DefaultFixedCropSize() { return m_DefaultFixedCropSize; } - COLORREF ColorBackground() { return m_colorBackground; } + COLORREF ColorBackground(int bIsFullscreen = false) { if (bIsFullscreen) return m_colorBackgroundFullscreen; else return m_colorBackground; } COLORREF ColorGUI() { return m_colorGUI; } COLORREF ColorHighlight() { return m_colorHighlight; } COLORREF ColorSelected() { return m_colorSelected; } COLORREF ColorSlider() { return m_colorSlider; } COLORREF ColorFileName() { return m_colorFileName; } - COLORREF ColorTransparency() { return m_colorTransparency; } + COLORREF ColorTransparency(int bIsFullscreen = false) { if (bIsFullscreen) return m_colorTransparencyFullscreen; else return m_colorTransparency; } LPCTSTR DefaultGUIFont() { return m_defaultGUIFont; } LPCTSTR FileNameFont() { return m_fileNameFont; } const CUnsharpMaskParams& UnsharpMaskParams() { return m_unsharpMaskParms; } @@ -272,12 +272,14 @@ class CSettingsProvider bool m_bExplicitWindowRect; CSize m_DefaultFixedCropSize; COLORREF m_colorBackground; + COLORREF m_colorBackgroundFullscreen; COLORREF m_colorGUI; COLORREF m_colorHighlight; COLORREF m_colorSelected; COLORREF m_colorSlider; COLORREF m_colorFileName; COLORREF m_colorTransparency; + COLORREF m_colorTransparencyFullscreen; CString m_defaultGUIFont; CString m_fileNameFont; CUnsharpMaskParams m_unsharpMaskParms; diff --git a/src/JPEGView/TimerEventIDs.h b/src/JPEGView/TimerEventIDs.h index 0b095b72..910042df 100644 --- a/src/JPEGView/TimerEventIDs.h +++ b/src/JPEGView/TimerEventIDs.h @@ -8,4 +8,5 @@ #define NAVPANEL_ANI_TIMER_EVENT_ID 6 // animation timer for navigation panel #define NAVPANEL_START_ANI_TIMER_EVENT_ID 7 // animation start timer for navigation panel #define IPPANEL_TIMER_EVENT_ID 8 // to show image processing panel in window mode -#define ANIMATION_TIMER_EVENT_ID 9 // GIF animation timer ID \ No newline at end of file +#define ANIMATION_TIMER_EVENT_ID 9 // GIF animation timer ID +#define SHOW_LOADING_TIMER_EVENT_ID 10 // Loading indicator timer ID \ No newline at end of file diff --git a/src/JPEGView/resource.h b/src/JPEGView/resource.h index 9fc158ef..1f746c06 100644 --- a/src/JPEGView/resource.h +++ b/src/JPEGView/resource.h @@ -162,7 +162,8 @@ #define IDM_TOUCH_IMAGE_EXIF_FOLDER 2720 // :KeyMap: set modification time to EXIF time for all images in folder #define IDM_SET_WALLPAPER_ORIG 2770 // :KeyMap: Set original image file as desktop wallpaper #define IDM_SET_WALLPAPER_DISPLAY 2774 // :KeyMap: Set image as displayed as desktop wallpaper -#define IDM_SHOW_FILEINFO 2800 // :KeyMap: toggle show file and EXIF info box in top, left corner +#define IDM_SHOW_FILEINFO 2800 // :KeyMap: toggle show file and EXIF info box in top-left corner +#define IDM_SHOW_FILEINFO_HISTOGRAM 2830 // :KeyMap: toggle show details and histogram in EXIF info box in top-left corner #define IDM_SHOW_FILENAME 3000 // :KeyMap: toggle show file name on top of screen #define IDM_SHOW_NAVPANEL 3100 // :KeyMap: toggle show navigation panel #define IDM_NEXT 4000 // :KeyMap: go to next image @@ -258,6 +259,8 @@ #define IDM_AUTO_ZOOM_FILL_NO_ZOOM 12510 // :KeyMap: set auto zoom mode fill screen, never zoom #define IDM_AUTO_ZOOM_FIT 12520 // :KeyMap: set auto zoom mode fit to screen #define IDM_AUTO_ZOOM_FILL 12530 // :KeyMap: set auto zoom mode fill screen +#define IDM_EDIT_GLOBAL_KEYMAP 12590 // :KeyMap: edit global keymap +#define IDM_EDIT_USER_KEYMAP 12695 // :KeyMap: edit user keymap #define IDM_EDIT_GLOBAL_CONFIG 12600 // :KeyMap: edit global configuration #define IDM_EDIT_USER_CONFIG 12610 // :KeyMap: edit user configuration #define IDM_MANAGE_OPEN_WITH_MENU 12612 @@ -321,18 +324,26 @@ // in the main menu // these position must be changed if menu items are inserted -#define SUBMENU_POS_OPENWITH 3 -#define SUBMENU_POS_MODDATE 9 -#define SUBMENU_POS_WALLPAPER 10 -#define SUBMENU_POS_NAVIGATION 23 -#define SUBMENU_POS_DISPLAY_ORDER 24 -#define SUBMENU_POS_MOVIE 25 -#define SUBMENU_POS_TRANSFORM 27 -#define SUBMENU_POS_TRANSFORM_LOSSLESS 28 -#define SUBMENU_POS_ZOOM 36 -#define SUBMENU_POS_AUTOZOOMMODE 37 -#define SUBMENU_POS_SETTINGS 39 -#define SUBMENU_POS_USER_COMMANDS 41 +#define SUBMENU_POS_OPENWITH 2 +#define SUBMENU_POS_WALLPAPER 7 +#define SUBMENU_POS_NAVIGATION 11 +#define SUBMENU_POS_ZOOM 12 +#define SUBMENU_POS_MODIFY 13 +#define SUBMENU_POS_USER_COMMANDS 14 +#define SUBMENU_POS_SETTINGS 16 + +// in modify menu +#define SUBMENU_POS_MODIFY_MODDATE 6 +#define SUBMENU_POS_TRANSFORM 2 +#define SUBMENU_POS_TRANSFORM_LOSSLESS 3 + +// in navigation menu +#define SUBMENU_POS_NAVIGATION_LOOP 6 +#define SUBMENU_POS_DISPLAY_ORDER 7 +#define SUBMENU_POS_SLIDESHOW 8 + +// in zoom menu +#define SUBMENU_POS_AUTOZOOMMODE 15 // in the crop menu #define SUBMENU_POS_CROPMODE 3 diff --git a/src/JPEGView/stdafx.h b/src/JPEGView/stdafx.h index 763bbf71..f89353c8 100644 --- a/src/JPEGView/stdafx.h +++ b/src/JPEGView/stdafx.h @@ -9,8 +9,8 @@ #pragma warning(disable:4800) // Change these values to use different versions -#define WINVER 0x0501 -#define _WIN32_WINNT 0x0501 +#define WINVER 0x0A00 +#define _WIN32_WINNT 0x0A00 #define _WIN32_IE 0x0600 #define _RICHEDIT_VER 0x0300 diff --git a/src/UpgradeLog.htm b/src/UpgradeLog.htm new file mode 100644 index 00000000..a5bd6c4c --- /dev/null +++ b/src/UpgradeLog.htm @@ -0,0 +1,275 @@ + + + + Migration Report +

+ Migration Report -

\ No newline at end of file diff --git a/src/WICLoader/WICLoader.vcxproj b/src/WICLoader/WICLoader.vcxproj index 0a2ad09b..ac488318 100644 --- a/src/WICLoader/WICLoader.vcxproj +++ b/src/WICLoader/WICLoader.vcxproj @@ -29,27 +29,27 @@ DynamicLibrary true Unicode - v142 + v143 DynamicLibrary true Unicode - v142 + v143 DynamicLibrary false true Unicode - v142 + v143 DynamicLibrary false true Unicode - v142 + v143