diff --git a/src/JPEGView/AboutDlg.cpp b/src/JPEGView/AboutDlg.cpp index 6e1e53dd..73edafd2 100644 --- a/src/JPEGView/AboutDlg.cpp +++ b/src/JPEGView/AboutDlg.cpp @@ -11,11 +11,11 @@ static LPCTSTR GetSIMDModeString() { Helpers::CPUType cpuType = CSettingsProvider::This().AlgorithmImplementation(); if (cpuType == Helpers::CPU_MMX) { - return _T("64 bit MMX"); + return _T("64-bit MMX"); } else if (cpuType == Helpers::CPU_SSE) { - return _T("128 bit SSE2"); + return _T("128-bit SSE2"); } else if (cpuType == Helpers::CPU_AVX2) { - return _T("256 bit AVX2"); + return _T("256-bit AVX2"); } else { return _T("Generic CPU"); diff --git a/src/JPEGView/BatchCopyDlg.cpp b/src/JPEGView/BatchCopyDlg.cpp index 5b760098..81c7c0bf 100644 --- a/src/JPEGView/BatchCopyDlg.cpp +++ b/src/JPEGView/BatchCopyDlg.cpp @@ -314,7 +314,7 @@ int CBatchCopyDlg::CreateItemList() { int nIndex = 0; for (iter = fileList.begin( ); iter != fileList.end( ); iter++ ) { m_lvFiles.InsertItem(nIndex, iter->GetTitle()); - SYSTEMTIME systemTime; + SYSTEMTIME systemTime{ 0 }; FileTimeToLocalSystemTime(iter->GetLastModTime(), systemTime); TCHAR dateBuff[64]; ::GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systemTime, NULL, dateBuff, 64); @@ -382,7 +382,7 @@ CString CBatchCopyDlg::ReplacePlaceholders(LPCTSTR strPattern, int nIndex, const CString sExtension(pStartExt + 1); strNewName.Replace(_T("%e"), sExtension); } - SYSTEMTIME systemTime; + SYSTEMTIME systemTime{ 0 }; FileTimeToLocalSystemTime(fileDesc.GetLastModTime(), systemTime); if (strNewName.Find(_T("%h")) != -1) { CString sHour; diff --git a/src/JPEGView/Config/JPEGView.ini b/src/JPEGView/Config/JPEGView.ini index ded706e6..d046c558 100644 --- a/src/JPEGView/Config/JPEGView.ini +++ b/src/JPEGView/Config/JPEGView.ini @@ -75,6 +75,20 @@ FileNameColor=255 255 255 ; Example: DefaultGUIFont="Arial" 9.0 bold DefaultGUIFont=Default + +; ----------------------------------------------- +; - WINDOW BEHAVIOR OPTIONS +; ----------------------------------------------- + +; If set to 'true', window starts in always-on-top mode (window will always be visible on top of other windows) +; The window mode can be changed after startup +WindowAlwaysOnTopOnStartup=false + + +; ----------------------------------------------- +; - ADVANCED IMAGE CORRECTION PARAMETERS +; ----------------------------------------------- + ; Contrast correction to apply to all images. Must be in -0.5 .. 0.5 ; Values > 0 increase contrast, values < 0 decrease contrast Contrast=0.0 @@ -91,6 +105,14 @@ Saturation=1.0 ; Note that for 100 % zoom, the BestQuality filter will not apply any sharpening, only the other filters do Sharpen=0.3 + + +; ***************************************************************************** +; * DEFAULT IMAGE EDITING OPTIONS +; * +; * These options are related to JPEGView defaults for image editing +; ***************************************************************************** + ; Default parameters for unsharp masking: Radius Amount Threshold ; Note that no unsharp masking can be applied automatically to every image - this setting only provides the default parameters ; when entering the unsharp mask mode diff --git a/src/JPEGView/DirectoryWatcher.cpp b/src/JPEGView/DirectoryWatcher.cpp index 99b6c4b4..c3c8ad25 100644 --- a/src/JPEGView/DirectoryWatcher.cpp +++ b/src/JPEGView/DirectoryWatcher.cpp @@ -23,9 +23,10 @@ static BOOL GetLastModificationTime(LPCTSTR fileName, FILETIME & lastModificatio // Public ///////////////////////////////////////////////////////////////////////////////////////////// -CDirectoryWatcher::CDirectoryWatcher(HWND hTargetWindow) { +CDirectoryWatcher::CDirectoryWatcher(HWND hTargetWindow) + : m_lock{ 0 } +{ m_hTargetWindow = hTargetWindow; - memset(&m_lock, 0, sizeof(CRITICAL_SECTION)); ::InitializeCriticalSection(&m_lock); m_terminateEvent = ::CreateEvent(0, TRUE, FALSE, NULL); m_newDirectoryEvent = ::CreateEvent(0, TRUE, FALSE, NULL); @@ -75,8 +76,7 @@ void CDirectoryWatcher::SetCurrentFile(LPCTSTR fileName) void CDirectoryWatcher::SetCurrentDirectory(LPCTSTR directoryName) { - TCHAR fullName[MAX_PATH]; - memset(fullName, 0, sizeof(TCHAR) * MAX_PATH); + TCHAR fullName[MAX_PATH]{ 0 }; GetFullPathName(directoryName, MAX_PATH, (LPTSTR)fullName, NULL); ::EnterCriticalSection(&m_lock); @@ -98,8 +98,7 @@ void CDirectoryWatcher::ThreadFunc(void* arg) { CDirectoryWatcher* thisPtr = (CDirectoryWatcher*) arg; bool bTerminate = false; bool bSetupNewDirectory = true; - HANDLE waitHandles[4]; - memset(waitHandles, 0, sizeof(HANDLE) * 4); + HANDLE waitHandles[4]{ 0 }; do { waitHandles[0] = thisPtr->m_terminateEvent; waitHandles[1] = thisPtr->m_newDirectoryEvent; diff --git a/src/JPEGView/EXIFReader.cpp b/src/JPEGView/EXIFReader.cpp index 1a79ce0e..981b5cdb 100644 --- a/src/JPEGView/EXIFReader.cpp +++ b/src/JPEGView/EXIFReader.cpp @@ -242,9 +242,10 @@ bool CEXIFReader::ParseDateString(SYSTEMTIME & date, const CString& str) { } CEXIFReader::CEXIFReader(void* pApp1Block, EImageFormat eImageFormat) -: m_exposureTime(0, 0) { - - memset(&m_acqDate, 0, sizeof(SYSTEMTIME)); + : m_exposureTime{ 0, 0 }, + m_acqDate{ 0 }, + m_dateTime{ 0 } +{ m_bFlashFired = false; m_bFlashFlagPresent = false; m_dFocalLength = m_dExposureBias = m_dFNumber = UNKNOWN_DOUBLE_VALUE; @@ -327,6 +328,14 @@ CEXIFReader::CEXIFReader(void* pApp1Block, EImageFormat eImageFormat) } } + uint8* pTagSoftware = FindTag(pIFD0, pLastIFD0, 0x0131, bLittleEndian); + ReadStringTag(m_sSoftware, pTagSoftware, pTIFFHeader, bLittleEndian); + + uint8* pTagModDate = FindTag(pIFD0, pLastIFD0, 0x0132, bLittleEndian); + CString sModDate; + ReadStringTag(sModDate, pTagModDate, pTIFFHeader, bLittleEndian); + ParseDateString(m_dateTime, sModDate); + uint8* pTagEXIFIFD = FindTag(pIFD0, pLastIFD0, 0x8769, bLittleEndian); if (pTagEXIFIFD == NULL) { return; @@ -507,6 +516,10 @@ void CEXIFReader::ReadGPSData(uint8* pTIFFHeader, uint8* pTagGPSIFD, int nApp1Si uint8* pTagAltitude = FindTag(pGPSIFD, pLastGPS, 0x6, bLittleEndian); if (pTagAltitude != NULL) { m_dAltitude = ReadDoubleTag(pTagAltitude, pTIFFHeader, bLittleEndian); + uint8* pTagAltitudeRef = FindTag(pGPSIFD, pLastGPS, 0x5, bLittleEndian); + if (pTagAltitudeRef != NULL && *(pTagAltitudeRef + 8) == 1) { + m_dAltitude *= -1; + } } } diff --git a/src/JPEGView/EXIFReader.h b/src/JPEGView/EXIFReader.h index 98716075..f13264e5 100644 --- a/src/JPEGView/EXIFReader.h +++ b/src/JPEGView/EXIFReader.h @@ -20,6 +20,14 @@ class GPSCoordinate { public: GPSCoordinate(LPCTSTR reference, double degrees, double minutes, double seconds) { m_sReference = CString(reference); + if (minutes == 0.0 && seconds == 0.0) { + minutes = 60 * abs(degrees - (int)degrees); + degrees = (int)degrees; + } + if (seconds == 0.0) { + seconds = 60 * abs(minutes - (int)minutes); + minutes = (int)minutes; + } Degrees = degrees; Minutes = minutes; Seconds = seconds; @@ -49,10 +57,15 @@ class CEXIFReader { LPCTSTR GetCameraModel() { return m_sModel; } LPCTSTR GetUserComment() { return m_sUserComment; } LPCTSTR GetImageDescription() { return m_sImageDescription; } + LPCTSTR GetSoftware() { return m_sSoftware; } bool GetCameraModelPresent() { return !m_sModel.IsEmpty(); } + bool GetSoftwarePresent() { return !m_sSoftware.IsEmpty(); } // Date-time the picture was taken const SYSTEMTIME& GetAcquisitionTime() { return m_acqDate; } bool GetAcquisitionTimePresent() { return m_acqDate.wYear > 1600; } + // Date-time the picture was saved/modified (used by editing software) + const SYSTEMTIME& GetDateTime() { return m_dateTime; } + bool GetDateTimePresent() { return m_dateTime.wYear > 1600; } // Exposure time const Rational& GetExposureTime() { return m_exposureTime; } bool GetExposureTimePresent() { return m_exposureTime.Denominator != 0; } @@ -106,7 +119,9 @@ class CEXIFReader { CString m_sModel; CString m_sUserComment; CString m_sImageDescription; + CString m_sSoftware; SYSTEMTIME m_acqDate; + SYSTEMTIME m_dateTime; Rational m_exposureTime; double m_dExposureBias; bool m_bFlashFired; diff --git a/src/JPEGView/FileExtensionsDlg.cpp b/src/JPEGView/FileExtensionsDlg.cpp index d0c221cb..43cb270c 100644 --- a/src/JPEGView/FileExtensionsDlg.cpp +++ b/src/JPEGView/FileExtensionsDlg.cpp @@ -279,7 +279,7 @@ LRESULT CFileExtensionsDlg::OnListViewKeyDown(WPARAM /*wParam*/, LPNMHDR lpnmhdr } void CFileExtensionsDlg::FillFileExtensionsList() { - InsertExtension(_T("*.jpg;*.jpeg"), FormatHint(CNLS::GetString(_T("%s images")), _T("JPEG"))); + InsertExtension(_T("*.jpg;*.jpeg;*.jfif"), FormatHint(CNLS::GetString(_T("%s images")), _T("JPEG"))); InsertExtension(_T("*.png"), FormatHint(CNLS::GetString(_T("%s images")), _T("PNG"))); InsertExtension(_T("*.avif"), FormatHint(CNLS::GetString(_T("%s images")), _T("AVIF"))); InsertExtension(_T("*.bmp"), FormatHint(CNLS::GetString(_T("%s images")), _T("Windows bitmap"))); @@ -292,6 +292,7 @@ void CFileExtensionsDlg::FillFileExtensionsList() { InsertExtension(_T("*.webp"), FormatHint(CNLS::GetString(_T("%s images")), _T("Google WEBP"))); InsertExtension(_T("*.jxl"), FormatHint(CNLS::GetString(_T("%s images")), _T("JPEG XL"))); InsertExtension(_T("*.qoi"), FormatHint(CNLS::GetString(_T("%s images")), _T("Quite OK Image"))); + InsertExtension(_T("*.psd"), FormatHint(CNLS::GetString(_T("%s images")), _T("Photoshop Document"))); InsertExtensions(CSettingsProvider::This().FilesProcessedByWIC(), CNLS::GetString(_T("%s images (processed by Window Imaging Component - WIC)"))); InsertExtensions(CSettingsProvider::This().FileEndingsRAW(), CNLS::GetString(_T("%s camera raw images (embedded JPEGs only)"))); } diff --git a/src/JPEGView/FileExtensionsRegistry.cpp b/src/JPEGView/FileExtensionsRegistry.cpp index 052e3229..25d1bff6 100644 --- a/src/JPEGView/FileExtensionsRegistry.cpp +++ b/src/JPEGView/FileExtensionsRegistry.cpp @@ -380,7 +380,7 @@ static RegResult ResetPermissionsForRegistryKey(LPCTSTR subKeyRelativeToHKCU) const int MAX_SIZE_SEC_DESC = 1024; SECURITY_DESCRIPTOR* pExistingSecDesc = (SECURITY_DESCRIPTOR*)new char[MAX_SIZE_SEC_DESC]; - std::auto_ptr auto_ptr_sec_desc((char*)pExistingSecDesc); + std::unique_ptr auto_ptr_sec_desc((char*)pExistingSecDesc); // Get the existing DACL of the registry key DWORD sizeDecDesc = MAX_SIZE_SEC_DESC; @@ -394,8 +394,7 @@ static RegResult ResetPermissionsForRegistryKey(LPCTSTR subKeyRelativeToHKCU) } // Obtain DACL information. - ACL_SIZE_INFORMATION asi; - memset(&asi, 0, sizeof(ACL_SIZE_INFORMATION)); + ACL_SIZE_INFORMATION asi{ 0 }; if (pACL != NULL && !::GetAclInformation(pACL, (LPVOID)&asi, (DWORD)sizeof(ACL_SIZE_INFORMATION), AclSizeInformation)) { return Reg_ErrorChangeDACL; } diff --git a/src/JPEGView/FileList.cpp b/src/JPEGView/FileList.cpp index 14cae4e1..3ae475fd 100644 --- a/src/JPEGView/FileList.cpp +++ b/src/JPEGView/FileList.cpp @@ -16,6 +16,16 @@ Helpers::ENavigationMode CFileList::sm_eMode = Helpers::NM_LoopDirectory; // Helper to add the current file of filefind object to the list static void AddToFileList(std::list & fileList, CFindFile &fileFind, LPCTSTR expectedExtension, int nMinFilesize = 1, bool bHideHidden = false) { + //std::list::iterator iter = fileList.begin(); + //if (iter != fileList.end()) + //{ + // CString fileName = fileFind.GetFilePath(), + // fileName0 = (*iter).GetName(); + // if (fileName == fileName0) + // { + // return; //already exists + // } + //} if (!fileFind.IsDirectory()) { if (expectedExtension != NULL) { // compare if the extension is the expected extension @@ -130,9 +140,10 @@ void CFileDesc::SetModificationDate(const FILETIME& lastModDate) { /////////////////////////////////////////////////////////////////////////////////// // image file types supported internally (there are additional endings for RAW and WIC - these come from INI file) -static const int cnNumEndingsInternal = 15; +// NOTE: when adding more supported filetypes, update installer to add another extension for "SupportedTypes" +static const int cnNumEndingsInternal = 17; static const TCHAR* csFileEndingsInternal[cnNumEndingsInternal] = {_T("jpg"), _T("jpeg"), _T("png"), - _T("avif"), _T("bmp"), _T("gif"), _T("heic"), _T("heif"), _T("ico"), _T("jxl"), _T("qoi"), _T("tif"), _T("tiff"), _T("tga"), _T("webp")}; + _T("avif"), _T("bmp"), _T("gif"), _T("heic"), _T("heif"), _T("ico"), _T("jxl"), _T("psb"), _T("psd"), _T("qoi"), _T("tif"), _T("tiff"), _T("tga"), _T("webp")}; // supported camera RAW formats static const TCHAR* csFileEndingsRAW = _T("*.pef;*.dng;*.crw;*.nef;*.cr2;*.mrw;*.rw2;*.orf;*.x3f;*.arw;*.kdc;*.nrw;*.dcr;*.sr2;*.raf"); @@ -358,8 +369,7 @@ bool CFileList::DeleteFile(LPCTSTR fileNameWithPath) const { _tcscpy(fileName, fileNameWithPath); fileName[_tcslen(fileName) + 1] = 0; - SHFILEOPSTRUCT fileOp; - memset(&fileOp, 0, sizeof(SHFILEOPSTRUCT)); + SHFILEOPSTRUCT fileOp{ 0 }; fileOp.hwnd = NULL; fileOp.wFunc = FO_DELETE; fileOp.pFrom = fileName; diff --git a/src/JPEGView/GUIControls.cpp b/src/JPEGView/GUIControls.cpp index 9b3ce337..1f8b154b 100644 --- a/src/JPEGView/GUIControls.cpp +++ b/src/JPEGView/GUIControls.cpp @@ -143,7 +143,14 @@ bool CTextCtrl::EnterEditMode() { m_pEdit->LimitText(256); m_pEdit->ShowWindow(SW_SHOW); m_pEdit->SetFocus(); - m_pEdit->SetSelAll(); + int last_dot_pos = m_sText.ReverseFind(_T('.')); + if (last_dot_pos == -1) { + // this selects the whole box + m_pEdit->SetSelAll(); + } else { + // select up till the extension + m_pEdit->SetSel(0, last_dot_pos, FALSE); + } return true; } diff --git a/src/JPEGView/HEIFWrapper.cpp b/src/JPEGView/HEIFWrapper.cpp index a3906217..29f7089f 100644 --- a/src/JPEGView/HEIFWrapper.cpp +++ b/src/JPEGView/HEIFWrapper.cpp @@ -51,21 +51,16 @@ void * HeifReader::ReadImage(int &width, } std::vector iccp = image.get_raw_color_profile(); void* transform = ICCProfileTransform::CreateTransform(iccp.data(), iccp.size(), ICCProfileTransform::FORMAT_RGBA); - uint8_t* p; size_t i, j; if (!ICCProfileTransform::DoTransform(transform, data, pPixelData, width, height, stride=stride)) { - memcpy(pPixelData, data, size); - unsigned char* o = pPixelData; - for (i = 0; i < height; i++) { - p = data + i * stride; - for (j = 0; j < width; j++) { + unsigned int* o = (unsigned int*)pPixelData; + for (i = 0; i < height; ++i) { + unsigned int* p = (unsigned int*)(data + i * stride); + for (j = 0; j < width; ++j) { // RGBA -> BGRA conversion - o[0] = p[2]; - o[1] = p[1]; - o[2] = p[0]; - o[3] = p[3]; - p += nchannels; - o += nchannels; + *o = _rotr(_byteswap_ulong(*p), 8); + ++p; + ++o; } } } @@ -75,12 +70,28 @@ void * HeifReader::ReadImage(int &width, if (!exif_blocks.empty()) { std::vector exif = handle.get_metadata(exif_blocks[0]); - if (exif.size() > 8 && exif.size() < 65538) { - exif_chunk = malloc(exif.size()); + // 65538 magic number comes from investigations by qbnu + // see https://github.com/sylikc/jpegview/pull/213#pullrequestreview-1494451359 for more details + /* + * These libraries all have their own ideas about where to start the Exif data from. + * JPEG Exif blocks are in the format + FF E1 SS SS 45 78 69 66 00 00 [data] + + The SS SS is a big-endian unsigned short representing the size of everything after FF E1. + + libjxl gives 00 00 00 00 [data] + libheif gives 00 00 00 00 45 78 69 66 00 00 [data] + libavif, libwebp and libpng give [data], so they have different limits for size. + + If you want I can change it to 65536 + an offset and add notes explaining why. + */ + size_t size = exif.size(); + if (size > 8 && size < 65538) { + exif_chunk = malloc(size); if (exif_chunk != NULL) { - memcpy(exif_chunk, exif.data(), exif.size()); + memcpy(exif_chunk, exif.data(), size); *((unsigned short*)exif_chunk) = _byteswap_ushort(0xFFE1); - *((unsigned short*)exif_chunk + 1) = _byteswap_ushort(exif.size() - 2); + *((unsigned short*)exif_chunk + 1) = _byteswap_ushort(size - 2); } } } diff --git a/src/JPEGView/Helpers.cpp b/src/JPEGView/Helpers.cpp index 5023bd7d..b0d9e79d 100644 --- a/src/JPEGView/Helpers.cpp +++ b/src/JPEGView/Helpers.cpp @@ -554,7 +554,27 @@ CRect GetWindowRectMatchingImageSize(HWND hWnd, CSize minSize, CSize maxSize, do int nOrigWidth = (pImage == NULL) ? ::GetSystemMetrics(SM_CXSCREEN) / 2 : pImage->OrigWidth(); int nOrigWidthUnzoomed = nOrigWidth; int nOrigHeight = (pImage == NULL) ? ::GetSystemMetrics(SM_CYSCREEN) / 2 : pImage->OrigHeight(); - if (dZoom > 0) { + if (dZoom == ZoomMax) { + // NOTE: somewhat hacky, but the original code did not account for ZoomMax == DBL_MAX, causing overflows + // This workaround artificially calculates an image that is == the maximum allowed dimensions, + // so as to not overflow int or double + // + // when dZoom was unbounded to DBL_MAX in 1.1.44, doing any arithmetic would cause the nOrig* to overflow + // So just set to scaled maximum allowed image dimension on one side + // the math below will not overflow, and it stays within the bounds of an integer + if (nOrigWidth == nOrigHeight) { + // ratio is 1:1 + nOrigWidth = nOrigHeight = MAX_IMAGE_DIMENSION; + } else if (nOrigWidth > nOrigHeight) { + // wider than high + nOrigHeight = (int)((double)nOrigHeight / nOrigWidth * MAX_IMAGE_DIMENSION); // max * height/width ratio + nOrigWidth = MAX_IMAGE_DIMENSION; + } else { + // higher than wide + nOrigWidth = (int)((double)nOrigWidth / nOrigHeight * MAX_IMAGE_DIMENSION); // max * width/height ratio + nOrigHeight = MAX_IMAGE_DIMENSION; + } + } else if (dZoom > 0) { nOrigWidth = (int)(nOrigWidth * dZoom + 0.5); nOrigHeight = (int)(nOrigHeight * dZoom + 0.5); } @@ -733,7 +753,7 @@ EImageFormat GetImageFormat(LPCTSTR sFileName) { LPCTSTR sEnding = _tcsrchr(sFileName, _T('.')); if (sEnding != NULL) { sEnding += 1; - if (_tcsicmp(sEnding, _T("JPG")) == 0 || _tcsicmp(sEnding, _T("JPEG")) == 0) { + if (_tcsicmp(sEnding, _T("JPG")) == 0 || _tcsicmp(sEnding, _T("JPEG")) == 0 || _tcsicmp(sEnding, _T("JFIF")) == 0) { return IF_JPEG; } else if (_tcsicmp(sEnding, _T("BMP")) == 0) { return IF_WindowsBMP; @@ -745,9 +765,7 @@ EImageFormat GetImageFormat(LPCTSTR sFileName) { return IF_WEBP; } else if (_tcsicmp(sEnding, _T("JXL")) == 0) { return IF_JXL; - } else if (_tcsicmp(sEnding, _T("HEIF")) == 0) { - return IF_HEIF; - } else if (_tcsicmp(sEnding, _T("HEIC")) == 0) { + } else if (_tcsicmp(sEnding, _T("HEIF")) == 0 || _tcsicmp(sEnding, _T("HEIC")) == 0) { return IF_HEIF; } else if (_tcsicmp(sEnding, _T("TGA")) == 0) { return IF_TGA; @@ -755,6 +773,8 @@ EImageFormat GetImageFormat(LPCTSTR sFileName) { return IF_AVIF; } else if (_tcsicmp(sEnding, _T("QOI")) == 0) { return IF_QOI; + } else if ((_tcsicmp(sEnding, _T("PSD")) == 0) || (_tcsicmp(sEnding, _T("PSB")) == 0)) { + return IF_PSD; } else if (IsInFileEndingList(CSettingsProvider::This().FilesProcessedByWIC(), sEnding)) { return IF_WIC; } else if (IsInFileEndingList(CSettingsProvider::This().FileEndingsRAW(), sEnding)) { diff --git a/src/JPEGView/Helpers.h b/src/JPEGView/Helpers.h index e26bc46a..f7cef1dd 100644 --- a/src/JPEGView/Helpers.h +++ b/src/JPEGView/Helpers.h @@ -90,8 +90,12 @@ namespace Helpers { }; // Maximum and minimum allowed zoom factors for images - const double ZoomMax = 16.0; - const double ZoomMin = 0.1; + const double ZoomMax = DBL_MAX; // unbound the maximum zoom, previously set at 16.0 (1600%) + const double ZoomMin = DBL_MIN; // unbound the minimum zoom, previously set at 0.1 (10%) + + // this is specified all over the code as the maximum image dimension that one side can be + // this particular variable will have limited usage, but is used to reflect that limitation. DO NOT CHANGE + const int MAX_IMAGE_DIMENSION = 65535; // Round to integer inline int RoundToInt(double d) { @@ -104,6 +108,34 @@ namespace Helpers { // Converts the system time to a string CString SystemTimeToString(const SYSTEMTIME &time); + // pixel is ARGB, backgroundColor is BGR. Returns ARGB + static inline uint32 AlphaBlendBackground(uint32 pixel, COLORREF backgroundColor) + { + uint32 alpha = pixel & 0xFF000000; + if (alpha == 0xFF000000) + return pixel; + + uint8 bg_r = GetRValue(backgroundColor); + uint8 bg_g = GetGValue(backgroundColor); + uint8 bg_b = GetBValue(backgroundColor); + + if (alpha == 0) { + return (bg_r << 16) + (bg_g << 8) + (bg_b); + } else { + uint8 r = (pixel >> 16) & 0xFF; + uint8 g = (pixel >> 8) & 0xFF; + uint8 b = (pixel ) & 0xFF; + uint8 a = alpha >> 24; + uint8 one_minus_a = 255 - a; + + return + 0xFF000000 + + ( (uint8)(((r * a + bg_r * one_minus_a) / 255.0) + 0.5) << 16) + + ( (uint8)(((g * a + bg_g * one_minus_a) / 255.0) + 0.5) << 8) + + ( (uint8)(((b * a + bg_b * one_minus_a) / 255.0) + 0.5) ); + } +} + // Gets the image size to be used when fitting the image to screen, either using 'fit to screen' // or 'fill with crop' method. If 'fill with crop' is used, the bLimitAR can be set to avoid // filling when to less pixels remain visible @@ -250,7 +282,7 @@ namespace Helpers { // Conversion class that replaces the | by null character in a string. // Caution: Uses a static buffer and therefore only one string can be replaced concurrently - const int MAX_SIZE_REPLACE_PIPE = 512; + const int MAX_SIZE_REPLACE_PIPE = 1024; class CReplacePipe { public: diff --git a/src/JPEGView/HelpersGUI.cpp b/src/JPEGView/HelpersGUI.cpp index 38f1224c..3696d5a9 100644 --- a/src/JPEGView/HelpersGUI.cpp +++ b/src/JPEGView/HelpersGUI.cpp @@ -112,8 +112,7 @@ namespace HelpersGUI { if (::GetTextFace(dc, LF_FACESIZE, buff) != 0) { TEXTMETRIC textMetrics; ::GetTextMetrics(dc, &textMetrics); - LOGFONT logFont; - memset(&logFont, 0, sizeof(LOGFONT)); + LOGFONT logFont{ 0 }; logFont.lfHeight = textMetrics.tmHeight; logFont.lfWeight = FW_BOLD; _tcsncpy_s(logFont.lfFaceName, LF_FACESIZE, buff, LF_FACESIZE); @@ -190,8 +189,7 @@ namespace HelpersGUI { sNewMenuText += _T('\t'); sNewMenuText += sKeyDesc; } - MENUITEMINFO menuInfo; - memset(&menuInfo , 0, sizeof(MENUITEMINFO)); + MENUITEMINFO menuInfo{ 0 }; menuInfo.cbSize = sizeof(MENUITEMINFO); menuInfo.fMask = MIIM_STRING; menuInfo.dwTypeData = (LPTSTR)(LPCTSTR)sNewMenuText; @@ -498,4 +496,16 @@ namespace HelpersGUI { return FindCommand(index, CSettingsProvider::This().OpenWithCommandList()); } + bool SetMenuTextById(HMENU hMenu, int nMenuId, CString sText) { + // While ModifyMenu works... it destroys and recreates menu, so it wouldn't preserve any existing properties like disabled or checked... + // https://stackoverflow.com/questions/6834541/problems-with-cmenumodifymenu + // ::ModifyMenu(hMenuCropMode, IDM_CROPMODE_5_4, MF_CHECKED | MF_STRING, 0, _T("Test AR")) ... you would have to re-set all the previous properties + + MENUITEMINFO itemInfo{ 0 }; + itemInfo.cbSize = sizeof(MENUITEMINFO); + itemInfo.fMask = MIIM_STRING; + itemInfo.dwTypeData = (LPTSTR)(LPCTSTR)sText; + itemInfo.cch = sText.GetLength(); + return ::SetMenuItemInfo(hMenu, nMenuId, FALSE, &itemInfo); + } } \ No newline at end of file diff --git a/src/JPEGView/HelpersGUI.h b/src/JPEGView/HelpersGUI.h index 9b2689a5..0f920c91 100644 --- a/src/JPEGView/HelpersGUI.h +++ b/src/JPEGView/HelpersGUI.h @@ -82,4 +82,7 @@ namespace HelpersGUI { // Finds a open with command given the menu index in the menu as created above CUserCommand* FindOpenWithCommand(int index); + // Changes a menu's text, if success returns true, else false + bool SetMenuTextById(HMENU hMenu, int nMenuId, CString sText); + } \ No newline at end of file diff --git a/src/JPEGView/HistogramCorr.cpp b/src/JPEGView/HistogramCorr.cpp index 09216216..3a9206a4 100644 --- a/src/JPEGView/HistogramCorr.cpp +++ b/src/JPEGView/HistogramCorr.cpp @@ -21,13 +21,14 @@ static __int64 CalculateSum(const int* pHistogram) { // CHistogram class /////////////////////////////////////////////////////////////////////////////////// -CHistogram::CHistogram(const CJPEGImage & image, bool bUseOrigPixels) { +CHistogram::CHistogram(const CJPEGImage & image, bool bUseOrigPixels) + : m_ChannelR{ 0 }, + m_ChannelG{ 0 }, + m_ChannelB{ 0 }, + m_ChannelGrey{ 0 } +{ const int NUM_VALUES = 50000; - memset(m_ChannelB, 0, sizeof(int)*256); - memset(m_ChannelG, 0, sizeof(int)*256); - memset(m_ChannelR, 0, sizeof(int)*256); - memset(m_ChannelGrey, 0, sizeof(int)*256); m_nBMean = m_nGMean = m_nRMean = 0; m_bUseOrigPixels = bUseOrigPixels; m_fNightshot = -1.0f; @@ -77,11 +78,12 @@ CHistogram::CHistogram(const CJPEGImage & image, bool bUseOrigPixels) { m_nRMean /= m_nTotalValues; } -CHistogram::CHistogram(const void* pPixels, const CSize& size) { - memset(m_ChannelB, 0, sizeof(int)*256); - memset(m_ChannelG, 0, sizeof(int)*256); - memset(m_ChannelR, 0, sizeof(int)*256); - memset(m_ChannelGrey, 0, sizeof(int)*256); +CHistogram::CHistogram(const void* pPixels, const CSize& size) + : m_ChannelR{ 0 }, + m_ChannelG{ 0 }, + m_ChannelB{ 0 }, + m_ChannelGrey{ 0 } +{ m_nBMean = m_nGMean = m_nRMean = 0; m_fNightshot = -1.0f; @@ -248,13 +250,13 @@ uint8* CHistogramCorr::CalculateCorrectionLUT(const CHistogram & histogram, floa // Reduce the maximal applied correction - if the black or white point is far from 0, // respectively 1, we only apply a decreasing fraction of the full correction. - float fHistogramWidthFactor = pow((fWhitePtGrey - fBlackPtGrey), fStrength*0.5f); - fBlackPtB = fBlackPtB*pow((1 - fBlackPtB)*(1 - fBlackPtGrey), fStrength)*fHistogramWidthFactor; - fBlackPtG = fBlackPtG*pow((1 - fBlackPtG)*(1 - fBlackPtGrey), fStrength)*fHistogramWidthFactor; - fBlackPtR = fBlackPtR*pow((1 - fBlackPtR)*(1 - fBlackPtGrey), fStrength)*fHistogramWidthFactor; - fWhitePtB = 1 - (1 - fWhitePtB)*pow(fWhitePtB*fWhitePtGrey, fStrength)*fHistogramWidthFactor; - fWhitePtG = 1 - (1 - fWhitePtG)*pow(fWhitePtG*fWhitePtGrey, fStrength)*fHistogramWidthFactor; - fWhitePtR = 1 - (1 - fWhitePtR)*pow(fWhitePtR*fWhitePtGrey, fStrength)*fHistogramWidthFactor; + float fHistogramWidthFactor = powf((fWhitePtGrey - fBlackPtGrey), fStrength*0.5f); + fBlackPtB = fBlackPtB*powf((1 - fBlackPtB)*(1 - fBlackPtGrey), fStrength)*fHistogramWidthFactor; + fBlackPtG = fBlackPtG*powf((1 - fBlackPtG)*(1 - fBlackPtGrey), fStrength)*fHistogramWidthFactor; + fBlackPtR = fBlackPtR*powf((1 - fBlackPtR)*(1 - fBlackPtGrey), fStrength)*fHistogramWidthFactor; + fWhitePtB = 1 - (1 - fWhitePtB)*powf(fWhitePtB*fWhitePtGrey, fStrength)*fHistogramWidthFactor; + fWhitePtG = 1 - (1 - fWhitePtG)*powf(fWhitePtG*fWhitePtGrey, fStrength)*fHistogramWidthFactor; + fWhitePtR = 1 - (1 - fWhitePtR)*powf(fWhitePtR*fWhitePtGrey, fStrength)*fHistogramWidthFactor; // If the brightness decreased by the correction, undo this float fMeanBlackPt = (fBlackPtB + fBlackPtG + fBlackPtR)/3; diff --git a/src/JPEGView/ICCProfileTransform.cpp b/src/JPEGView/ICCProfileTransform.cpp index d3c827d1..fd147f91 100644 --- a/src/JPEGView/ICCProfileTransform.cpp +++ b/src/JPEGView/ICCProfileTransform.cpp @@ -9,6 +9,7 @@ // This define is necessary for 32-bit builds to work, for some reason #define CMS_DLL #include "lcms2.h" +#define TYPE_LabA_8 (COLORSPACE_SH(PT_Lab)|EXTRA_SH(1)|CHANNELS_SH(3)|BYTES_SH(1)) void* ICCProfileTransform::sRGBProfile = NULL; @@ -30,8 +31,29 @@ void* ICCProfileTransform::CreateTransform(const void* profile, unsigned int siz // Create transform from embedded profile to sRGB cmsUInt32Number flags = cmsFLAGS_BLACKPOINTCOMPENSATION | cmsFLAGS_COPY_ALPHA; - cmsUInt32Number inFormat = (format == FORMAT_BGRA) ? TYPE_BGRA_8 : TYPE_RGBA_8; - cmsHTRANSFORM transform = cmsCreateTransform(hInProfile, inFormat, sRGBProfile, TYPE_BGRA_8, INTENT_RELATIVE_COLORIMETRIC, flags); + cmsUInt32Number inFormat, outFormat; + switch (format) { + case FORMAT_BGRA: + inFormat = TYPE_BGRA_8; + outFormat = TYPE_BGRA_8; + break; + case FORMAT_RGBA: + inFormat = TYPE_RGBA_8; + outFormat = TYPE_BGRA_8; + break; + case FORMAT_BGR: + inFormat = TYPE_BGR_8; + outFormat = TYPE_BGR_8; + break; + case FORMAT_RGB: + inFormat = TYPE_RGB_8; + outFormat = TYPE_BGR_8; + break; + default: + cmsCloseProfile(hInProfile); + return NULL; + } + cmsHTRANSFORM transform = cmsCreateTransform(hInProfile, inFormat, sRGBProfile, outFormat, INTENT_RELATIVE_COLORIMETRIC, flags); cmsCloseProfile(hInProfile); return transform; } @@ -41,10 +63,25 @@ bool ICCProfileTransform::DoTransform(void* transform, const void* inputBuffer, unsigned int numPixels = width * height; if (transform == NULL || inputBuffer == NULL || outputBuffer == NULL || numPixels == 0 || !CSettingsProvider::This().UseEmbeddedColorProfiles()) return false; + cmsUInt32Number inFormat = cmsGetTransformInputFormat(transform); + int nchannels; + switch (inFormat) { + case TYPE_BGRA_8: + case TYPE_RGBA_8: + case TYPE_LabA_8: + nchannels = 4; + break; + case TYPE_BGR_8: + case TYPE_RGB_8: + case TYPE_Lab_8: + nchannels = 3; + break; + default: + return false; + } if (stride == 0) - cmsDoTransform(transform, inputBuffer, outputBuffer, numPixels); - else - cmsDoTransformLineStride(transform, inputBuffer, outputBuffer, width, height, stride, width * 4, stride * height, numPixels * 4); + stride = width * nchannels; + cmsDoTransformLineStride(transform, inputBuffer, outputBuffer, width, height, stride, Helpers::DoPadding(width * nchannels, 4), stride * height, Helpers::DoPadding(width * nchannels, 4) * height); return true; } @@ -54,6 +91,39 @@ void ICCProfileTransform::DeleteTransform(void* transform) cmsDeleteTransform(transform); } +void* ICCProfileTransform::CreateLabTransform(PixelFormat format) { + cmsHTRANSFORM transform = NULL; + cmsHPROFILE hLabProfile = NULL; + try { + hLabProfile = cmsCreateLab4Profile(cmsD50_xyY()); + if (sRGBProfile == NULL) { + sRGBProfile = cmsCreate_sRGBProfile(); + } + } catch (...) {} + + if (hLabProfile == NULL || sRGBProfile == NULL) + return NULL; // Could not create profile + + // Create transform from CIELAB D50 (Photoshop "Lab mode") to sRGB + cmsUInt32Number flags = cmsFLAGS_BLACKPOINTCOMPENSATION | cmsFLAGS_COPY_ALPHA; + cmsUInt32Number inFormat, outFormat; + switch (format) { + case FORMAT_Lab: + inFormat = TYPE_Lab_8; + outFormat = TYPE_BGR_8; + break; + case FORMAT_LabA: + inFormat = TYPE_LabA_8; + outFormat = TYPE_BGRA_8; + break; + default: + cmsCloseProfile(hLabProfile); + return NULL; + } + transform = cmsCreateTransform(hLabProfile, inFormat, sRGBProfile, outFormat, INTENT_RELATIVE_COLORIMETRIC, flags); + cmsCloseProfile(hLabProfile); + return transform; +} #else @@ -69,4 +139,8 @@ bool ICCProfileTransform::DoTransform(void* /* transform */, const void* /* inpu void ICCProfileTransform::DeleteTransform(void* /* transform */) { } -#endif \ No newline at end of file +void* ICCProfileTransform::CreateLabTransform(PixelFormat /* format */) { + return NULL; +} + +#endif diff --git a/src/JPEGView/ICCProfileTransform.h b/src/JPEGView/ICCProfileTransform.h index ff59ca6b..d2fe56a3 100644 --- a/src/JPEGView/ICCProfileTransform.h +++ b/src/JPEGView/ICCProfileTransform.h @@ -6,6 +6,10 @@ class ICCProfileTransform enum PixelFormat { FORMAT_BGRA, FORMAT_RGBA, + FORMAT_BGR, + FORMAT_RGB, + FORMAT_Lab, + FORMAT_LabA, }; // Create a transform from given ICC Profile to standard sRGB color space. @@ -28,6 +32,8 @@ class ICCProfileTransform // Free memory associated with the given transform static void DeleteTransform(void* transform); + static void* CreateLabTransform(PixelFormat format); + private: static void* sRGBProfile; }; diff --git a/src/JPEGView/ImageLoadThread.cpp b/src/JPEGView/ImageLoadThread.cpp index 86aa25ae..0979d3f0 100644 --- a/src/JPEGView/ImageLoadThread.cpp +++ b/src/JPEGView/ImageLoadThread.cpp @@ -16,6 +16,7 @@ #include "JXLWrapper.h" #include "HEIFWrapper.h" #include "QOIWrapper.h" +#include "PSDWrapper.h" #include "MaxImageDef.h" using namespace Gdiplus; @@ -168,13 +169,15 @@ static EImageFormat GetImageFormat(LPCTSTR sFileName) { { return IF_HEIF; } + } else if (header[0] == '8' && header[1] == 'B' && header[2] == 'P' && header[3] == 'S') { + return IF_PSD; } + // default fallback if no matches based on magic bytes return Helpers::GetImageFormat(sFileName); } static EImageFormat GetBitmapFormat(Gdiplus::Bitmap* pBitmap) { - GUID guid; - memset(&guid, 0, sizeof(GUID)); + GUID guid{ 0 }; pBitmap->GetRawFormat(&guid); if (guid == Gdiplus::ImageFormatBMP) { return IF_WindowsBMP; @@ -469,6 +472,14 @@ void CImageLoadThread::ProcessRequest(CRequestBase& request) { DeleteCachedPngDecoder(); ProcessReadAVIFRequest(&rq); break; + case IF_PSD: + DeleteCachedGDIBitmap(); + DeleteCachedWebpDecoder(); + DeleteCachedPngDecoder(); + DeleteCachedJxlDecoder(); + DeleteCachedAvifDecoder(); + ProcessReadPSDRequest(&rq); + break; default: // try with GDI+ DeleteCachedWebpDecoder(); @@ -1153,6 +1164,13 @@ void CImageLoadThread::ProcessReadHEIFRequest(CRequest* request) { delete[] pBuffer; } +void CImageLoadThread::ProcessReadPSDRequest(CRequest* request) { + request->Image = PsdReader::ReadImage(request->FileName, request->OutOfMemory); + if (request->Image == NULL && !request->OutOfMemory) { + request->Image = PsdReader::ReadThumb(request->FileName, request->OutOfMemory); + } +} + void CImageLoadThread::ProcessReadQOIRequest(CRequest* request) { HANDLE hFile; hFile = ::CreateFile(request->FileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); diff --git a/src/JPEGView/ImageLoadThread.h b/src/JPEGView/ImageLoadThread.h index b6ef2132..26346170 100644 --- a/src/JPEGView/ImageLoadThread.h +++ b/src/JPEGView/ImageLoadThread.h @@ -119,6 +119,7 @@ class CImageLoadThread : public CWorkThread void ProcessReadJXLRequest(CRequest* request); void ProcessReadHEIFRequest(CRequest * request); void ProcessReadQOIRequest(CRequest * request); + void ProcessReadPSDRequest(CRequest * request); void ProcessReadRAWRequest(CRequest * request); void ProcessReadGDIPlusRequest(CRequest * request); void ProcessReadWICRequest(CRequest* request); diff --git a/src/JPEGView/ImageProcessingTypes.h b/src/JPEGView/ImageProcessingTypes.h index 7a6f0e6b..cb0921b3 100644 --- a/src/JPEGView/ImageProcessingTypes.h +++ b/src/JPEGView/ImageProcessingTypes.h @@ -40,6 +40,7 @@ enum EImageFormat { IF_HEIF, IF_AVIF, IF_QOI, + IF_PSD, IF_WIC, IF_CLIPBOARD, IF_CameraRAW, diff --git a/src/JPEGView/JPEGImage.cpp b/src/JPEGView/JPEGImage.cpp index e2b0489e..d10abe8c 100644 --- a/src/JPEGView/JPEGImage.cpp +++ b/src/JPEGView/JPEGImage.cpp @@ -64,7 +64,10 @@ static CBasicProcessing::SIMDArchitecture ToSIMDArchitecture(Helpers::CPUType cp CJPEGImage::CJPEGImage(int nWidth, int nHeight, void* pPixels, void* pEXIFData, int nChannels, __int64 nJPEGHash, EImageFormat eImageFormat, bool bIsAnimation, int nFrameIndex, int nNumberOfFrames, int nFrameTimeMs, - CLocalDensityCorr* pLDC, bool bIsThumbnailImage, CRawMetadata* pRawMetadata) : m_rotationParams(0) { + CLocalDensityCorr* pLDC, bool bIsThumbnailImage, CRawMetadata* pRawMetadata) + : m_rotationParams{ 0 }, + m_fColorCorrectionFactorsNull{ 0 } +{ if (nChannels == 3 || nChannels == 4) { m_pOrigPixels = pPixels; m_nOriginalChannels = nChannels; @@ -153,7 +156,6 @@ CJPEGImage::CJPEGImage(int nWidth, int nHeight, void* pPixels, void* pEXIFData, // Initialize to INI value, may be overriden later by parameter DB memcpy(m_fColorCorrectionFactors, CSettingsProvider::This().ColorCorrectionAmounts(), sizeof(m_fColorCorrectionFactors)); - memset(m_fColorCorrectionFactorsNull, 0, sizeof(m_fColorCorrectionFactorsNull)); } CJPEGImage::~CJPEGImage(void) { diff --git a/src/JPEGView/JPEGLosslessTransform.cpp b/src/JPEGView/JPEGLosslessTransform.cpp index 44a1f123..52b26a00 100644 --- a/src/JPEGView/JPEGLosslessTransform.cpp +++ b/src/JPEGView/JPEGLosslessTransform.cpp @@ -8,11 +8,10 @@ bool _WriteFile(LPCTSTR sFileName, unsigned char* pBuffer, unsigned int nLengthB int _TransformationEnumToOpCode(CJPEGLosslessTransform::ETransformation transformation); // Performs a lossless JPEG transformation, transforming the input file and writing the result to the output file. -// Input and output file can be identical, then the input file is overriden by the resulting output file. +// Input and output file can be identical, then the input file is overwritten by the resulting output file. CJPEGLosslessTransform::EResult CJPEGLosslessTransform::PerformTransformation(LPCTSTR sInputFile, LPCTSTR sOutputFile, CJPEGLosslessTransform::ETransformation transformation, bool bAllowTrim) { - tjtransform transform; - memset(&transform, 0, sizeof(tjtransform)); + tjtransform transform{ 0 }; transform.op = _TransformationEnumToOpCode(transformation); transform.options = bAllowTrim ? TJXOPT_TRIM : TJXOPT_PERFECT; @@ -20,10 +19,9 @@ CJPEGLosslessTransform::EResult CJPEGLosslessTransform::PerformTransformation(LP } // Performs a lossless JPEG crop, using the input file and writing the result to the output file. -// Input and output file can be identical, then the input file is overriden by the resulting output file. +// Input and output file can be identical, then the input file is overwritten by the resulting output file. CJPEGLosslessTransform::EResult CJPEGLosslessTransform::PerformCrop(LPCTSTR sInputFile, LPCTSTR sOutputFile, const CRect& cropRect) { - tjtransform transform; - memset(&transform, 0, sizeof(tjtransform)); + tjtransform transform{ 0 }; transform.op = TJXOP_NONE; transform.options = TJXOPT_PERFECT | TJXOPT_CROP; transform.r.x = cropRect.left; diff --git a/src/JPEGView/JPEGLosslessTransform.h b/src/JPEGView/JPEGLosslessTransform.h index b4cebe0f..603bd761 100644 --- a/src/JPEGView/JPEGLosslessTransform.h +++ b/src/JPEGView/JPEGLosslessTransform.h @@ -24,10 +24,10 @@ class CJPEGLosslessTransform }; // Performs a lossless JPEG transformation, transforming the input file and writing the result to the output file. - // Input and output file can be identical, then the input file is overriden by the resulting output file. + // Input and output file can be identical, then the input file is overwritten by the resulting output file. static EResult PerformTransformation(LPCTSTR sInputFile, LPCTSTR sOutputFile, ETransformation transformation, bool bAllowTrim); // Performs a lossless JPEG crop, using the input file and writing the result to the output file. - // Input and output file can be identical, then the input file is overriden by the resulting output file. + // Input and output file can be identical, then the input file is overwritten by the resulting output file. static EResult PerformCrop(LPCTSTR sInputFile, LPCTSTR sOutputFile, const CRect& cropRect); }; \ No newline at end of file diff --git a/src/JPEGView/JPEGView.cpp b/src/JPEGView/JPEGView.cpp index 8f371f6c..0c4aecd9 100644 --- a/src/JPEGView/JPEGView.cpp +++ b/src/JPEGView/JPEGView.cpp @@ -220,8 +220,7 @@ int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lp ::EnumWindows((WNDENUMPROC)EnumWindowsProc, 0); if (_HWNDOtherInstance != NULL) { // Other instance found, send the filename to be loaded to this instance - COPYDATASTRUCT copyData; - memset(©Data, 0, sizeof(COPYDATASTRUCT)); + COPYDATASTRUCT copyData{ 0 }; copyData.dwData = KEY_MAGIC; copyData.cbData = (sStartupFile.GetLength() + 1) * sizeof(TCHAR); copyData.lpData = (LPVOID)(LPCTSTR)sStartupFile; diff --git a/src/JPEGView/JPEGView.rc b/src/JPEGView/JPEGView.rc index 4e6a5bf7..ea2ae8e9 100644 --- a/src/JPEGView/JPEGView.rc +++ b/src/JPEGView/JPEGView.rc @@ -515,12 +515,12 @@ BEGIN BEGIN VALUE "CompanyName", "Kevin M (sylikc)" VALUE "FileDescription", "JPEGView" - VALUE "FileVersion", "1.2.45.0" + VALUE "FileVersion", "1.2.50.0" VALUE "InternalName", "JPEGView" VALUE "LegalCopyright", "Copyright 2020-2023 Kevin M (sylikc), 2006-2018 David Kleiner" VALUE "OriginalFilename", "JPEGView.exe" VALUE "ProductName", "JPEGView Application (Mod)" - VALUE "ProductVersion", "1.2.45.0" + VALUE "ProductVersion", "1.2.50.0" END END BLOCK "VarFileInfo" diff --git a/src/JPEGView/JPEGView.vcxproj b/src/JPEGView/JPEGView.vcxproj index 30ea8154..e1e27714 100644 --- a/src/JPEGView/JPEGView.vcxproj +++ b/src/JPEGView/JPEGView.vcxproj @@ -273,6 +273,7 @@ xcopy "$(ProjectDir)libheif\bin\*.dll" "$(ProjectDir)$(OutDir)" /Y /D true false CompileAsCpp + stdcpp14 NDEBUG;%(PreprocessorDefinitions) @@ -337,6 +338,7 @@ xcopy "$(ProjectDir)lcms2\bin64\*.dll" "$(ProjectDir)$(OutDir)" /Y /D + @@ -428,6 +430,7 @@ xcopy "$(ProjectDir)lcms2\bin64\*.dll" "$(ProjectDir)$(OutDir)" /Y /D + diff --git a/src/JPEGView/JPEGView.vcxproj.filters b/src/JPEGView/JPEGView.vcxproj.filters index 90732482..7d6d1e3f 100644 --- a/src/JPEGView/JPEGView.vcxproj.filters +++ b/src/JPEGView/JPEGView.vcxproj.filters @@ -270,6 +270,9 @@ Source Files + + Source Files\Image Types + Source Files @@ -533,6 +536,9 @@ Header Files + + Header Files\Image Types + diff --git a/src/JPEGView/JXLWrapper.cpp b/src/JPEGView/JXLWrapper.cpp index b48c00dc..1e4a2ccf 100644 --- a/src/JPEGView/JXLWrapper.cpp +++ b/src/JPEGView/JXLWrapper.cpp @@ -207,10 +207,11 @@ void* JxlReader::ReadImage(int& width, if (cache.transform == NULL) cache.transform = ICCProfileTransform::CreateTransform(icc_profile.data(), icc_profile.size(), ICCProfileTransform::FORMAT_RGBA); if (!ICCProfileTransform::DoTransform(cache.transform, pixels.data(), pPixelData, width, height)) { - memcpy(pPixelData, pixels.data(), size); // RGBA -> BGRA conversion (with little-endian integers) - for (uint32_t* i = (uint32_t*)pPixelData; (uint8_t*)i < pPixelData + size; i++) - *i = ((*i & 0x00FF0000) >> 16) | ((*i & 0x0000FF00)) | ((*i & 0x000000FF) << 16) | ((*i & 0xFF000000)); + uint32_t* data = (uint32_t*)pixels.data(); + for (int i = 0; i * sizeof(uint32_t) < size; ++i) { + ((uint32_t*)pPixelData)[i] = _rotr(_byteswap_ulong(data[i]), 8); + } } // Copy Exif data into the format understood by CEXIFReader diff --git a/src/JPEGView/LocalDensityCorr.cpp b/src/JPEGView/LocalDensityCorr.cpp index 28d3d4e3..1cd5b343 100644 --- a/src/JPEGView/LocalDensityCorr.cpp +++ b/src/JPEGView/LocalDensityCorr.cpp @@ -75,12 +75,8 @@ CLocalDensityCorr::CLocalDensityCorr(const CJPEGImage & image, bool bFullConstru m_fIsSunset = m_fMiddleGrey = m_fSunsetPixels = -1.0f; // LDC also needs the histogram of the image - int channelR[256], channelG[256], channelB[256]; - int channelGrey[256]; - memset(channelR, 0, sizeof(int)*256); - memset(channelG, 0, sizeof(int)*256); - memset(channelB, 0, sizeof(int)*256); - memset(channelGrey, 0, sizeof(int)*256); + int channelR[256]{ 0 }, channelG[256]{ 0 }, channelB[256]{ 0 }; + int channelGrey[256]{ 0 }; int nWidth = image.OrigWidth(); int nHeight = image.OrigHeight(); diff --git a/src/JPEGView/MainDlg.cpp b/src/JPEGView/MainDlg.cpp index ef2418ce..c9d48b98 100644 --- a/src/JPEGView/MainDlg.cpp +++ b/src/JPEGView/MainDlg.cpp @@ -179,7 +179,9 @@ CMainDlg::CMainDlg(bool bForceFullScreen): m_hToastFont(0), m_strToast(""), m_nImageRetryCnt(0), - m_bMouseTracking(false) + m_bMouseTracking(false), + m_nLastAnimationOffset(0), + m_nExpectedNextAnimationTickCount(0) { CSettingsProvider& sp = CSettingsProvider::This(); @@ -449,7 +451,7 @@ LRESULT CMainDlg::OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam // 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_pCurrentImage = m_pJPEGProvider->RequestImage(0, CJPEGProvider::FORWARD, m_pFileList->Current(), 0, CreateProcessParams(!m_bFullScreenMode), m_bOutOfMemoryLastImage, m_bExceptionErrorLastImage); if (m_pCurrentImage != NULL && m_pCurrentImage->IsAnimation()) { StartAnimation(); @@ -477,6 +479,11 @@ LRESULT CMainDlg::OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOCOPYBITS | SWP_FRAMECHANGED); } + if (CSettingsProvider::This().WindowAlwaysOnTopOnStartup()) { + // if set by default for startup ... it is false by default, toggle = true + ToggleAlwaysOnTop(); + } + m_bLockPaint = false; m_isBeforeFileSelected = m_sStartupFile.IsEmpty(); @@ -936,8 +943,7 @@ void CMainDlg::BlendBlackRect(CDC & targetDC, CPanel& panel, float fBlendFactor) memDCPanel.SelectBitmap(bitmapPanel); memDCPanel.FillSolidRect(0, 0, nW, nH, RGB(0, 0, 1)); // nVidia workaround: blending pure black has a bug - BLENDFUNCTION blendFunc; - memset(&blendFunc, 0, sizeof(blendFunc)); + BLENDFUNCTION blendFunc{ 0 }; blendFunc.BlendOp = AC_SRC_OVER; blendFunc.SourceConstantAlpha = (unsigned char)(fBlendFactor*255 + 0.5f); blendFunc.AlphaFormat = 0; @@ -2379,14 +2385,7 @@ void CMainDlg::ExecuteCommand(int nCommand) { break; case IDM_ALWAYS_ON_TOP: - m_bAlwaysOnTop = !m_bAlwaysOnTop; - - // SetWindowPos - https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos - this->SetWindowPos( - m_bAlwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST, - 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE // causes SetWindowPos to ignore the parameters for top/left/width/height - ); + ToggleAlwaysOnTop(); break; case IDM_FIT_WINDOW_TO_IMAGE: @@ -2748,7 +2747,7 @@ void CMainDlg::OpenFile(LPCTSTR sFileName, bool bAfterStartup) { InitParametersForNewImage(); m_pJPEGProvider->NotifyNotUsed(m_pCurrentImage); m_pJPEGProvider->ClearAllRequests(); - m_pCurrentImage = m_pJPEGProvider->RequestImage(m_pFileList, CJPEGProvider::FORWARD, + m_pCurrentImage = m_pJPEGProvider->RequestImage(0, CJPEGProvider::FORWARD, m_pFileList->Current(), 0, CreateProcessParams(!m_bFullScreenMode && (bAfterStartup || IsAdjustWindowToImage())), m_bOutOfMemoryLastImage, m_bExceptionErrorLastImage); m_nLastLoadError = GetLoadErrorAfterOpenFile(); @@ -4150,8 +4149,7 @@ void CMainDlg::AnimateTransition() { int nSteps = max(1, (m_nTransitionTime + 20) / nFrameTimeMs); - BLENDFUNCTION blendFunc; - memset(&blendFunc, 0, sizeof(blendFunc)); + BLENDFUNCTION blendFunc{ 0 }; blendFunc.BlendOp = AC_SRC_OVER; blendFunc.AlphaFormat = 0; float fAlphaStep = 255.0f / nSteps; @@ -4289,15 +4287,22 @@ void CMainDlg::StartAnimation() { m_bLDC = false; m_bLandscapeMode = false; m_bIsAnimationPlaying = true; - ::SetTimer(this->m_hWnd, ANIMATION_TIMER_EVENT_ID, max(10, m_pCurrentImage->FrameTimeMs()), NULL); + int nNewFrameTime = max(10, m_pCurrentImage->FrameTimeMs()); + ::SetTimer(this->m_hWnd, ANIMATION_TIMER_EVENT_ID, nNewFrameTime, NULL); m_pNavPanelCtl->EndNavPanelAnimation(); m_nLastSlideShowImageTickCount = ::GetTickCount(); + m_nLastAnimationOffset = 0; + m_nExpectedNextAnimationTickCount = ::GetTickCount() + nNewFrameTime; } void CMainDlg::AdjustAnimationFrameTime() { // restart timer with new frame time ::KillTimer(this->m_hWnd, ANIMATION_TIMER_EVENT_ID); - ::SetTimer(this->m_hWnd, ANIMATION_TIMER_EVENT_ID, max(10, m_pCurrentImage->FrameTimeMs()), NULL); + m_nLastAnimationOffset += ::GetTickCount() - m_nExpectedNextAnimationTickCount; + m_nLastAnimationOffset = min(m_nLastAnimationOffset, max(100, m_pCurrentImage->FrameTimeMs())); // prevent offset from getting too big + int nNewFrameTime = max(10, m_pCurrentImage->FrameTimeMs() - max(0, m_nLastAnimationOffset)); + m_nExpectedNextAnimationTickCount = ::GetTickCount() + max(10, m_pCurrentImage->FrameTimeMs()); + ::SetTimer(this->m_hWnd, ANIMATION_TIMER_EVENT_ID, nNewFrameTime, NULL); } void CMainDlg::StopAnimation() { @@ -4319,3 +4324,16 @@ void CMainDlg::StopAnimation() { ::KillTimer(this->m_hWnd, ANIMATION_TIMER_EVENT_ID); m_bIsAnimationPlaying = false; } + +void CMainDlg::ToggleAlwaysOnTop() { + // toggle the member variable and call the SetWindowPos to set + m_bAlwaysOnTop = !m_bAlwaysOnTop; + + // SetWindowPos - https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos + this->SetWindowPos( + m_bAlwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST, + 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE // causes SetWindowPos to ignore the parameters for top/left/width/height + ); + +} diff --git a/src/JPEGView/MainDlg.h b/src/JPEGView/MainDlg.h index 93bc9f52..9f32092f 100644 --- a/src/JPEGView/MainDlg.h +++ b/src/JPEGView/MainDlg.h @@ -307,6 +307,8 @@ class CMainDlg : public CDialogImpl bool m_bMouseOn; bool m_bKeepParametersBeforeAnimation; bool m_bIsAnimationPlaying; + int m_nLastAnimationOffset; + int m_nExpectedNextAnimationTickCount; int m_nMonitor; WINDOWPLACEMENT m_storedWindowPlacement; CRect m_monitorRect; @@ -399,6 +401,7 @@ class CMainDlg : public CDialogImpl void StartAnimation(); void AdjustAnimationFrameTime(); void StopAnimation(); + void ToggleAlwaysOnTop(); CSize ComputeAdjustments(CPoint& offsetsAdjusted, CPoint& offsetsInWin, CPoint& offsetsInImage, CSize& clippedSize); void CropToSelection(bool bLossless); bool AdjustCropSelection(CRect& cropRect); diff --git a/src/JPEGView/MaxImageDef.h b/src/JPEGView/MaxImageDef.h index cc59a3e5..c26996af 100644 --- a/src/JPEGView/MaxImageDef.h +++ b/src/JPEGView/MaxImageDef.h @@ -32,12 +32,19 @@ const unsigned int MAX_HEIF_FILE_SIZE = 1024 * 1024 * 150; const unsigned int MAX_HEIF_FILE_SIZE = 1024 * 1024 * 50; #endif +#ifdef _WIN64 +const unsigned int MAX_PSD_FILE_SIZE = 1024 * 1024 * 500; +#else +const unsigned int MAX_PSD_FILE_SIZE = 1024 * 1024 * 100; +#endif + #ifdef _WIN64 const unsigned int MAX_BMP_FILE_SIZE = 1024 * 1024 * 500; #else const unsigned int MAX_BMP_FILE_SIZE = 1024 * 1024 * 100; #endif +// this may be an artificial limitation and might make configurable, or ignore custom setting only for win32 #ifdef _WIN64 const unsigned int MAX_IMAGE_PIXELS = 1024 * 1024 * 500; #else @@ -45,4 +52,7 @@ const unsigned int MAX_IMAGE_PIXELS = 1024 * 1024 * 100; #endif // That must not be bigger than 65535 due to internal limitations +// +// unbounding (>65535) this causes crashes if HighQualityResampling=true +// but if it's false, some images load (so far png tested was corrupted) const unsigned int MAX_IMAGE_DIMENSION = 65535; \ No newline at end of file diff --git a/src/JPEGView/NLS.cpp b/src/JPEGView/NLS.cpp index 6c1d8723..714a1088 100644 --- a/src/JPEGView/NLS.cpp +++ b/src/JPEGView/NLS.cpp @@ -20,7 +20,7 @@ CString CNLS::GetLocalizedFileName(LPCTSTR sPath, LPCTSTR sPrefixName, LPCTSTR s } } if (sNLSFile.IsEmpty()) { - sNLSFile = CString(sPath) + sPrefixName + _T("_") + sLanguageCode + __T(".") + sExtension; + sNLSFile = CString(sPath) + sPrefixName + _T("_") + sLanguageCode + _T(".") + sExtension; } return sNLSFile; } diff --git a/src/JPEGView/NavigationPanelCtl.cpp b/src/JPEGView/NavigationPanelCtl.cpp index 9bea2fb8..23f7c262 100644 --- a/src/JPEGView/NavigationPanelCtl.cpp +++ b/src/JPEGView/NavigationPanelCtl.cpp @@ -248,8 +248,7 @@ void CNavigationPanelCtl::DoNavPanelAnimation() { backBrush.CreateSolidBrush(CSettingsProvider::This().ColorBackground()); m_pMemDCAnimation->FillRect(CRect(0, 0, rectNavPanel.Width(), rectNavPanel.Height()), backBrush); - BITMAPINFO bmInfo; - memset(&bmInfo, 0, sizeof(BITMAPINFO)); + BITMAPINFO bmInfo{ 0 }; bmInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmInfo.bmiHeader.biWidth = pCurrentImage->DIBWidth(); bmInfo.bmiHeader.biHeight = -pCurrentImage->DIBHeight(); diff --git a/src/JPEGView/PSDWrapper.cpp b/src/JPEGView/PSDWrapper.cpp new file mode 100644 index 00000000..c5ac0907 --- /dev/null +++ b/src/JPEGView/PSDWrapper.cpp @@ -0,0 +1,508 @@ +/* This file uses code adapted from SAIL (https://github.com/HappySeaFox/sail/blob/master/src/sail-codecs/psd/psd.c) + See the original copyright notice below: + + Copyright (c) 2022 Dmitry Baryshev + + The MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +/* Documentation of the PSD file format can be found here: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/ + Tags can also be found here: https://exiftool.org/TagNames/Photoshop.html + + Useful image resources: + 0x0409 1033 (Photoshop 4.0) Thumbnail resource for Photoshop 4.0 only.See See Thumbnail resource format. + 0x040C 1036 (Photoshop 5.0) Thumbnail resource(supersedes resource 1033).See See Thumbnail resource format. + 0x040F 1039 (Photoshop 5.0) ICC Profile.The raw bytes of an ICC(International Color Consortium) format profile.See ICC1v42_2006 - 05.pdf in the Documentation folder and icProfileHeader.h in Sample Code\Common\Includes . + 0x0411 1041 (Photoshop 5.0) ICC Untagged Profile. 1 byte that disables any assumed profile handling when opening the file. 1 = intentionally untagged. + 0x0417 1047 (Photoshop 6.0) Transparency Index. 2 bytes for the index of transparent color, if any. + 0x0419 1049 (Photoshop 6.0) Global Altitude. 4 byte entry for altitude + 0x041D 1053 (Photoshop 6.0) Alpha Identifiers. 4 bytes of length, followed by 4 bytes each for every alpha identifier. + Get alpha identifier and look at its index number, if not 0 abort + 0x0421 1057 (Photoshop 6.0) Version Info. 4 bytes version, 1 byte hasRealMergedData, Unicode string : writer name, Unicode string : reader name, 4 bytes file version. + 0x0422 1058 (Photoshop 7.0) EXIF data 1. See http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf + 0x0423 1059 (Photoshop 7.0) EXIF data 3. See http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf + Not sure what 0x0423 is. +*/ + +#include "stdafx.h" +#include "PSDWrapper.h" +#include "MaxImageDef.h" +#include "Helpers.h" +#include "TJPEGWrapper.h" +#include "ICCProfileTransform.h" +#include "SettingsProvider.h" + + +// Throw exception if bShouldThrow is true. Setting a breakpoint in here is useful for debugging +static inline void ThrowIf(bool bShouldThrow) { + if (bShouldThrow) { + throw 1; + } +} + +// Read exactly sz bytes of the file into p +static inline void ReadFromFile(void* dst, HANDLE file, DWORD sz) { + unsigned int nNumBytesRead; + ThrowIf(!(::ReadFile(file, dst, sz, (LPDWORD)&nNumBytesRead, NULL) && nNumBytesRead == sz)); +} + +// Read and return an unsigned int from file +static inline unsigned int ReadUIntFromFile(HANDLE file) { + unsigned int val; + ReadFromFile(&val, file, 4); + return _byteswap_ulong(val); +} + +// Read and return an unsigned short from file +static inline unsigned short ReadUShortFromFile(HANDLE file) { + unsigned short val; + ReadFromFile(&val, file, 2); + return _byteswap_ushort(val); +} + +// Read and return an unsigned char from file +static inline unsigned short ReadUCharFromFile(HANDLE file) { + unsigned char val; + ReadFromFile(&val, file, 1); + return val; +} + +// Move file pointer by offset from current position +static inline void SeekFile(HANDLE file, LONG offset) { + ThrowIf(::SetFilePointer(file, offset, NULL, 1) == INVALID_SET_FILE_POINTER); +} + +// Move file pointer to offset from beginning of file +static inline void SeekFileFromStart(HANDLE file, LONG offset) { + ThrowIf(::SetFilePointer(file, offset, NULL, 0) == INVALID_SET_FILE_POINTER); +} + +CJPEGImage* PsdReader::ReadImage(LPCTSTR strFileName, bool& bOutOfMemory) +{ + HANDLE hFile; + hFile = ::CreateFile(strFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + return NULL; + } + char* pBuffer = NULL; + void* pPixelData = NULL; + void* pEXIFData = NULL; + char* pICCProfile = NULL; + unsigned int nICCProfileSize = 0; + void* transform = NULL; + CJPEGImage* Image = NULL; + try { + unsigned int nFileSize = 0; + nFileSize = ::GetFileSize(hFile, NULL); + ThrowIf(nFileSize > MAX_PSD_FILE_SIZE); + + // Skip file signature + SeekFile(hFile, 4); + + // Read version: 1 for PSD, 2 for PSB + unsigned short nVersion = ReadUShortFromFile(hFile); + ThrowIf(nVersion != 1); + + // Check reserved bytes + char pReserved[6]; + ReadFromFile(pReserved, hFile, 6); + ThrowIf(memcmp(pReserved, "\0\0\0\0\0\0", 6)); + + // Read number of channels + unsigned short nRealChannels = ReadUShortFromFile(hFile); + + // Read width and height + unsigned int nHeight = ReadUIntFromFile(hFile); + unsigned int nWidth = ReadUIntFromFile(hFile); + if ((double)nHeight * nWidth > MAX_IMAGE_PIXELS) { + bOutOfMemory = true; + } + ThrowIf(bOutOfMemory || nHeight > MAX_IMAGE_DIMENSION || nWidth > MAX_IMAGE_DIMENSION); + + // PSD can have bit depths of 1, 2, 4, 8, 16, 32 + unsigned short nBitDepth = ReadUShortFromFile(hFile); + // Only 8-bit is supported for now + ThrowIf(nBitDepth != 8); + + + // Read color mode + // Bitmap = 0; Grayscale = 1; Indexed = 2; RGB = 3; CMYK = 4; Multichannel = 7; Duotone = 8; Lab = 9. + // TODO: NegateCMYK + unsigned short nChannels = 0; + unsigned short nColorMode = ReadUShortFromFile(hFile); + switch (nColorMode) { + case MODE_Grayscale: + case MODE_Duotone: + nChannels = min(nRealChannels, 1); + break; + case MODE_Multichannel: + nChannels = min(nRealChannels, 3); + break; + case MODE_Lab: + case MODE_RGB: + case MODE_CMYK: + nChannels = min(nRealChannels, 4); + break; + } + if (nChannels == 2) { + nChannels = 1; + } + ThrowIf(nChannels != 1 && nChannels != 3 && nChannels != 4); + + // Skip color mode data + unsigned int nColorDataSize = ReadUIntFromFile(hFile); + SeekFile(hFile, nColorDataSize); + + // Read resource section size + unsigned int nResourceSectionSize = ReadUIntFromFile(hFile); + + // This default value should detect alpha channels for PSDs created by programs which don't save alpha identifiers (e.g. Krita, GIMP) + bool bUseAlpha = nChannels == 4; + + for (;;) { + // Resource block signature + try { + if (ReadUIntFromFile(hFile) != 0x3842494D) { // "8BIM" + break; + } + } catch (...) { + break; + } + + // Resource ID + unsigned short nResourceID = ReadUShortFromFile(hFile); + + // Skip Pascal string (padded to be even length) + while (ReadUShortFromFile(hFile)); + + // Resource size + unsigned int nResourceSize = ReadUIntFromFile(hFile); + + // Parse image resources + switch (nResourceID) { + case 0x040F: // ICC Profile + if (nColorMode == MODE_RGB) { + pICCProfile = new(std::nothrow) char[nResourceSize]; + } + if (pICCProfile != NULL) { + ReadFromFile(pICCProfile, hFile, nResourceSize); + SeekFile(hFile, -nResourceSize); + nICCProfileSize = nResourceSize; + } + break; + case 0x041D: // 0x041D 1053 (Photoshop 6.0) Alpha Identifiers. 4 bytes of length, followed by 4 bytes each for every alpha identifier. + if (bUseAlpha) { + bUseAlpha = false; + int i = 0; + while (i < nResourceSize / 4) { + i++; + if (ReadUIntFromFile(hFile) == 0) { + bUseAlpha = true; + break; + } + } + SeekFile(hFile, -i * 4); + } + break; + case 0x0421: // 0x0421 1057 (Photoshop 6.0) Version Info. 4 bytes version, 1 byte hasRealMergedData, Unicode string : writer name, Unicode string : reader name, 4 bytes file version. + if (nResourceSize >= 5) { + ReadUIntFromFile(hFile); + // See https://exiftool.org/forum/index.php?topic=12897.0 + ThrowIf(!ReadUCharFromFile(hFile)); + SeekFile(hFile, -5); + } + break; + case 0x0422: // 0x0422 1058 (Photoshop 7.0) EXIF data 1. See http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf + case 0x0423: // 0x0423 1059 (Photoshop 7.0) EXIF data 3. See http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf + if (pEXIFData == NULL && nResourceSize < 65526) { + pEXIFData = new(std::nothrow) char[nResourceSize + 10]; + if (pEXIFData != NULL) { + memcpy(pEXIFData, "\xFF\xE1\0\0Exif\0\0", 10); + *((unsigned short*)pEXIFData + 1) = _byteswap_ushort(nResourceSize + 8); + ReadFromFile((char*)pEXIFData + 10, hFile, nResourceSize); + SeekFile(hFile, -nResourceSize); + } + } + break; + } + + // Skip resource data (padded to be even length) + SeekFile(hFile, (nResourceSize + 1) & -2); + } + + // Go back to start of file + SeekFileFromStart(hFile, 26 + 4 + nColorDataSize + 4 + nResourceSectionSize); + + // Skip Layer and Mask Info section + unsigned int nLayerSize = ReadUIntFromFile(hFile); + ReadUIntFromFile(hFile); + short nLayerCount = ReadUShortFromFile(hFile); + bUseAlpha = bUseAlpha && (nLayerCount <= 0); + SeekFile(hFile, nLayerSize - 6); + + // Compression. 0 = Raw Data, 1 = RLE compressed, 2 = ZIP without prediction, 3 = ZIP with prediction. + unsigned short nCompressionMethod = ReadUShortFromFile(hFile); + ThrowIf(nCompressionMethod != COMPRESSION_RLE && nCompressionMethod != COMPRESSION_None); + + unsigned int nImageDataSize = nFileSize - (26 + 4 + nColorDataSize + 4 + nResourceSectionSize + 4 + nLayerSize + 2); + pBuffer = new(std::nothrow) char[nImageDataSize]; + if (pBuffer == NULL) { + bOutOfMemory = true; + ThrowIf(true); + } + ReadFromFile(pBuffer, hFile, nImageDataSize); + + if (!bUseAlpha && nColorMode != MODE_CMYK) { + nChannels = min(nChannels, 3); + } + + // Apply ICC Profile + if (nChannels == 3 || nChannels == 4) { + if (nColorMode == MODE_Lab) { + transform = ICCProfileTransform::CreateLabTransform(nChannels == 4 ? ICCProfileTransform::FORMAT_LabA : ICCProfileTransform::FORMAT_Lab); + if (transform == NULL) { + // If we can't convert Lab to sRGB then just use the Lightness channel as grayscale + nChannels = min(nChannels, 1); + } + } else if (nColorMode == MODE_RGB) { + transform = ICCProfileTransform::CreateTransform(pICCProfile, nICCProfileSize, nChannels == 4 ? ICCProfileTransform::FORMAT_BGRA : ICCProfileTransform::FORMAT_BGR); + } + } + + int nRowSize = Helpers::DoPadding(nWidth * nChannels, 4); + pPixelData = new(std::nothrow) char[nRowSize * nHeight]; + if (pPixelData == NULL) { + bOutOfMemory = true; + ThrowIf(true); + } + // TODO: non-8bit, better non-RGB support + // non-8bit must first be decompressed as arbitrary data + // TODO: continue next row at end of row bytes (to support corrupt images) + unsigned char* p = (unsigned char*)pBuffer; + if (nCompressionMethod == COMPRESSION_RLE) { + // Skip byte counts for scanlines + p += nHeight * nRealChannels * 2; + for (unsigned channel = 0; channel < nChannels; channel++) { + unsigned rchannel; + if (nColorMode == MODE_Lab) { + rchannel = channel; + } else { + rchannel = (-channel - 2) % nChannels; + } + for (unsigned row = 0; row < nHeight; row++) { + for (unsigned count = 0; count < nWidth; ) { + unsigned char c; + ThrowIf(p >= (unsigned char*)pBuffer + nImageDataSize); + c = *p; + p += 1; + + if (c > 128) { + c = ~c + 2; + + ThrowIf(p >= (unsigned char*)pBuffer + nImageDataSize); + unsigned char value = *p; + p += 1; + + for (unsigned i = count; i < count + c; i++) { + unsigned char* scan = (unsigned char*)pPixelData + row * nRowSize + i * nChannels; + unsigned char* pixel = scan + rchannel; + ThrowIf(pixel >= (unsigned char*)pPixelData + nRowSize * nHeight); + *pixel = value; + } + } else if (c < 128) { + c++; + + for (unsigned i = count; i < count + c; i++) { + ThrowIf(p >= (unsigned char*)pBuffer + nImageDataSize); + unsigned char value = *p; + p += 1; + + unsigned char* scan = (unsigned char*)pPixelData + row * nRowSize + i * nChannels; + unsigned char* pixel = scan + rchannel; + ThrowIf(pixel >= (unsigned char*)pPixelData + nRowSize * nHeight); + *pixel = value; + } + } + + count += c; + } + } + } + } else { // No compression + for (unsigned channel = 0; channel < nChannels; channel++) { + unsigned rchannel; + if (nColorMode == MODE_Lab) { + rchannel = channel; + } else { + rchannel = (-channel - 2) % nChannels; + } + for (unsigned row = 0; row < nHeight; row++) { + for (unsigned count = 0; count < nWidth; count++) { + ThrowIf(p >= (unsigned char*)pBuffer + nImageDataSize); + unsigned char value = *p; + p += 1; + + unsigned char* scan = (unsigned char*)pPixelData + row * nRowSize + count * nChannels; + unsigned char* pixel = scan + rchannel; + ThrowIf(pixel >= (unsigned char*)pPixelData + nRowSize * nHeight); + *pixel = value; + } + } + } + } + + ICCProfileTransform::DoTransform(transform, pPixelData, pPixelData, nWidth, nHeight, nRowSize); + + if (nChannels == 4) { + // Multiply alpha value into each AABBGGRR pixel + uint32* pImage32 = (uint32*)pPixelData; + // Blend K channel for CMYK images, alpha channel for RGBA images + COLORREF backgroundColor = nColorMode == MODE_CMYK ? 0 : CSettingsProvider::This().ColorTransparency(); + for (int i = 0; i < nWidth * nHeight; i++) + *pImage32++ = Helpers::AlphaBlendBackground(*pImage32, backgroundColor); + } + + Image = new CJPEGImage(nWidth, nHeight, pPixelData, pEXIFData, nChannels, 0, IF_PSD, false, 0, 1, 0); + } catch (...) { + delete Image; + Image = NULL; + } + ::CloseHandle(hFile); + if (Image == NULL) { + delete[] pPixelData; + } + delete[] pBuffer; + delete[] pEXIFData; + delete[] pICCProfile; + ICCProfileTransform::DeleteTransform(transform); + return Image; +}; + + +CJPEGImage* PsdReader::ReadThumb(LPCTSTR strFileName, bool& bOutOfMemory) +{ + HANDLE hFile; + hFile = ::CreateFile(strFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + return NULL; + } + char* pBuffer = NULL; + void* pPixelData = NULL; + void* pEXIFData = NULL; + CJPEGImage* Image = NULL; + int nWidth, nHeight, nChannels; + int nJpegSize; + TJSAMP eChromoSubSampling; + + try { + // Skip file signature + SeekFile(hFile, 26); + + // Skip color mode data + unsigned int nColorDataSize = ReadUIntFromFile(hFile); + SeekFile(hFile, nColorDataSize); + + // Skip resource section size + ReadUIntFromFile(hFile); + + for (;;) { + // Resource block signature + try { + if (ReadUIntFromFile(hFile) != 0x3842494D) { // "8BIM" + break; + } + } catch (...) { + break; + } + + + unsigned short nResourceID = ReadUShortFromFile(hFile); + + // Skip Pascal string (padded to be even length) + while (ReadUShortFromFile(hFile)); + + // Resource size + unsigned int nResourceSize = ReadUIntFromFile(hFile); + + // Parse image resources + switch (nResourceID) { + case 0x0409: // 0x0409 1033 (Photoshop 4.0) Thumbnail resource for Photoshop 4.0 only. See See Thumbnail resource format. + case 0x040C: // 0x040C 1036 (Photoshop 5.0) Thumbnail resource (supersedes resource 1033). See See Thumbnail resource format. + // Skip thumbnail resource header + SeekFile(hFile, 28); + + // Read embedded JPEG thumbnail + nJpegSize = nResourceSize - 28; + if (nJpegSize > MAX_JPEG_FILE_SIZE) { + bOutOfMemory = true; + ThrowIf(true); + } + + pBuffer = new(std::nothrow) char[nJpegSize]; + if (pBuffer == NULL) { + bOutOfMemory = true; + ThrowIf(true); + } + + ReadFromFile(pBuffer, hFile, nJpegSize); + SeekFile(hFile, -nResourceSize); + + + pPixelData = TurboJpeg::ReadImage(nWidth, nHeight, nChannels, eChromoSubSampling, bOutOfMemory, pBuffer, nJpegSize); + break; + + case 0x0422: // 0x0422 1058 (Photoshop 7.0) EXIF data 1. See http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf + case 0x0423: // 0x0423 1059 (Photoshop 7.0) EXIF data 3. See http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf + if (pEXIFData == NULL && nResourceSize < 65526) { + pEXIFData = new(std::nothrow) char[nResourceSize + 10]; + if (pEXIFData != NULL) { + memcpy(pEXIFData, "\xFF\xE1\0\0Exif\0\0", 10); + *((unsigned short*)pEXIFData + 1) = _byteswap_ushort(nResourceSize + 8); + ReadFromFile((char*)pEXIFData + 10, hFile, nResourceSize); + SeekFile(hFile, -nResourceSize); + } + } + break; + } + + // Skip resource data (padded to be even length) + SeekFile(hFile, (nResourceSize + 1) & -2); + } + + if (pPixelData != NULL) { + Image = new CJPEGImage(nWidth, nHeight, pPixelData, pEXIFData, + nChannels, Helpers::CalculateJPEGFileHash(pBuffer, nJpegSize), IF_JPEG_Embedded, false, 0, 1, 0); + Image->SetJPEGComment(Helpers::GetJPEGComment(pBuffer, nJpegSize)); + Image->SetJPEGChromoSampling(eChromoSubSampling); + } + + } catch(...) { + delete Image; + Image = NULL; + } + ::CloseHandle(hFile); + if (Image == NULL) { + delete[] pPixelData; + } + delete[] pEXIFData; + delete[] pBuffer; + return Image; +} diff --git a/src/JPEGView/PSDWrapper.h b/src/JPEGView/PSDWrapper.h new file mode 100644 index 00000000..3c43e63e --- /dev/null +++ b/src/JPEGView/PSDWrapper.h @@ -0,0 +1,32 @@ +#pragma once + +#include "JPEGImage.h" + +class PsdReader +{ +public: + // Returns image from PSD file + static CJPEGImage* ReadImage(LPCTSTR strFileName, bool& bOutOfMemory); + + // Returns embedded JPEG thumbnail from PSD file + static CJPEGImage* ReadThumb(LPCTSTR strFileName, bool& bOutOfMemory); + +private: + enum ColorMode { + MODE_Bitmap = 0, + MODE_Grayscale = 1, + MODE_Indexed = 2, + MODE_RGB = 3, + MODE_CMYK = 4, + MODE_Multichannel = 7, + MODE_Duotone = 8, + MODE_Lab = 9, + }; + + enum CompressionMode { + COMPRESSION_None = 0, + COMPRESSION_RLE = 1, + COMPRESSION_ZipWithoutPrediction = 2, + COMPRESSION_ZipWithPrediction = 3, + }; +}; diff --git a/src/JPEGView/PaintMemDCMgr.cpp b/src/JPEGView/PaintMemDCMgr.cpp index 5fe8e82a..8dd0cf9b 100644 --- a/src/JPEGView/PaintMemDCMgr.cpp +++ b/src/JPEGView/PaintMemDCMgr.cpp @@ -71,8 +71,7 @@ void CPaintMemDCMgr::BitBltBlended(CDC & dc, CDC & paintDC, const CSize& dcSize, dc.SetDIBitsToDevice(dibStart.x, dibStart.y, dibSize.cx, dibSize.cy, 0, 0, 0, dibSize.cy, pDIBData, pbmInfo, DIB_RGB_COLORS); - BLENDFUNCTION blendFunc; - memset(&blendFunc, 0, sizeof(blendFunc)); + BLENDFUNCTION blendFunc{ 0 }; blendFunc.BlendOp = AC_SRC_OVER; blendFunc.SourceConstantAlpha = (unsigned char)(fDimFactor*255 + 0.5f); blendFunc.AlphaFormat = 0; @@ -97,8 +96,7 @@ void CPaintMemDCMgr::BitBltBlended(CDC & dc, CDC & paintDC, const CSize& dcSize, memDCPanel.BitBlt(0, 0, nW, nH, dc, 0, 0, SRCCOPY); panel.OnPaint(memDCPanel, offsetPanel); - BLENDFUNCTION blendFunc; - memset(&blendFunc, 0, sizeof(blendFunc)); + BLENDFUNCTION blendFunc{ 0 }; blendFunc.BlendOp = AC_SRC_OVER; blendFunc.SourceConstantAlpha = (unsigned char)(fBlendFactor*255 + 0.5f); blendFunc.AlphaFormat = 0; diff --git a/src/JPEGView/ParameterDB.cpp b/src/JPEGView/ParameterDB.cpp index d03d0729..b785d0a9 100644 --- a/src/JPEGView/ParameterDB.cpp +++ b/src/JPEGView/ParameterDB.cpp @@ -478,9 +478,9 @@ void CParameterDB::RestoreParamDB(HWND hWnd) { // Private ///////////////////////////////////////////////////////////////////////////////////////////// -CParameterDB::CParameterDB(void) { - - memset(&m_csDBLock, 0, sizeof(CRITICAL_SECTION)); +CParameterDB::CParameterDB(void) + : m_csDBLock{ 0 } +{ ::InitializeCriticalSection(&m_csDBLock); m_LRUHash = 0; @@ -630,8 +630,7 @@ bool CParameterDB::SaveToFile(int nIndex, const CParameterDBEntry & dbEntry) { } } else if (nFileSize == 0) { // new file created, write the header - ParameterDBHeader header; - memset(&header, 0, sizeof(ParameterDBHeader)); + ParameterDBHeader header{ 0 }; header.nMagic1 = MAGIC_HEADER_1; header.nMagic2 = MAGIC_HEADER_2; header.nVersion = DB_FILE_VERSION; diff --git a/src/JPEGView/PrintDlg.cpp b/src/JPEGView/PrintDlg.cpp index 74643c5a..09e590d9 100644 --- a/src/JPEGView/PrintDlg.cpp +++ b/src/JPEGView/PrintDlg.cpp @@ -250,8 +250,7 @@ LRESULT CPrintDlg::OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, int yDest = clippedRect.top; int xSize = clippedRect.Width(); int ySize = clippedRect.Height(); - BITMAPINFO bmInfo; - memset(&bmInfo, 0, sizeof(BITMAPINFO)); + BITMAPINFO bmInfo{ 0 }; bmInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmInfo.bmiHeader.biWidth = xSize; bmInfo.bmiHeader.biHeight = -ySize; diff --git a/src/JPEGView/PrintImage.cpp b/src/JPEGView/PrintImage.cpp index 10cc808d..0c8c9826 100644 --- a/src/JPEGView/PrintImage.cpp +++ b/src/JPEGView/PrintImage.cpp @@ -54,8 +54,7 @@ bool CPrintImage::Print(HWND hWnd, CJPEGImage * pImage, const CImageProcessingPa bool CPrintImage::DoPrint(HDC hPrinterDC, CPrintParameters* pPrintParameters, CJPEGImage * pImage, const CImageProcessingParams& procParams, EProcessingFlags eFlags, LPCTSTR fileName) { bool bSuccess = false; - DOCINFO docInfo; - memset(&docInfo, 0, sizeof(DOCINFO)); + DOCINFO docInfo{ 0 }; docInfo.cbSize = sizeof(DOCINFO); docInfo.lpszDocName = fileName; @@ -117,8 +116,7 @@ bool CPrintImage::DoPrint(HDC hPrinterDC, CPrintParameters* pPrintParameters, CJ pImage->EnableDimming(true); if (pDIBData != NULL) { - BITMAPINFO bmInfo; - memset(&bmInfo, 0, sizeof(BITMAPINFO)); + BITMAPINFO bmInfo{ 0 }; bmInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmInfo.bmiHeader.biWidth = dibSize.cx; bmInfo.bmiHeader.biHeight = -dibSize.cy; diff --git a/src/JPEGView/ResizeFilter.cpp b/src/JPEGView/ResizeFilter.cpp index 59be82a7..53a5e6ee 100644 --- a/src/JPEGView/ResizeFilter.cpp +++ b/src/JPEGView/ResizeFilter.cpp @@ -183,16 +183,17 @@ static void GetBicubicFilter(uint16 nFrac, int16* pFilterOut) { // Public ////////////////////////////////////////////////////////////////////////////////////// -CResizeFilter::CResizeFilter(int nSourceSize, int nTargetSize, double dSharpen, EFilterType eFilter, FilterSIMDType filterSIMDType) { +CResizeFilter::CResizeFilter(int nSourceSize, int nTargetSize, double dSharpen, EFilterType eFilter, FilterSIMDType filterSIMDType) + : m_kernels{ 0 }, + m_kernelsXMM{ 0 }, + m_kernelsAVX{ 0 }, + m_nRefCnt(0) +{ m_nSourceSize = nSourceSize; m_nTargetSize = nTargetSize; m_dSharpen = min(0.5, max(0.0, dSharpen)); m_eFilter = eFilter; m_filterSIMDType = filterSIMDType; - m_nRefCnt = 0; - memset(&m_kernels, 0, sizeof(m_kernels)); - memset(&m_kernelsXMM, 0, sizeof(m_kernelsXMM)); - memset(&m_kernelsAVX, 0, sizeof(m_kernelsAVX)); if (filterSIMDType == FilterSIMDType_AVX) { CalculateAVXFilterKernels(); @@ -487,8 +488,9 @@ CResizeFilterCache& CResizeFilterCache::This() { return *sm_instance; } -CResizeFilterCache::CResizeFilterCache() { - memset(&m_csList, 0, sizeof(CRITICAL_SECTION)); +CResizeFilterCache::CResizeFilterCache() + : m_csList{ 0 } +{ ::InitializeCriticalSection(&m_csList); } @@ -555,10 +557,11 @@ void CResizeFilterCache::ReleaseFilter(const CResizeFilter& filter) { // CGaussFilter ////////////////////////////////////////////////////////////////////////////////////// -CGaussFilter::CGaussFilter(int nSourceSize, double dRadius) { +CGaussFilter::CGaussFilter(int nSourceSize, double dRadius) + : m_kernels{ 0 } +{ m_nSourceSize = nSourceSize; m_dRadius = dRadius; - memset(&m_kernels, 0, sizeof(m_kernels)); CalculateKernels(); } diff --git a/src/JPEGView/SettingsProvider.cpp b/src/JPEGView/SettingsProvider.cpp index 384c1eeb..3415021e 100644 --- a/src/JPEGView/SettingsProvider.cpp +++ b/src/JPEGView/SettingsProvider.cpp @@ -233,6 +233,7 @@ CSettingsProvider::CSettingsProvider(void) { m_sFileEndingsRAW = GetString(_T("FileEndingsRAW"), _T("*.pef;*.dng;*.crw;*.nef;*.cr2;*.mrw;*.rw2;*.orf;*.x3f;*.arw;*.kdc;*.nrw;*.dcr;*.sr2;*.raf")); m_bCreateParamDBEntryOnSave = GetBool(_T("CreateParamDBEntryOnSave"), true); m_bWrapAroundFolder = GetBool(_T("WrapAroundFolder"), true); + m_bWindowAlwaysOnTopOnStartup = GetBool(_T("WindowAlwaysOnTopOnStartup"), false); m_bSaveWithoutPrompt = GetBool(_T("OverrideOriginalFileWithoutSaveDialog"), false); m_bTrimWithoutPromptLosslessJPEG = GetBool(_T("TrimWithoutPromptLosslessJPEG"), false); m_bAllowFileDeletion = GetBool(_T("AllowFileDeletion"), true); diff --git a/src/JPEGView/SettingsProvider.h b/src/JPEGView/SettingsProvider.h index 68e8707b..a442af17 100644 --- a/src/JPEGView/SettingsProvider.h +++ b/src/JPEGView/SettingsProvider.h @@ -131,6 +131,7 @@ class CSettingsProvider Helpers::EIniEditor IniEditor() { return m_eIniEditor; } LPCTSTR CustomIniEditor() { return m_sIniEditor; } LPCTSTR GPSMapProvider() { return m_sGPSMapProvider; } + bool WindowAlwaysOnTopOnStartup() { return m_bWindowAlwaysOnTopOnStartup; } // Returns if a user INI file exists bool ExistsUserINI(); @@ -297,6 +298,7 @@ class CSettingsProvider Helpers::EIniEditor m_eIniEditor; CString m_sIniEditor; CString m_sGPSMapProvider; + bool m_bWindowAlwaysOnTopOnStartup; std::list m_userCommands; std::list m_openWithCommands; diff --git a/src/JPEGView/UserCommand.cpp b/src/JPEGView/UserCommand.cpp index c9f886e5..ac8cb80f 100644 --- a/src/JPEGView/UserCommand.cpp +++ b/src/JPEGView/UserCommand.cpp @@ -299,11 +299,9 @@ bool CUserCommand::Execute(HWND hWnd, LPCTSTR sFileName, const CRect& selectionR __int64 nRetVal = (__int64)::ShellExecute(hWnd, _T("open"), sEXE, sParameters, sStartupPath, SW_SHOW); bSuccess = nRetVal > 32; } else { - STARTUPINFO startupInfo; - memset(&startupInfo, 0, sizeof(STARTUPINFO)); + STARTUPINFO startupInfo{ 0 }; startupInfo.cb = sizeof(STARTUPINFO); - PROCESS_INFORMATION processInfo; - memset(&processInfo, 0, sizeof(PROCESS_INFORMATION)); + PROCESS_INFORMATION processInfo{ 0 }; bool bIsCmdProcess = (_tcsnicmp(m_sCommand, _T("cmd "), 4) == 0) || (_tcsnicmp(m_sCommand, _T("cmd.exe"), 7) == 0); if (::CreateProcess(NULL, sCommandLine.GetBuffer(512), NULL, NULL, FALSE, (bIsCmdProcess || m_bNoWindow) ? CREATE_NO_WINDOW : 0, NULL, diff --git a/src/JPEGView/WorkThread.cpp b/src/JPEGView/WorkThread.cpp index 25cff153..c086d294 100644 --- a/src/JPEGView/WorkThread.cpp +++ b/src/JPEGView/WorkThread.cpp @@ -7,10 +7,11 @@ // Public ///////////////////////////////////////////////////////////////////////////////////////////// -CWorkThread::CWorkThread(bool bCoInitialize) { +CWorkThread::CWorkThread(bool bCoInitialize) + : m_csList{ 0 } +{ m_bTerminate = false; m_bCoInitialize = bCoInitialize; - memset(&m_csList, 0, sizeof(CRITICAL_SECTION)); ::InitializeCriticalSection(&m_csList); m_wakeUp = ::CreateEvent(0, TRUE, FALSE, NULL); diff --git a/src/JPEGView/ZoomNavigator.cpp b/src/JPEGView/ZoomNavigator.cpp index a4ef94c0..56f27e20 100644 --- a/src/JPEGView/ZoomNavigator.cpp +++ b/src/JPEGView/ZoomNavigator.cpp @@ -82,8 +82,7 @@ void CZoomNavigator::PaintZoomNavigator(CJPEGImage* pImage, const CRectF& visRec int yDest = navigatorRect.top; int xSize = navigatorRect.Width(); int ySize = navigatorRect.Height(); - BITMAPINFO bmInfo; - memset(&bmInfo, 0, sizeof(BITMAPINFO)); + BITMAPINFO bmInfo{ 0 }; bmInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmInfo.bmiHeader.biWidth = xSize; bmInfo.bmiHeader.biHeight = -ySize; diff --git a/src/JPEGView/resource.h b/src/JPEGView/resource.h index 62eec90c..78cd2df0 100644 --- a/src/JPEGView/resource.h +++ b/src/JPEGView/resource.h @@ -3,7 +3,7 @@ // Used by JPEGView.RC // -#define JPEGVIEW_VERSION "1, 2, 45, 0\0" +#define JPEGVIEW_VERSION "1, 2, 50, 0\0" // title for main window and msgbox so it can be change via actions #define JPEGVIEW_TITLE "JPEGView"