diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..b818c75f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,43 @@ +name: Build + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Add MSBuild to PATH + uses: microsoft/setup-msbuild@v2 + + - name: Build x64 + run: msbuild vs2026/voidImageViewer.sln /p:Configuration=Release /p:Platform=x64 /p:PlatformToolset=v143 + + - name: Build x86 + run: msbuild vs2026/voidImageViewer.sln /p:Configuration=Release /p:Platform=Win32 /p:PlatformToolset=v143 + + - name: Setup NSIS + run: | + choco install nsis -y + "C:\Program Files (x86)\NSIS" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + + - name: Build Installer x64 + run: powershell -File nsis/build_installer.ps1 -Arch x64 -VsVersion vs2026 -BuildConfig Release -Lang English + + - name: Build Installer x86 + run: powershell -File nsis/build_installer.ps1 -Arch x86 -VsVersion vs2026 -BuildConfig Release -Lang English + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: voidImageViewer-build + path: | + voidImageViewer/x64/Release/voidImageViewer.exe + voidImageViewer/Release/voidImageViewer.exe + nsis/voidImageViewer-*-setup.exe diff --git a/Changes.txt b/Changes.txt index 62d6cdba..654adff5 100644 --- a/Changes.txt +++ b/Changes.txt @@ -1,3 +1,13 @@ +Saturday, 29 August 2026: Version 1.2.1.0 + added Smooth (Halftone) toggle to main viewer View menu and context menu. + added Compare Images side-by-side comparison mode with synchronized/individual zoom and pan. + added drag and drop support directly onto left/right compare panes. + added "Use Current Image" toggle per pane with mutual exclusion. + added mini toolbars per pane for file browsing, zooming, 1:1, fit, and rotation. + added top control bar with tips, Auto-Fit, Smooth, Filename overlay, and Close. + added WebP fallback decoder and alpha blending over window background for transparent PNG/ICO/GIF images. + added full multilingual localization for English, Indonesian, and Simplified Chinese. + Monday, 12 January 2026: Version 1.0.0.15 fixed an issue with scrolling large images. improved position offset when using numpad keys. diff --git a/README.md b/README.md index 1d27215e..c1f2f2b5 100644 --- a/README.md +++ b/README.md @@ -26,25 +26,83 @@ void Image Viewer main window: void Image Viewer General Options: -![Void Image Viewer Options General](https://www.voidtools.com/voidImageViewer.Options.General10.png) +image


void Image Viewer View Options: -![Void Image Viewer Options View](https://www.voidtools.com/voidImageViewer.Options.View10.png) +image


void Image Viewer Controls Options: -![Void Image Viewer Image Controls](https://www.voidtools.com/voidImageViewer.Options.Controls10.png) +image


+## Changelog + +### Version 1.2.1.0 (Latest) +- **New Feature: Smooth Toggle in Main Viewer** + - Added **Smooth** (Halftone / Bilinear interpolation) toggle directly in the `View` menu and right-click context menu. + - Linked with configuration persistence (`shrink_blit_mode` / `mag_filter`), and seamlessly integrated with Compare Images initial smoothing state. +- **New Feature: Compare Images (Side-by-Side Image Comparison)** + - Added multi-pane **Compare Images** window accessible via `View` -> `Compare Images...` or shortcut (`Ctrl+Alt+C`). + - **Drag & Drop Support:** + - Directly drag & drop image files from File Explorer to either the left or right pane to compare. + - Dropping 2 files simultaneously automatically loads them into the left and right panes. + - **"Use Current Image" Toggle:** + - Convenient checkbox on each pane to instantly load the image currently open in the main viewer. + - Automatically disables on the other pane when active to avoid duplicate/conflicting states. + - **Synchronized Navigation & Precise Zoom:** + - Mouse wheel zooms all compared images simultaneously using the exact same zoom engine (`_viv_calculate_next_zoom`) with synchronized viewport centers to eliminate drift. + - Left-click drag pans all compared images synchronously. + - **Individual Pane Mode:** Hold `Ctrl` key or use **Right Mouse Button** to zoom or pan only the hovered image pane. + - **Per-Pane Mini Toolbar:** + - Previous (`<`) and Next (`>`) buttons to browse folder/playlist images per pane. + - Zoom In (`+`), Zoom Out (`-`), Zoom % display, `1:1` (Actual size), and `Fit` (Best Fit) buttons. + - Rotate clockwise button (`90°`). + - Change/Select image file button (`...`). + - **Top Control Bar:** + - Helpful shortcut and mouse tips. + - **Auto-Fit** toggle to automatically scale images to fit pane viewports. + - **Smooth** toggle (Halftone / High-quality interpolation). + - **Filename** toggle with bright green overlay text (`filename`, `dimensions`, `file size`). + - **Close** button (or `ESC` key) to quickly return to main viewer. + - **Full Format Support & Alpha Transparency:** + - Native support for PNG (with alpha transparency), GIF, animated GIF, BMP, ICO, TIFF, JPEG, and WebP (via `libwebp` fallback decoder). + - Clean alpha blending over the window background color, preventing black background issues on transparent images. + - **Full Multilingual Localization:** Added translations for all compare UI elements in English (`en-US`), Indonesian (`id-ID`), and Simplified Chinese (`zh-CN`). + +### Version 1.1.0.0 +- **New Feature: Zoom Options Tab & Calculation Engine** + - Added dedicated **Zoom options** tab in Options dialog (`IDD_ZOOM`). + - Added customizable zoom calculation methods: + - **Relative step:** Multiplicative scaling using configured zoom step % (default: 10%). + - **Absolute step:** Additive scaling by fixed percentage point steps. + - **Fixed table:** Step-by-step navigation through a user-defined list of zoom percentage levels (default: `1;2;3;10;14;16;19;21;25;28;33;38;43;50;57;66;76;87;100;115;132;150;174;200;230;260;300;350;400;450;500;600;700;800;900;1000;1200;1400;1600;2000;3000;`). + - Added **Load default table** button to quickly restore default values. +- **Improved Zoom Handling:** Pixel under cursor remains stationary during mouse wheel and shortcut zooms across all zoom methods. +- **Expanded Localization:** Added full UI translations for the Zoom options page in English (`en-US`), Indonesian (`id-ID`), and Simplified Chinese (`zh-CN`). +- **Configuration Persistence:** Zoom settings (`zoom_calc_method`, `zoom_step`, `zoom_fixed_table`) automatically saved to and loaded from `voidImageViewer.ini`. + +| Feature / Behavior | Before (v1.0.1.0) | After (v1.1.0.0) | +| :--- | :--- | :--- | +| **Zoom Calculation Method** | Hardcoded fixed exponential power steps (`_viv_zoom_pos` from 0 to 63) | Choice between **Relative step (%)**, **Absolute step (%)**, or customizable **Fixed table** | +| **Zoom Step Config** | Fixed / non-configurable | Configurable zoom step percentage (default `10%`) | +| **Fixed Table Values** | Not available | Semicolon-delimited custom table support (up to 50 ordered values) with default reset | +| **Settings UI** | 3 tabs (*General*, *View*, *Controls*) | 4 tabs (*General*, *View*, *Controls*, ***Zoom options***) | +| **Localization** | Dialog strings for 3 tabs | Full multilingual translations for all 4 tabs (English, Indonesian, Chinese) | +| **INI Storage** | No zoom calculation preferences saved | Saves `zoom_calc_method`, `zoom_step`, `zoom_fixed_table` to INI | + +


+ See also -------- https://www.voidtools.com/forum/viewtopic.php?t=5623 + diff --git a/comments.json b/comments.json new file mode 100644 index 00000000..a65ec5fd Binary files /dev/null and b/comments.json differ diff --git a/issues.json b/issues.json new file mode 100644 index 00000000..811c766b Binary files /dev/null and b/issues.json differ diff --git a/nsis/version.nsh b/nsis/version.nsh index 6e65d04c..75f01347 100644 --- a/nsis/version.nsh +++ b/nsis/version.nsh @@ -1,3 +1,4 @@ -!define VERSION "1.0.0.15" +!define VERSION "1.2.1.0" !define BETAVERSION "" !define VERSIONYEAR "2026" + diff --git a/res/resource.h b/res/resource.h index e3639f70..32e21440 100644 --- a/res/resource.h +++ b/res/resource.h @@ -25,6 +25,7 @@ #define IDD_RENAME 127 #define IDD_JUMPTO 128 #define IDD_EVERYTHING 129 +#define IDD_ZOOM 130 #define IDC_TAB1 1003 #define IDC_BMP 1006 #define IDC_GIF 1007 @@ -90,15 +91,43 @@ #define IDC_TITLE_BAR_FORMAT 1052 #define IDC_SHRINK_BLIT_MODE_STATIC 1053 #define IDC_MAGNIFY_BLIT_MODE_STATIC 1054 +#define IDC_RESET_WINDOW 1055 +#define IDC_LANGUAGE_STATIC 1056 +#define IDC_LANGUAGE_COMBOBOX 1057 +#define IDC_LOOP_ANIMATIONS_ONCE_STATIC 1058 +#define IDC_PRELOAD_NEXT_IMAGE_STATIC 1059 +#define IDC_CACHE_LAST_IMAGE_STATIC 1060 +#define IDC_WINDOWEDBACKGROUNDCOLOR_STATIC 1061 +#define IDC_FULLSCREENBACKGROUNDCOLOR_STATIC 1062 +#define IDC_LEFT_CLICK_ACTION_STATIC 1063 +#define IDC_RIGHT_CLICK_ACTION_STATIC 1064 +#define IDC_MOUSE_WHEEL_ACTION_STATIC 1065 +#define IDC_COMMANDS_STATIC 1066 +#define IDC_SETTINGS_FOR_SELECTED_COMMAND_STATIC 1067 +#define IDC_TITLE_BAR_FORMAT_STATIC 1068 +#define IDC_AUTO_SIZE_WINDOW_STATIC 1069 +#define IDC_EDIT_KEYBOARD_SHORTCUT_KEY_STATIC 1070 +#define IDC_EDIT_KEYBOARD_SHORTCUT_KEY_CURRENTLY_USED_BY_STATIC 1071 +#define IDC_CUSTOM_RATE_STATIC 1072 +#define IDC_TAB4 1073 +#define IDC_ZOOM_STEP_STATIC 1074 +#define IDC_ZOOM_STEP_EDIT 1075 +#define IDC_ZOOM_STEP_PERCENT_STATIC 1076 +#define IDC_ZOOM_STEP_DEFAULT_STATIC 1077 +#define IDC_ZOOM_CALC_METHOD_STATIC 1078 +#define IDC_ZOOM_CALC_RELATIVE_RADIO 1079 +#define IDC_ZOOM_CALC_ABSOLUTE_RADIO 1080 +#define IDC_ZOOM_CALC_FIXED_TABLE_RADIO 1081 +#define IDC_ZOOM_FIXED_TABLE_EDIT 1082 +#define IDC_ZOOM_LOAD_DEFAULT_TABLE_BUTTON 1083 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NO_MFC 1 -#define _APS_NEXT_RESOURCE_VALUE 130 +#define _APS_NEXT_RESOURCE_VALUE 131 #define _APS_NEXT_COMMAND_VALUE 40048 -#define _APS_NEXT_CONTROL_VALUE 1055 -#define _APS_NEXT_SYMED_VALUE 109 +#define _APS_NEXT_CONTROL_VALUE 1084 #endif #endif diff --git a/res/voidImageViewer.rc b/res/voidImageViewer.rc index b138421b..1c6b57ee 100644 --- a/res/voidImageViewer.rc +++ b/res/voidImageViewer.rc @@ -26,86 +26,109 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // Dialog // -IDD_GENERAL DIALOGEX 0, 0, 194, 180 +IDD_GENERAL DIALOGEX 0, 0, 244, 200 STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS EXSTYLE WS_EX_CONTROLPARENT FONT 8, "MS Shell Dlg", 0, 0, 0x0 BEGIN + LTEXT "&Language:",IDC_LANGUAGE_STATIC,0,2,50,12,SS_CENTERIMAGE + COMBOBOX IDC_LANGUAGE_COMBOBOX,54,0,135,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP CONTROL "&Store settings in %APPDATA%\\voidImageViewer",IDC_APPDATA, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,0,186,10 + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,20,186,10 CONTROL "Allow multiple &instances",IDC_MULTIPLE_INSTANCES, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,18,186,10 - CONTROL "Start &menu shortcuts",IDC_STARTMENU,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,36,186,10 - GROUPBOX "Associations",IDC_STATIC,0,54,54,124 - CONTROL "&BMP",IDC_BMP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,66,42,10 - CONTROL "&GIF",IDC_GIF,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,78,42,10 - CONTROL "IC&O",IDC_ICO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,90,42,10 - CONTROL "JP&EG",IDC_JPEG,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,102,42,10 - CONTROL "&JPG",IDC_JPG,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,114,42,10 - CONTROL "&PNG",IDC_PNG,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,126,42,10 - CONTROL "&TIF",IDC_TIF,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,138,42,10 - CONTROL "TIF&F",IDC_TIFF,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,150,42,10 - CONTROL "&WEBP",IDC_WEBP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,162,42,10 - PUSHBUTTON "Check &All",IDC_CHECKALL,60,60,54,14 - PUSHBUTTON "Check &None",IDC_CHECKNONE,60,78,54,14 + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,38,186,10 + CONTROL "Start &menu shortcuts",IDC_STARTMENU,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,56,186,10 + GROUPBOX "Associations",IDC_STATIC,0,74,54,124 + CONTROL "&BMP",IDC_BMP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,86,42,10 + CONTROL "&GIF",IDC_GIF,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,98,42,10 + CONTROL "IC&O",IDC_ICO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,110,42,10 + CONTROL "JP&EG",IDC_JPEG,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,122,42,10 + CONTROL "&JPG",IDC_JPG,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,134,42,10 + CONTROL "&PNG",IDC_PNG,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,146,42,10 + CONTROL "&TIF",IDC_TIF,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,158,42,10 + CONTROL "TIF&F",IDC_TIFF,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,170,42,10 + CONTROL "&WEBP",IDC_WEBP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,182,42,10 + PUSHBUTTON "Check &All",IDC_CHECKALL,60,80,54,14 + PUSHBUTTON "Check &None",IDC_CHECKNONE,60,98,54,14 END -IDD_OPTIONS DIALOGEX 0, 0, 310, 271 +IDD_OPTIONS DIALOGEX 0, 0, 360, 271 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CONTROLPARENT CAPTION "Options - Void Image Viewer" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN - DEFPUSHBUTTON "OK",IDOK,198,252,50,14,WS_GROUP - PUSHBUTTON "Cancel",IDCANCEL,252,252,50,14 + DEFPUSHBUTTON "OK",IDOK,248,252,50,14,WS_GROUP + PUSHBUTTON "Cancel",IDCANCEL,302,252,50,14 CONTROL "",IDC_TREE1,"SysTreeView32",TVS_SHOWSELALWAYS | TVS_TRACKSELECT | WS_BORDER | WS_TABSTOP,6,6,84,240 - CONTROL "",IDC_TAB1,"SysTabControl32",WS_TABSTOP,96,6,210,240 - CONTROL "",IDC_TAB2,"SysTabControl32",WS_TABSTOP,96,6,210,240 - CONTROL "",IDC_TAB3,"SysTabControl32",WS_TABSTOP,96,6,210,240 - LTEXT "Static",IDC_PAGEPLACEHOLDER,106,26,186,214,NOT WS_VISIBLE + CONTROL "",IDC_TAB1,"SysTabControl32",WS_TABSTOP,96,6,260,240 + CONTROL "",IDC_TAB2,"SysTabControl32",WS_TABSTOP,96,6,260,240 + CONTROL "",IDC_TAB3,"SysTabControl32",WS_TABSTOP,96,6,260,240 + CONTROL "",IDC_TAB4,"SysTabControl32",WS_TABSTOP,96,6,260,240 + LTEXT "Static",IDC_PAGEPLACEHOLDER,106,26,236,214,NOT WS_VISIBLE END -IDD_VIEW DIALOGEX 0, 0, 194, 183 +IDD_VIEW DIALOGEX 0, 0, 244, 183 STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS EXSTYLE WS_EX_CONTROLPARENT FONT 8, "MS Shell Dlg", 0, 0, 0x0 BEGIN - LTEXT "&Shrink blit mode:",IDC_SHRINK_BLIT_MODE_STATIC,0,0,74,12,SS_CENTERIMAGE - COMBOBOX IDC_COMBO1,74,0,119,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - LTEXT "&Magnify blit mode:",IDC_MAGNIFY_BLIT_MODE_STATIC,0,19,74,8 - COMBOBOX IDC_COMBO2,74,17,119,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - LTEXT "&Title bar format:",IDC_STATIC,0,36,74,8 - COMBOBOX IDC_TITLE_BAR_FORMAT,74,34,119,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - CONTROL "Auto si&ze window:",IDC_AUTO_ZOOM,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,53,74,10 - COMBOBOX IDC_COMBO4,74,51,60,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - CONTROL "&Play animations at least once in slideshow",IDC_LOOP_ANIMATIONS_ONCE, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,70,192,10 - CONTROL "Preload &next image",IDC_PRELOAD_NEXT_IMAGE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,87,192,10 - CONTROL "Cache &last image",IDC_CACHE_LAST_IMAGE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,103,192,10 - LTEXT "&Windowed background color:",IDC_STATIC,0,122,96,8 - PUSHBUTTON "",IDC_WINDOWEDBACKGROUNDCOLOR,96,120,50,14,BS_BITMAP - LTEXT "&Fullscreen background color:",IDC_STATIC,0,140,96,8 - PUSHBUTTON "",IDC_FULLSCREENBACKGROUNDCOLOR,96,138,50,14,BS_BITMAP + LTEXT "&Shrink blit mode:",IDC_SHRINK_BLIT_MODE_STATIC,0,0,100,12,SS_CENTERIMAGE + COMBOBOX IDC_COMBO1,104,0,135,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "&Magnify blit mode:",IDC_MAGNIFY_BLIT_MODE_STATIC,0,19,100,8 + COMBOBOX IDC_COMBO2,104,17,135,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "&Title bar format:",IDC_TITLE_BAR_FORMAT_STATIC,0,36,100,8 + COMBOBOX IDC_TITLE_BAR_FORMAT,104,34,135,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "Auto si&ze window:",IDC_AUTO_ZOOM,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,53,100,10 + COMBOBOX IDC_COMBO4,104,51,60,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "&Play animations at least once in slideshow",IDC_LOOP_ANIMATIONS_ONCE_STATIC, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,70,236,10 + CONTROL "Preload &next image",IDC_PRELOAD_NEXT_IMAGE_STATIC, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,87,236,10 + CONTROL "Cache &last image",IDC_CACHE_LAST_IMAGE_STATIC, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,103,236,10 + LTEXT "&Windowed background color:",IDC_WINDOWEDBACKGROUNDCOLOR_STATIC,0,122,120,8 + PUSHBUTTON "",IDC_WINDOWEDBACKGROUNDCOLOR,124,120,50,14,BS_BITMAP + LTEXT "&Fullscreen background color:",IDC_FULLSCREENBACKGROUNDCOLOR_STATIC,0,140,120,8 + PUSHBUTTON "",IDC_FULLSCREENBACKGROUNDCOLOR,124,138,50,14,BS_BITMAP + PUSHBUTTON "Reset Window Size && Position",IDC_RESET_WINDOW,0,158,150,14 END -IDD_CONTROLS DIALOGEX 0, 0, 194, 216 +IDD_CONTROLS DIALOGEX 0, 0, 244, 216 STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS EXSTYLE WS_EX_CONTROLPARENT FONT 8, "MS Shell Dlg", 0, 0, 0x0 BEGIN - LTEXT "&Left click action:",IDC_STATIC,0,0,54,12,SS_CENTERIMAGE - COMBOBOX IDC_LEFTCLICKACTION,74,0,119,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - LTEXT "&Right click action:",IDC_STATIC,0,19,63,8 - COMBOBOX IDC_RIGHTCLICKACTION,74,17,119,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - LTEXT "&Mouse wheel action:",IDC_STATIC,0,36,72,8 - COMBOBOX IDC_MOUSEWHEELACTION,74,34,119,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - LTEXT "&Commands:",IDC_STATIC,0,53,57,8 - LISTBOX IDC_COMMANDS_LIST,0,64,193,78,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP - GROUPBOX "Settings for selected command",IDC_STATIC,0,148,193,67 + LTEXT "&Left click action:",IDC_LEFT_CLICK_ACTION_STATIC,0,0,70,12,SS_CENTERIMAGE + COMBOBOX IDC_LEFTCLICKACTION,74,0,165,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "&Right click action:",IDC_RIGHT_CLICK_ACTION_STATIC,0,19,70,8 + COMBOBOX IDC_RIGHTCLICKACTION,74,17,165,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "&Mouse wheel action:",IDC_MOUSE_WHEEL_ACTION_STATIC,0,36,70,8 + COMBOBOX IDC_MOUSEWHEELACTION,74,34,165,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "&Commands:",IDC_COMMANDS_STATIC,0,53,70,8 + LISTBOX IDC_COMMANDS_LIST,0,64,244,78,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + GROUPBOX "Settings for selected command",IDC_SETTINGS_FOR_SELECTED_COMMAND_STATIC,0,148,244,67 LISTBOX IDC_KEYS_LIST,7,158,125,50,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP - PUSHBUTTON "&Add...",IDC_ADD_KEY,138,158,50,14 - PUSHBUTTON "&Edit...",IDC_EDIT_KEY,137,177,50,14 - PUSHBUTTON "Remo&ve",IDC_REMOVE_KEY,137,195,50,14 + PUSHBUTTON "&Add...",IDC_ADD_KEY,188,158,50,14 + PUSHBUTTON "&Edit...",IDC_EDIT_KEY,187,177,50,14 + PUSHBUTTON "Remo&ve",IDC_REMOVE_KEY,187,195,50,14 +END + +IDD_ZOOM DIALOGEX 0, 0, 244, 200 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + LTEXT "Zoom &step:",IDC_ZOOM_STEP_STATIC,0,2,42,12,SS_CENTERIMAGE + EDITTEXT IDC_ZOOM_STEP_EDIT,46,2,24,12,ES_AUTOHSCROLL | ES_NUMBER + LTEXT "%",IDC_ZOOM_STEP_PERCENT_STATIC,73,2,8,12,SS_CENTERIMAGE + LTEXT "(default: 10 %)",IDC_ZOOM_STEP_DEFAULT_STATIC,86,2,100,12,SS_CENTERIMAGE + LTEXT "Zoom calculation method:",IDC_ZOOM_CALC_METHOD_STATIC,0,22,244,10 + CONTROL "&Relative step",IDC_ZOOM_CALC_RELATIVE_RADIO,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,10,36,220,10 + CONTROL "&Absolute step",IDC_ZOOM_CALC_ABSOLUTE_RADIO,"Button",BS_AUTORADIOBUTTON,10,50,220,10 + CONTROL "Use &fixed table: (for experts: max. 50 ordered values from 3 to 4000)",IDC_ZOOM_CALC_FIXED_TABLE_RADIO,"Button",BS_AUTORADIOBUTTON,10,64,234,10 + EDITTEXT IDC_ZOOM_FIXED_TABLE_EDIT,18,78,140,14,ES_AUTOHSCROLL | WS_TABSTOP + PUSHBUTTON "&Load default table",IDC_ZOOM_LOAD_DEFAULT_TABLE_BUTTON,162,78,76,14,WS_TABSTOP END IDD_CUSTOM_RATE DIALOGEX 0, 0, 202, 63 @@ -205,8 +228,8 @@ IDI_BESTFIT ICON "bestfit.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,0,0,15 - PRODUCTVERSION 1,0,0,15 + FILEVERSION 1,2,1,0 + PRODUCTVERSION 1,2,1,0 FILEFLAGSMASK 0x17L #ifdef _DEBUG FILEFLAGS 0x1L @@ -222,12 +245,12 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "FileDescription", "voidImageViewer" - VALUE "FileVersion", "1.0.0.15" + VALUE "FileVersion", "1.2.1.0" VALUE "InternalName", "voidImageViewer" - VALUE "LegalCopyright", "Copyright 2026 voidtools" + VALUE "LegalCopyright", "Copyright © 2026 voidtools" VALUE "OriginalFilename", "voidImageViewer.exe" VALUE "ProductName", "voidImageViewer" - VALUE "ProductVersion", "1.0.0.15" + VALUE "ProductVersion", "1.2.1.0" END END BLOCK "VarFileInfo" @@ -287,3 +310,5 @@ END ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED + + diff --git a/res/voidImageViewer_utf8.rc b/res/voidImageViewer_utf8.rc new file mode 100644 index 00000000..b7657fca --- /dev/null +++ b/res/voidImageViewer_utf8.rc @@ -0,0 +1,312 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_GENERAL DIALOGEX 0, 0, 244, 200 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + LTEXT "&Language:",IDC_LANGUAGE_STATIC,0,2,50,12,SS_CENTERIMAGE + COMBOBOX IDC_LANGUAGE_COMBOBOX,54,0,135,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "&Store settings in %APPDATA%\\voidImageViewer",IDC_APPDATA, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,20,186,10 + CONTROL "Allow multiple &instances",IDC_MULTIPLE_INSTANCES, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,38,186,10 + CONTROL "Start &menu shortcuts",IDC_STARTMENU,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,56,186,10 + GROUPBOX "Associations",IDC_STATIC,0,74,54,124 + CONTROL "&BMP",IDC_BMP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,86,42,10 + CONTROL "&GIF",IDC_GIF,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,98,42,10 + CONTROL "IC&O",IDC_ICO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,110,42,10 + CONTROL "JP&EG",IDC_JPEG,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,122,42,10 + CONTROL "&JPG",IDC_JPG,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,134,42,10 + CONTROL "&PNG",IDC_PNG,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,146,42,10 + CONTROL "&TIF",IDC_TIF,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,158,42,10 + CONTROL "TIF&F",IDC_TIFF,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,170,42,10 + CONTROL "&WEBP",IDC_WEBP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,182,42,10 + PUSHBUTTON "Check &All",IDC_CHECKALL,60,80,54,14 + PUSHBUTTON "Check &None",IDC_CHECKNONE,60,98,54,14 +END + +IDD_OPTIONS DIALOGEX 0, 0, 360, 271 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_CONTROLPARENT +CAPTION "Options - Void Image Viewer" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK",IDOK,248,252,50,14,WS_GROUP + PUSHBUTTON "Cancel",IDCANCEL,302,252,50,14 + CONTROL "",IDC_TREE1,"SysTreeView32",TVS_SHOWSELALWAYS | TVS_TRACKSELECT | WS_BORDER | WS_TABSTOP,6,6,84,240 + CONTROL "",IDC_TAB1,"SysTabControl32",WS_TABSTOP,96,6,260,240 + CONTROL "",IDC_TAB2,"SysTabControl32",WS_TABSTOP,96,6,260,240 + CONTROL "",IDC_TAB3,"SysTabControl32",WS_TABSTOP,96,6,260,240 + CONTROL "",IDC_TAB4,"SysTabControl32",WS_TABSTOP,96,6,260,240 + LTEXT "Static",IDC_PAGEPLACEHOLDER,106,26,236,214,NOT WS_VISIBLE +END + +IDD_VIEW DIALOGEX 0, 0, 244, 183 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + LTEXT "&Shrink blit mode:",IDC_SHRINK_BLIT_MODE_STATIC,0,0,100,12,SS_CENTERIMAGE + COMBOBOX IDC_COMBO1,104,0,135,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "&Magnify blit mode:",IDC_MAGNIFY_BLIT_MODE_STATIC,0,19,100,8 + COMBOBOX IDC_COMBO2,104,17,135,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "&Title bar format:",IDC_TITLE_BAR_FORMAT_STATIC,0,36,100,8 + COMBOBOX IDC_TITLE_BAR_FORMAT,104,34,135,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "Auto si&ze window:",IDC_AUTO_ZOOM,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,53,100,10 + COMBOBOX IDC_COMBO4,104,51,60,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "&Play animations at least once in slideshow",IDC_LOOP_ANIMATIONS_ONCE_STATIC, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,70,236,10 + CONTROL "Preload &next image",IDC_PRELOAD_NEXT_IMAGE_STATIC, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,87,236,10 + CONTROL "Cache &last image",IDC_CACHE_LAST_IMAGE_STATIC, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,103,236,10 + LTEXT "&Windowed background color:",IDC_WINDOWEDBACKGROUNDCOLOR_STATIC,0,122,120,8 + PUSHBUTTON "",IDC_WINDOWEDBACKGROUNDCOLOR,124,120,50,14,BS_BITMAP + LTEXT "&Fullscreen background color:",IDC_FULLSCREENBACKGROUNDCOLOR_STATIC,0,140,120,8 + PUSHBUTTON "",IDC_FULLSCREENBACKGROUNDCOLOR,124,138,50,14,BS_BITMAP + PUSHBUTTON "Reset Window Size && Position",IDC_RESET_WINDOW,0,158,150,14 +END + +IDD_CONTROLS DIALOGEX 0, 0, 244, 216 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + LTEXT "&Left click action:",IDC_LEFT_CLICK_ACTION_STATIC,0,0,70,12,SS_CENTERIMAGE + COMBOBOX IDC_LEFTCLICKACTION,74,0,165,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "&Right click action:",IDC_RIGHT_CLICK_ACTION_STATIC,0,19,70,8 + COMBOBOX IDC_RIGHTCLICKACTION,74,17,165,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "&Mouse wheel action:",IDC_MOUSE_WHEEL_ACTION_STATIC,0,36,70,8 + COMBOBOX IDC_MOUSEWHEELACTION,74,34,165,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "&Commands:",IDC_COMMANDS_STATIC,0,53,70,8 + LISTBOX IDC_COMMANDS_LIST,0,64,244,78,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + GROUPBOX "Settings for selected command",IDC_SETTINGS_FOR_SELECTED_COMMAND_STATIC,0,148,244,67 + LISTBOX IDC_KEYS_LIST,7,158,125,50,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "&Add...",IDC_ADD_KEY,188,158,50,14 + PUSHBUTTON "&Edit...",IDC_EDIT_KEY,187,177,50,14 + PUSHBUTTON "Remo&ve",IDC_REMOVE_KEY,187,195,50,14 +END + +IDD_ZOOM DIALOGEX 0, 0, 244, 200 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + LTEXT "Zoom &step:",IDC_ZOOM_STEP_STATIC,0,2,42,12,SS_CENTERIMAGE + EDITTEXT IDC_ZOOM_STEP_EDIT,46,2,24,12,ES_AUTOHSCROLL | ES_NUMBER + LTEXT "%",IDC_ZOOM_STEP_PERCENT_STATIC,73,2,8,12,SS_CENTERIMAGE + LTEXT "(default: 10 %)",IDC_ZOOM_STEP_DEFAULT_STATIC,86,2,100,12,SS_CENTERIMAGE + LTEXT "Zoom calculation method:",IDC_ZOOM_CALC_METHOD_STATIC,0,22,244,10 + CONTROL "&Relative step",IDC_ZOOM_CALC_RELATIVE_RADIO,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,10,36,220,10 + CONTROL "&Absolute step",IDC_ZOOM_CALC_ABSOLUTE_RADIO,"Button",BS_AUTORADIOBUTTON,10,50,220,10 + CONTROL "Use &fixed table: (for experts: max. 50 ordered values from 3 to 4000)",IDC_ZOOM_CALC_FIXED_TABLE_RADIO,"Button",BS_AUTORADIOBUTTON,10,64,234,10 + EDITTEXT IDC_ZOOM_FIXED_TABLE_EDIT,18,78,140,14,ES_AUTOHSCROLL | WS_TABSTOP + PUSHBUTTON "&Load default table",IDC_ZOOM_LOAD_DEFAULT_TABLE_BUTTON,162,78,76,14,WS_TABSTOP +END + +IDD_CUSTOM_RATE DIALOGEX 0, 0, 202, 63 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU +CAPTION "Set Custom Rate" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + EDITTEXT IDC_CUSTOM_RATE_EDIT,54,12,66,12,ES_AUTOHSCROLL | ES_NUMBER,WS_EX_RIGHT + DEFPUSHBUTTON "OK",IDOK,90,36,48,14 + PUSHBUTTON "Cancel",IDCANCEL,144,36,48,14 + LTEXT "&Custom rate:",IDC_STATIC,6,14,45,8 + COMBOBOX IDC_CUSTOM_RATE_TYPE_COMBO,126,12,66,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP +END + +IDD_ABOUT DIALOGEX 0, 0, 228, 189 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU +CAPTION "About void Image Viewer" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK",IDOK,120,168,50,14 + PUSHBUTTON "Cancel",IDCANCEL,174,168,50,14 + LTEXT "",IDC_ABOUTBACK,0,0,228,54 + EDITTEXT IDC_ABOUTTITLE,30,18,192,30,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + EDITTEXT IDC_ABOUTVOIDIMAGEVIEWER,30,66,192,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + EDITTEXT IDC_ABOUTVERSION,30,84,192,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + EDITTEXT IDC_ABOUTCOPYRIGHT,30,102,192,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + EDITTEXT IDC_ABOUTEMAIL,30,120,192,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + EDITTEXT IDC_ABOUTWEBSITE,30,138,192,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER +END + +IDD_EDIT_KEY DIALOGEX 0, 0, 186, 119 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Edit Keyboard Shortcut" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Shortcut &key:",IDC_STATIC,7,7,44,8 + EDITTEXT IDC_EDIT_KEY_EDIT,7,17,172,14,ES_AUTOHSCROLL | ES_WANTRETURN + LTEXT "Shortcut key currently used by:",IDC_STATIC,7,36,102,8 + LISTBOX IDC_EDIT_KEY_CURRENTLY_USED_BY_LIST,7,47,172,43,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + DEFPUSHBUTTON "OK",IDOK,70,98,50,14 + PUSHBUTTON "Cancel",IDCANCEL,129,98,50,14 +END + +IDD_RENAME DIALOGEX 0, 0, 186, 51 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Rename" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + EDITTEXT IDC_RENAME_EDIT,7,7,172,14,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,74,30,50,14 + PUSHBUTTON "Cancel",IDCANCEL,129,30,50,14 + EDITTEXT IDC_RENAME_OLD_EDIT,7,30,40,14,ES_AUTOHSCROLL | NOT WS_VISIBLE +END + +IDD_JUMPTO DIALOGEX 0, 0, 187, 229 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +EXSTYLE WS_EX_CONTROLPARENT +CAPTION "Jump To" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + EDITTEXT IDC_JUMPTO_EDIT,7,7,173,14,ES_AUTOHSCROLL | WS_CLIPSIBLINGS | WS_CLIPCHILDREN + LISTBOX IDC_JUMPTO_LIST,7,29,173,170,WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_VSCROLL | WS_TABSTOP + DEFPUSHBUTTON "OK",IDOK,74,208,50,14,WS_CLIPSIBLINGS | WS_CLIPCHILDREN + PUSHBUTTON "Cancel",IDCANCEL,130,208,50,14,WS_CLIPSIBLINGS | WS_CLIPCHILDREN +END + +IDD_EVERYTHING DIALOGEX 0, 0, 186, 63 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Search Everything" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + EDITTEXT IDC_EVERYTHING_EDIT,6,6,173,14,ES_AUTOHSCROLL + CONTROL "Randomize",IDC_SEARCH_EVERYTHING_RANDOM,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,24,51,10 + DEFPUSHBUTTON "OK",IDOK,72,42,50,14 + PUSHBUTTON "Cancel",IDCANCEL,127,42,50,14 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON "voidImageViewer.ico" +IDI_PREV ICON "prev.ico" +IDI_PLAY ICON "play.ico" +IDI_NEXT ICON "next.ico" +IDI_PAUSE ICON "pause.ico" +IDI_1TO1 ICON "1to1-8bit.ico" +IDI_BESTFIT ICON "bestfit.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,2,1,0 + PRODUCTVERSION 1,2,1,0 + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x0L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "voidImageViewer" + VALUE "FileVersion", "1.2.1.0" + VALUE "InternalName", "voidImageViewer" + VALUE "LegalCopyright", "Copyright © 2026 voidtools" + VALUE "OriginalFilename", "voidImageViewer.exe" + VALUE "ProductName", "voidImageViewer" + VALUE "ProductVersion", "1.2.1.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// English (Australia) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENA) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (Australia) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/scratch_version.ps1 b/scratch_version.ps1 new file mode 100644 index 00000000..91d9bb86 --- /dev/null +++ b/scratch_version.ps1 @@ -0,0 +1,4 @@ +$c = Get-Content res\voidImageViewer.rc -Raw +$c = $c -replace '1,0,0,15', '1,0,1,0' +$c = $c -replace '1\.0\.0\.15', '1.0.1.0' +Set-Content res\voidImageViewer.rc $c -Encoding Default diff --git a/src/config.c b/src/config.c index 719eb0c1..c7285193 100644 --- a/src/config.c +++ b/src/config.c @@ -29,7 +29,8 @@ static void _config_write_string(HANDLE h,const char *ascii_key,const wchar_t *s static void _config_write_utf8(HANDLE h,const utf8_t *s); static void _config_save_settings_by_location(const wchar_t *path,int is_root); -BYTE config_appdata = 0; // store settings in %APPDATA%\voidimageviewer or in the same location as voidimageviewer.exe +BYTE config_appdata = 1; // store settings in %APPDATA%\voidimageviewer or in the same location as voidimageviewer.exe +BYTE config_language = 255; // 255 = auto BYTE config_keep_centered = 1; // when zooming out, don't recenter the image. (keep cursor under the same pixel) int config_x = 0; int config_y = 0; @@ -40,8 +41,8 @@ int config_slideshow_rate = 5000; BYTE config_allow_shrinking = 1; // prevent resizing an image below 100% BYTE config_shrink_blit_mode = CONFIG_SHRINK_BLIT_MODE_HALFTONE; // shrink filter BYTE config_mag_filter = CONFIG_MAG_FILTER_COLORONCOLOR; // magnify filter -BYTE config_nav_sort = CONFIG_NAV_SORT_DATE_MODIFIED; // current navigation sort. -BYTE config_nav_sort_ascending = 0; // sort navigation ascending or descending. +BYTE config_nav_sort = CONFIG_NAV_SORT_NAME; // current navigation sort. +BYTE config_nav_sort_ascending = 1; // sort navigation ascending or descending. BYTE config_keep_aspect_ratio = 1; // stretch images with the original aspect ratio. BYTE config_fill_window = 0; // stretch the image to fill the window BYTE config_fullscreen_fill_window = 1; // same as fill_window, except this setting is used when we are fullscreen @@ -90,6 +91,9 @@ BYTE config_pixel_info = 0; BYTE config_orientation = 1; BYTE config_title_bar_format = 1; // 0=full path, 1=filename, 2=none int config_add_command_line_timeout = 500; // in milliseconds +BYTE config_zoom_calc_method = CONFIG_ZOOM_CALC_RELATIVE; +int config_zoom_step = CONFIG_ZOOM_DEFAULT_STEP; +wchar_t config_zoom_fixed_table[STRING_SIZE] = CONFIG_ZOOM_DEFAULT_FIXED_TABLE; static void _config_load_settings_by_location(const wchar_t *path,int is_root) { @@ -102,6 +106,7 @@ static void _config_load_settings_by_location(const wchar_t *path,int is_root) if (ini) { config_x = ini_get_int(ini,(const utf8_t *)"x",config_x); + config_language = ini_get_int(ini,(const utf8_t *)"language",config_language); config_y = ini_get_int(ini,(const utf8_t *)"y",config_y); config_wide = ini_get_int(ini,(const utf8_t *)"wide",config_wide); config_high = ini_get_int(ini,(const utf8_t *)"high",config_high); @@ -161,6 +166,15 @@ static void _config_load_settings_by_location(const wchar_t *path,int is_root) config_toolbar_move_window = ini_get_int(ini,(const utf8_t *)"toolbar_move_window",config_toolbar_move_window); config_title_bar_format = ini_get_int(ini,(const utf8_t *)"title_bar_format",config_title_bar_format); config_add_command_line_timeout = ini_get_int(ini,(const utf8_t *)"add_command_line_timeout",config_add_command_line_timeout); + config_zoom_calc_method = ini_get_int(ini,(const utf8_t *)"zoom_calc_method",config_zoom_calc_method); + config_zoom_step = ini_get_int(ini,(const utf8_t *)"zoom_step",config_zoom_step); + { + const utf8_t *fixed_table = ini_get_string(ini,(const utf8_t *)"zoom_fixed_table"); + if (fixed_table && *fixed_table) + { + string_copy_utf8_string(config_zoom_fixed_table,fixed_table); + } + } if (is_root) { @@ -286,6 +300,7 @@ static void _config_save_settings_by_location(const wchar_t *path,int is_root) else { _config_write_int(h,"x",config_x); + _config_write_int(h,"language",config_language); _config_write_int(h,"y",config_y); _config_write_int(h,"wide",config_wide); _config_write_int(h,"high",config_high); @@ -345,6 +360,9 @@ static void _config_save_settings_by_location(const wchar_t *path,int is_root) _config_write_int(h,"toolbar_move_window",config_toolbar_move_window); _config_write_int(h,"title_bar_format",config_title_bar_format); _config_write_int(h,"add_command_line_timeout",config_add_command_line_timeout); + _config_write_int(h,"zoom_calc_method",config_zoom_calc_method); + _config_write_int(h,"zoom_step",config_zoom_step); + _config_write_string(h,"zoom_fixed_table",config_zoom_fixed_table); // save keys { diff --git a/src/config.h b/src/config.h index 6a6afa13..953bb92e 100644 --- a/src/config.h +++ b/src/config.h @@ -55,10 +55,19 @@ typedef struct config_key_s #define CONFIG_MAG_FILTER_COLORONCOLOR 0 #define CONFIG_MAG_FILTER_HALFTONE 1 +#define CONFIG_ZOOM_CALC_RELATIVE 0 +#define CONFIG_ZOOM_CALC_ABSOLUTE 1 +#define CONFIG_ZOOM_CALC_FIXED_TABLE 2 + +#define CONFIG_ZOOM_DEFAULT_STEP 10 +#define CONFIG_ZOOM_DEFAULT_FIXED_TABLE L"1;2;3;10;14;16;19;21;25;28;33;38;43;50;57;66;76;87;100;115;132;150;174;200;230;260;300;350;400;450;500;600;700;800;900;1000;1200;1400;1600;2000;3000;" +#define CONFIG_ZOOM_DEFAULT_FIXED_TABLE_A "1;2;3;10;14;16;19;21;25;28;33;38;43;50;57;66;76;87;100;115;132;150;174;200;230;260;300;350;400;450;500;600;700;800;900;1000;1200;1400;1600;2000;3000;" + void config_load_settings(void); void config_save_settings(int appdata); extern BYTE config_appdata; +extern BYTE config_language; extern BYTE config_keep_centered; extern int config_x; extern int config_y; @@ -115,11 +124,13 @@ extern BYTE config_show_caption; extern BYTE config_show_thickframe; extern BYTE config_toolbar_move_window; extern BYTE config_windowed_hide_cursor; -extern BYTE config_auto_zoom; extern BYTE config_pixel_info; extern BYTE config_orientation; extern BYTE config_title_bar_format; extern int config_add_command_line_timeout; +extern BYTE config_zoom_calc_method; +extern int config_zoom_step; +extern wchar_t config_zoom_fixed_table[STRING_SIZE]; #ifdef __cplusplus } diff --git a/src/localization.c b/src/localization.c index 691e4716..9e58b292 100644 --- a/src/localization.c +++ b/src/localization.c @@ -24,11 +24,13 @@ #include "viv.h" #include "localization_en_us.h" #include "localization_zh_cn.h" +#include "localization_id_id.h" static const utf8_t **_localization_language_array[LOCALIZATION_LANGUAGE_COUNT] = { _localization_string_array_en_us, // LOCALIZATION_LANGUAGE_ENGLISH _localization_string_array_zh_cn, // LOCALIZATION_LANGUAGE_CHINESE_SIMPLIFIED + _localization_string_array_id_id, // LOCALIZATION_LANGUAGE_INDONESIAN }; BYTE localization_language = LOCALIZATION_LANGUAGE_ENGLISH; @@ -54,6 +56,16 @@ const utf8_t *localization_get_en_us_string(localization_id_t localization_id) void localization_init(void) { + // If config_language is not Auto, use it + if (config_language != 255) + { + if (config_language < LOCALIZATION_LANGUAGE_COUNT) + { + localization_language = config_language; + return; + } + } + // Detect system language if (os_GetUserDefaultUILanguage) { @@ -69,5 +81,23 @@ void localization_init(void) { localization_language = LOCALIZATION_LANGUAGE_CHINESE_SIMPLIFIED; } + else if (langid == 0x0421) // Indonesian + { + localization_language = LOCALIZATION_LANGUAGE_INDONESIAN; + } + else + { + localization_language = LOCALIZATION_LANGUAGE_ENGLISH; + } } } + +const utf8_t *localization_get_language_name(BYTE language) +{ + if (language < LOCALIZATION_LANGUAGE_COUNT) + { + return _localization_language_array[language][LOCALIZATION_ID_LANGUAGE]; + } + + return (const utf8_t *)""; +} diff --git a/src/localization.h b/src/localization.h index 99248c5d..72c2c512 100644 --- a/src/localization.h +++ b/src/localization.h @@ -29,17 +29,19 @@ enum { LOCALIZATION_LANGUAGE_ENGLISH = 0, LOCALIZATION_LANGUAGE_CHINESE_SIMPLIFIED, + LOCALIZATION_LANGUAGE_INDONESIAN, LOCALIZATION_LANGUAGE_COUNT, }; -typedef BYTE localization_id_t; +typedef unsigned short localization_id_t; -#define LOCALIZATION_ID_INVALID 255 +#define LOCALIZATION_ID_INVALID 65535 // Get localized string for a command name // Returns UTF-8 encoded string const utf8_t *localization_get_string(localization_id_t localization_id); const utf8_t *localization_get_en_us_string(localization_id_t localization_id); +const utf8_t *localization_get_language_name(BYTE language); // Initialize language system (detects system language) void localization_init(void); @@ -49,6 +51,8 @@ enum { // Localization LOCALIZATION_ID_LANGUAGE = 0, + LOCALIZATION_ID_LANGUAGE_STATIC, + LOCALIZATION_ID_LANGUAGE_AUTO, // Application name LOCALIZATION_ID_APP_NAME, @@ -233,6 +237,7 @@ enum LOCALIZATION_ID_OPTIONS_GENERAL_DIALOG, LOCALIZATION_ID_OPTIONS_VIEW_DIALOG, LOCALIZATION_ID_OPTIONS_CONTROLS_DIALOG, + LOCALIZATION_ID_OPTIONS_ZOOM_DIALOG, LOCALIZATION_ID_OK_BUTTON, LOCALIZATION_ID_CANCEL_BUTTON, LOCALIZATION_ID_STORE_SETTINGS_APPDATA, @@ -255,6 +260,7 @@ enum LOCALIZATION_ID_CACHE_LAST_IMAGE_STATIC, LOCALIZATION_ID_WINDOWED_BACKGROUND_COLOR_STATIC, LOCALIZATION_ID_FULLSCREEN_BACKGROUND_COLOR_STATIC, + LOCALIZATION_ID_OPTIONS_VIEW_RESET_WINDOW_BUTTON, LOCALIZATION_ID_LEFT_CLICK_ACTION_STATIC, LOCALIZATION_ID_RIGHT_CLICK_ACTION_STATIC, LOCALIZATION_ID_MOUSE_WHEEL_ACTION_STATIC, @@ -300,6 +306,13 @@ enum LOCALIZATION_ID_OPTIONS_TITLE_BAR_FORMAT_FULL_PATH_COMBOBOXITEM, LOCALIZATION_ID_OPTIONS_TITLE_BAR_FORMAT_FILENAME_ONLY_COMBOBOXITEM, LOCALIZATION_ID_OPTIONS_TITLE_BAR_FORMAT_NONE_COMBOBOXITEM, + LOCALIZATION_ID_OPTIONS_ZOOM_STEP_STATIC, + LOCALIZATION_ID_OPTIONS_ZOOM_STEP_DEFAULT_STATIC, + LOCALIZATION_ID_OPTIONS_ZOOM_CALC_METHOD_STATIC, + LOCALIZATION_ID_OPTIONS_ZOOM_CALC_RELATIVE_RADIO, + LOCALIZATION_ID_OPTIONS_ZOOM_CALC_ABSOLUTE_RADIO, + LOCALIZATION_ID_OPTIONS_ZOOM_CALC_FIXED_TABLE_RADIO, + LOCALIZATION_ID_OPTIONS_ZOOM_LOAD_DEFAULT_TABLE_BUTTON, LOCALIZATION_ID_EVERYTHING_NOT_AVAILABLE_MESSAGE, LOCALIZATION_ID_EVERYTHING_ADD_EVERYTHING_SEARCH_CAPTION, LOCALIZATION_ID_EVERYTHING_LOAD_EVERYTHING_SEARCH_CAPTION, @@ -318,6 +331,26 @@ enum LOCALIZATION_ID_ASSOCIATION_DESCRIPTION_TIFF, LOCALIZATION_ID_ASSOCIATION_DESCRIPTION_WEBP, + // Compare Images + LOCALIZATION_ID_COMPARE_IMAGES, + LOCALIZATION_ID_COMPARE_TIPS, + LOCALIZATION_ID_COMPARE_AUTO_FIT, + LOCALIZATION_ID_COMPARE_SMOOTH, + LOCALIZATION_ID_COMPARE_FILENAME, + LOCALIZATION_ID_COMPARE_CLOSE, + LOCALIZATION_ID_COMPARE_PREV, + LOCALIZATION_ID_COMPARE_NEXT, + LOCALIZATION_ID_COMPARE_ZOOM_IN, + LOCALIZATION_ID_COMPARE_ZOOM_OUT, + LOCALIZATION_ID_COMPARE_ACTUAL_SIZE, + LOCALIZATION_ID_COMPARE_BEST_FIT, + LOCALIZATION_ID_COMPARE_ROTATE, + LOCALIZATION_ID_COMPARE_SELECT_IMAGE, + LOCALIZATION_ID_COMPARE_USE_CURRENT, + LOCALIZATION_ID_COMPARE_NO_IMAGE_DROP, + + LOCALIZATION_ID_SMOOTH, + LOCALIZATION_ID_COUNT, }; diff --git a/src/localization_en_us.h b/src/localization_en_us.h index 5747262c..fa7d0cbf 100644 --- a/src/localization_en_us.h +++ b/src/localization_en_us.h @@ -26,6 +26,8 @@ static const utf8_t *_localization_string_array_en_us[LOCALIZATION_ID_COUNT] = { // Application name "English (US)", // LOCALIZATION_ID_LANGUAGE + "&Language:", // LOCALIZATION_ID_LANGUAGE_STATIC + "System Default", // LOCALIZATION_ID_LANGUAGE_AUTO // Application name "void Image Viewer", // LOCALIZATION_ID_APP_NAME @@ -210,6 +212,7 @@ static const utf8_t *_localization_string_array_en_us[LOCALIZATION_ID_COUNT] = "General", // LOCALIZATION_ID_OPTIONS_GENERAL_DIALOG, "View", // LOCALIZATION_ID_OPTIONS_VIEW_DIALOG, "Controls", // LOCALIZATION_ID_OPTIONS_CONTROLS_DIALOG, + "Zoom options", // LOCALIZATION_ID_OPTIONS_ZOOM_DIALOG, "OK", // LOCALIZATION_ID_OK_BUTTON, "Cancel", // LOCALIZATION_ID_CANCEL_BUTTON, "&Store settings in %APPDATA%\\voidImageViewer", // LOCALIZATION_ID_STORE_SETTINGS_APPDATA, @@ -232,6 +235,7 @@ static const utf8_t *_localization_string_array_en_us[LOCALIZATION_ID_COUNT] = "Cache &last image", // LOCALIZATION_ID_CACHE_LAST_IMAGE_STATIC, "&Windowed background color:", // LOCALIZATION_ID_WINDOWED_BACKGROUND_COLOR_STATIC, "&Fullscreen background color:", // LOCALIZATION_ID_FULLSCREEN_BACKGROUND_COLOR_STATIC, + "Reset Window Size & Position", // LOCALIZATION_ID_OPTIONS_VIEW_RESET_WINDOW_BUTTON, "&Left click action:", // LOCALIZATION_ID_LEFT_CLICK_ACTION_STATIC, "&Right click action:", // LOCALIZATION_ID_RIGHT_CLICK_ACTION_STATIC, "&Mouse wheel action:", // LOCALIZATION_ID_MOUSE_WHEEL_ACTION_STATIC, @@ -277,6 +281,13 @@ static const utf8_t *_localization_string_array_en_us[LOCALIZATION_ID_COUNT] = "Full Path", // LOCALIZATION_ID_OPTIONS_TITLE_BAR_FORMAT_FULL_PATH_COMBOBOXITEM, "Filename Only", // LOCALIZATION_ID_OPTIONS_TITLE_BAR_FORMAT_FILENAME_ONLY_COMBOBOXITEM, "None", // LOCALIZATION_ID_OPTIONS_TITLE_BAR_FORMAT_NONE_COMBOBOXITEM, + "Zoom &step:", // LOCALIZATION_ID_OPTIONS_ZOOM_STEP_STATIC, + "(default: 10 %)", // LOCALIZATION_ID_OPTIONS_ZOOM_STEP_DEFAULT_STATIC, + "Zoom calculation method:", // LOCALIZATION_ID_OPTIONS_ZOOM_CALC_METHOD_STATIC, + "&Relative step", // LOCALIZATION_ID_OPTIONS_ZOOM_CALC_RELATIVE_RADIO, + "&Absolute step", // LOCALIZATION_ID_OPTIONS_ZOOM_CALC_ABSOLUTE_RADIO, + "Use &fixed table: (for experts: max. 50 ordered values from 3 to 4000)", // LOCALIZATION_ID_OPTIONS_ZOOM_CALC_FIXED_TABLE_RADIO, + "&Load default table", // LOCALIZATION_ID_OPTIONS_ZOOM_LOAD_DEFAULT_TABLE_BUTTON, "Everything not available", // LOCALIZATION_ID_EVERYTHING_NOT_AVAILABLE_MESSAGE, "Add Everything Search", // LOCALIZATION_ID_EVERYTHING_ADD_EVERYTHING_SEARCH_CAPTION, "Load Everything Search", // LOCALIZATION_ID_EVERYTHING_LOAD_EVERYTHING_SEARCH_CAPTION, @@ -294,4 +305,23 @@ static const utf8_t *_localization_string_array_en_us[LOCALIZATION_ID_COUNT] = "TIFF Image", "TIFF Image", "WebP Image", + + // Compare Images + "Compare Images...", // LOCALIZATION_ID_COMPARE_IMAGES, + "Tips: Drag images with LEFT mouse button; Scroll mouse WHEEL to zoom in/out; Press Ctrl key or Right mouse button to work with individual images.", // LOCALIZATION_ID_COMPARE_TIPS, + "Auto-Fit", // LOCALIZATION_ID_COMPARE_AUTO_FIT, + "Smooth", // LOCALIZATION_ID_COMPARE_SMOOTH, + "Filename", // LOCALIZATION_ID_COMPARE_FILENAME, + "Close", // LOCALIZATION_ID_COMPARE_CLOSE, + "Previous", // LOCALIZATION_ID_COMPARE_PREV, + "Next", // LOCALIZATION_ID_COMPARE_NEXT, + "Zoom In", // LOCALIZATION_ID_COMPARE_ZOOM_IN, + "Zoom Out", // LOCALIZATION_ID_COMPARE_ZOOM_OUT, + "1:1", // LOCALIZATION_ID_COMPARE_ACTUAL_SIZE, + "Fit", // LOCALIZATION_ID_COMPARE_BEST_FIT, + "Rotate", // LOCALIZATION_ID_COMPARE_ROTATE, + "Select Image", // LOCALIZATION_ID_COMPARE_SELECT_IMAGE, + "Use Current Image", // LOCALIZATION_ID_COMPARE_USE_CURRENT, + "[No Image - Drop image here or click ... / Use Current]", // LOCALIZATION_ID_COMPARE_NO_IMAGE_DROP, + "&Smooth", // LOCALIZATION_ID_SMOOTH, }; diff --git a/src/localization_id_id.h b/src/localization_id_id.h new file mode 100644 index 00000000..e3dded6f --- /dev/null +++ b/src/localization_id_id.h @@ -0,0 +1,327 @@ +// +// Copyright 2026 hesphoros +// +// 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. +// +// Indonesian localization +// Save as UTF-8 without BOM. + +static const utf8_t *_localization_string_array_id_id[LOCALIZATION_ID_COUNT] = +{ + // Nama bahasa + "Bahasa Indonesia", // LOCALIZATION_ID_LANGUAGE + "&Bahasa:", // LOCALIZATION_ID_LANGUAGE_STATIC + "Default Sistem", // LOCALIZATION_ID_LANGUAGE_AUTO + + // Nama aplikasi + "void Image Viewer", // LOCALIZATION_ID_APP_NAME + + // Menu Berkas + "&Berkas", // LOCALIZATION_ID_FILE, + "&Buka Berkas...", // LOCALIZATION_ID_OPEN_FILE, + "Buka &Folder...", // LOCALIZATION_ID_OPEN_FOLDER, + "Buka Pencarian &Everything...", // LOCALIZATION_ID_OPEN_EVERYTHING_SEARCH, + "&Tambah Berkas...", // LOCALIZATION_ID_ADD_FILE, + "Tambah Folder...", // LOCALIZATION_ID_ADD_FOLDER, + "Tambah Pencarian Everything...", // LOCALIZATION_ID_ADD_EVERYTHING_SEARCH, + "Buka &Lokasi Berkas...", // LOCALIZATION_ID_OPEN_FILE_LOCATION, + "&Ubah...", // LOCALIZATION_ID_EDIT, + "P&ratinjau...", // LOCALIZATION_ID_PREVIEW, + "&Cetak...", // LOCALIZATION_ID_PRINT, + "Jadikan Wallpaper Des&ktop", // LOCALIZATION_ID_SET_DESKTOP_WALLPAPER, + "&Tutup", // LOCALIZATION_ID_CLOSE, + "&Hapus", // LOCALIZATION_ID_DELETE, + "Hapus (Tong Sampah)", // LOCALIZATION_ID_DELETE_RECYCLE, + "Hapus (Permanen)", // LOCALIZATION_ID_DELETE_PERMANENTLY, + "Ubah &Nama", // LOCALIZATION_ID_RENAME, + "P&roperti", // LOCALIZATION_ID_PROPERTIES, + "&Keluar", // LOCALIZATION_ID_EXIT, + + // Menu Ubah + "&Ubah", // LOCALIZATION_ID_EDIT_MENU, + "Po&tong", // LOCALIZATION_ID_CUT, + "&Salin", // LOCALIZATION_ID_COPY, + "Salin Nama Berkas", // LOCALIZATION_ID_COPY_FILENAME, + "Salin &Gambar", // LOCALIZATION_ID_COPY_IMAGE, + "&Tempel", // LOCALIZATION_ID_PASTE, + "Putar Se&arah Jarum Jam", // LOCALIZATION_ID_ROTATE_CLOCKWISE, + "Putar Ber&lawanan Jarum Jam", // LOCALIZATION_ID_ROTATE_COUNTERCLOCKWISE, + "Salin ke &Folder...", // LOCALIZATION_ID_COPY_TO, + "Pi&ndah ke Folder...", // LOCALIZATION_ID_MOVE_TO, + + // Menu Tampilan + "&Tampilan", // LOCALIZATION_ID_VIEW, + "Keterangan", // LOCALIZATION_ID_CAPTION, + "Bingkai", // LOCALIZATION_ID_FRAME, + "&Menu", // LOCALIZATION_ID_MENU, + "Bilah &Status", // LOCALIZATION_ID_STATUS_BAR, + "K&ontrol", // LOCALIZATION_ID_CONTROLS, + "&Preset", // LOCALIZATION_ID_PRESET, + "&Minimalis", // LOCALIZATION_ID_MINIMAL, + "&Ringkas", // LOCALIZATION_ID_COMPACT, + "&Normal", // LOCALIZATION_ID_NORMAL, + "&Layar Penuh", // LOCALIZATION_ID_FULLSCREEN, + "&Slideshow", // LOCALIZATION_ID_SLIDESHOW, + "&Ukuran Jendela", // LOCALIZATION_ID_VIEW_WINDOW_SIZE, + "50%", // LOCALIZATION_ID_VIEW_WINDOW_SIZE_50_PERCENT, + "100%", // LOCALIZATION_ID_VIEW_WINDOW_SIZE_100_PERCENT, + "200%", // LOCALIZATION_ID_VIEW_WINDOW_SIZE_200_PERCENT, + "&Pas Otomatis", // LOCALIZATION_ID_VIEW_WINDOW_SIZE_AUTO_FIT, + "&Segarkan", // LOCALIZATION_ID_REFRESH, + "&Izinkan Pengecilan", // LOCALIZATION_ID_ALLOW_SHRINKING, + "&Pertahankan Rasio Aspek", // LOCALIZATION_ID_KEEP_ASPECT_RATIO, + "&Isi Jendela", // LOCALIZATION_ID_FILL_WINDOW, + "1:1", // LOCALIZATION_ID_ONE_TO_ONE, + "Paling &Pas", // LOCALIZATION_ID_BEST_FIT, + "Geser && &Pindai", // LOCALIZATION_ID_PAN_SCAN, + "&Perbesar Ukuran", // LOCALIZATION_ID_INCREASE_SIZE, + "&Perkecil Ukuran", // LOCALIZATION_ID_DECREASE_SIZE, + "Pe&rlebar", // LOCALIZATION_ID_INCREASE_WIDTH, + "Pe&nyempit", // LOCALIZATION_ID_DECREASE_WIDTH, + "Per&tinggi", // LOCALIZATION_ID_INCREASE_HEIGHT, + "Per&pendek", // LOCALIZATION_ID_DECREASE_HEIGHT, + "&Zoom", // LOCALIZATION_ID_ZOOM, + "Zoom &Masuk", // LOCALIZATION_ID_ZOOM_IN, + "Zoom &Keluar", // LOCALIZATION_ID_ZOOM_OUT, + "&Atur Ulang", // LOCALIZATION_ID_RESET, + "Geser Ke &Atas", // LOCALIZATION_ID_MOVE_UP, + "Geser Ke &Bawah", // LOCALIZATION_ID_MOVE_DOWN, + "Geser Ke &Kiri", // LOCALIZATION_ID_MOVE_LEFT, + "Geser Ke &Kanan", // LOCALIZATION_ID_MOVE_RIGHT, + "Geser Atas Kiri", // LOCALIZATION_ID_MOVE_UP_LEFT, + "Geser Atas Kanan", // LOCALIZATION_ID_MOVE_UP_RIGHT, + "Geser Bawah Kiri", // LOCALIZATION_ID_MOVE_DOWN_LEFT, + "Geser Bawah Kanan", // LOCALIZATION_ID_MOVE_DOWN_RIGHT, + "Geser ke &Tengah", // LOCALIZATION_ID_MOVE_CENTER, + "Atur &Ulang", // LOCALIZATION_ID_PANSCAN_RESET, + "Selalu di &Atas", // LOCALIZATION_ID_ON_TOP, + "&Selalu", // LOCALIZATION_ID_ALWAYS, + "S&aat Memutar Slideshow atau Animasi", // LOCALIZATION_ID_WHILE_PLAYING_OR_ANIMATING, + "&Jangan Pernah", // LOCALIZATION_ID_NEVER, + "&Opsi...", // LOCALIZATION_ID_OPTIONS, + + // Menu Slideshow + "&Slideshow", // LOCALIZATION_ID_SLIDESHOW_MENU, + "&Putar/Jeda", // LOCALIZATION_ID_PLAY_PAUSE, + "&Kecepatan", // LOCALIZATION_ID_RATE, + "&Kurangi Kecepatan", // LOCALIZATION_ID_DECREASE_RATE, + "&Tambah Kecepatan", // LOCALIZATION_ID_INCREASE_RATE, + "250 Milidetik", // LOCALIZATION_ID_RATE_250_MILLISECONDS, + "500 Milidetik", // LOCALIZATION_ID_RATE_500_MILLISECONDS, + "&1 Detik", // LOCALIZATION_ID_RATE_1_SECOND, + "&2 Detik", // LOCALIZATION_ID_RATE_2_SECONDS, + "&3 Detik", // LOCALIZATION_ID_RATE_3_SECONDS, + "&4 Detik", // LOCALIZATION_ID_RATE_4_SECONDS, + "&5 Detik", // LOCALIZATION_ID_RATE_5_SECONDS, + "&6 Detik", // LOCALIZATION_ID_RATE_6_SECONDS, + "&7 Detik", // LOCALIZATION_ID_RATE_7_SECONDS, + "&8 Detik", // LOCALIZATION_ID_RATE_8_SECONDS, + "&9 Detik", // LOCALIZATION_ID_RATE_9_SECONDS, + "1&0 Detik", // LOCALIZATION_ID_RATE_10_SECONDS, + "20 Detik", // LOCALIZATION_ID_RATE_20_SECONDS, + "30 Detik", // LOCALIZATION_ID_RATE_30_SECONDS, + "40 Detik", // LOCALIZATION_ID_RATE_40_SECONDS, + "50 Detik", // LOCALIZATION_ID_RATE_50_SECONDS, + "1 Menit", // LOCALIZATION_ID_RATE_1_MINUTE, + "Kustom...", // LOCALIZATION_ID_CUSTOM, + + // Menu Animasi + "&Animasi", // LOCALIZATION_ID_ANIMATION, + "&Putar/Jeda", // LOCALIZATION_ID_ANIMATION_PLAY_PAUSE, + "Lompat ke &Depan", // LOCALIZATION_ID_ANIMATION_JUMP_FORWARD, + "Lompat ke &Belakang", // LOCALIZATION_ID_ANIMATION_JUMP_BACKWARD, + "Lompat Pendek ke &Depan", // LOCALIZATION_ID_ANIMATION_SHORT_JUMP_FORWARD, + "Lompat Pendek ke &Belakang", // LOCALIZATION_ID_ANIMATION_SHORT_JUMP_BACKWARD, + "Lompat Jauh ke &Depan", // LOCALIZATION_ID_ANIMATION_LONG_JUMP_FORWARD, + "Lompat Jauh ke &Belakang", // LOCALIZATION_ID_ANIMATION_LONG_JUMP_BACKWARD, + "Langkah &Frame", // LOCALIZATION_ID_ANIMATION_FRAME_STEP, + "Frame &Sebelumnya", // LOCALIZATION_ID_ANIMATION_PREVIOUS_FRAME, + "Frame Per&tama", // LOCALIZATION_ID_ANIMATION_FIRST_FRAME, + "Frame Ter&akhir", // LOCALIZATION_ID_ANIMATION_LAST_FRAME, + "&Kurangi Kecepatan", // LOCALIZATION_ID_ANIMATION_DECREASE_RATE, + "&Tambah Kecepatan", // LOCALIZATION_ID_ANIMATION_INCREASE_RATE, + "Atur &Ulang Kecepatan", // LOCALIZATION_ID_ANIMATION_RESET_RATE, + + // Menu Navigasi + "&Navigasi", // LOCALIZATION_ID_NAVIGATE, + "&Selanjutnya", // LOCALIZATION_ID_NEXT, + "&Sebelumnya", // LOCALIZATION_ID_PREVIOUS, + "&Home", // LOCALIZATION_ID_HOME, + "&End", // LOCALIZATION_ID_END, + "&Urutkan", // LOCALIZATION_ID_SORT, + "&Nama", // LOCALIZATION_ID_SORT_NAME, + "&Path Lengkap", // LOCALIZATION_ID_SORT_FULL_PATH, + "&Ukuran", // LOCALIZATION_ID_SORT_SIZE, + "Tanggal &Modifikasi", // LOCALIZATION_ID_SORT_DATE_MODIFIED, + "Tanggal &Dibuat", // LOCALIZATION_ID_SORT_DATE_CREATED, + "&Menaik (Ascending)", // LOCALIZATION_ID_SORT_ASCENDING, + "&Menurun (Descending)", // LOCALIZATION_ID_SORT_DESCENDING, + "Acak", // LOCALIZATION_ID_SHUFFLE, + "&Lompat Ke...", // LOCALIZATION_ID_JUMP_TO, + + // Menu Bantuan + "&Bantuan", // LOCALIZATION_ID_HELP, + "&Bantuan", // LOCALIZATION_ID_HELP_MENU, + "&Opsi Command Line", // LOCALIZATION_ID_COMMAND_LINE_OPTIONS, + "&Halaman Utama", // LOCALIZATION_ID_HOME_PAGE, + "&Donasi", // LOCALIZATION_ID_DONATE, + "&Tentang", // LOCALIZATION_ID_ABOUT, + "https://www.voidtools.com/support/voidimageviewer/", // LOCALIZATION_ID_HELP_SUPPORT_URL, + "https://www.voidtools.com/donate/", // LOCALIZATION_ID_HELP_DONATE_URL, + "https://www.voidtools.com/", // LOCALIZATION_ID_HELP_WEBSITE_URL, + + // Bilah Alat (Toolbar) + "Gambar Sebelumnya", // LOCALIZATION_ID_TOOLBAR_PREVIOUS_IMAGE_BUTTON, + "Gambar Selanjutnya", // LOCALIZATION_ID_TOOLBAR_NEXT_IMAGE_BUTTON, + "Putar Slideshow", // LOCALIZATION_ID_TOOLBAR_PLAY_SLIDESHOW_BUTTON, + "Jeda Slideshow", // LOCALIZATION_ID_TOOLBAR_PAUSE_SLIDESHOW_BUTTON, + "Paling Pas", // LOCALIZATION_ID_TOOLBAR_BEST_FIT_BUTTON, + "Ukuran Asli", // LOCALIZATION_ID_TOOLBAR_ACTUAL_SIZE_BUTTON, + + // Bilah Status + "PRAPEMUATAN", // LOCALIZATION_ID_STATUS_BAR_PRELOAD, + "Memuat...", // LOCALIZATION_ID_STATUS_BAR_LOADING, + "Berkas tidak ditemukan.", // LOCALIZATION_ID_STATUS_BAR_FILE_NOT_FOUND, + "Gagal memuat gambar.", // LOCALIZATION_ID_STATUS_BAR_FAILED_TO_LOAD_IMAGE, + "Slideshow diputar", // LOCALIZATION_ID_STATUS_BAR_SLIDESHOW_PLAYING, + "Pos %0.3f %0.3f, Zoom %0.3f %0.3f, Rasio Aspek %0.3f", // LOCALIZATION_ID_STATUS_BAR_POS_ZOOM_FORMAT, + "Kecepatan animasi %0.3f", // LOCALIZATION_ID_STATUS_BAR_ANIMATION_RATE_FORMAT, + "Kecepatan slideshow %d %s", // LOCALIZATION_ID_STATUS_BAR_SLIDESHOW_RATE_FORMAT, + "menit", // LOCALIZATION_ID_STATUS_BAR_MINUTES, + "detik", // LOCALIZATION_ID_STATUS_BAR_SECONDS, + "milidetik", // LOCALIZATION_ID_STATUS_BAR_MILLISECONDS, + + // Dialog Opsi + "Opsi - void Image Viewer", // LOCALIZATION_ID_OPTIONS_CAPTION, + "Umum", // LOCALIZATION_ID_OPTIONS_GENERAL_DIALOG, + "Tampilan", // LOCALIZATION_ID_OPTIONS_VIEW_DIALOG, + "Kontrol", // LOCALIZATION_ID_OPTIONS_CONTROLS_DIALOG, + "Opsi zoom", // LOCALIZATION_ID_OPTIONS_ZOOM_DIALOG, + "OK", // LOCALIZATION_ID_OK_BUTTON, + "Batal", // LOCALIZATION_ID_CANCEL_BUTTON, + "&Simpan pengaturan di %APPDATA%\\voidImageViewer", // LOCALIZATION_ID_STORE_SETTINGS_APPDATA, + "Izinkan beberapa &instansi", // LOCALIZATION_ID_ALLOW_MULTIPLE_INSTANCES, + "Pintasan &menu Start", // LOCALIZATION_ID_STARTMENU_SHORTCUTS, + "Asosiasi", // LOCALIZATION_ID_ASSOCIATIONS, + "Pilih &Semua", // LOCALIZATION_ID_CHECK_ALL, + "Pilih &Tidak Ada", // LOCALIZATION_ID_CHECK_NONE, + "Mode blit &pengecilan:", // LOCALIZATION_ID_SHRINK_BLIT_MODE_STATIC, + "Mode blit &pembesaran:", // LOCALIZATION_ID_MAGNIFY_BLIT_MODE, + "Nearest", // LOCALIZATION_ID_BLIT_MODE_NEAREST_COMBOBOXITEM, + "Linear", // LOCALIZATION_ID_BLIT_MODE_LINEAR_COMBOBOXITEM, + "Ukuran jendela &otomatis:", // LOCALIZATION_ID_OPTIONS_VIEW_AUTO_SIZE_WINDOW_STATIC, + "50%", // LOCALIZATION_ID_OPTIONS_VIEW_AUTO_SIZE_WINDOW_50_PERCENT_COMBOBOXITEM, + "100%", // LOCALIZATION_ID_OPTIONS_VIEW_AUTO_SIZE_WINDOW_100_PERCENT_COMBOBOXITEM, + "200%", // LOCALIZATION_ID_OPTIONS_VIEW_AUTO_SIZE_WINDOW_200_PERCENT_COMBOBOXITEM, + "Pas Otomatis", // LOCALIZATION_ID_OPTIONS_VIEW_AUTO_SIZE_WINDOW_AUTO_FIT_COMBOBOXITEM, + "&Putar animasi setidaknya sekali dalam slideshow", // LOCALIZATION_ID_PLAY_ANIMATIONS_ONCE_STATIC, + "Prapemuatan gambar &selanjutnya", // LOCALIZATION_ID_PRELOAD_NEXT_IMAGE_STATIC, + "Simpan gambar &terakhir di cache", // LOCALIZATION_ID_CACHE_LAST_IMAGE_STATIC, + "Warna latar &jendela:", // LOCALIZATION_ID_WINDOWED_BACKGROUND_COLOR_STATIC, + "Warna latar &layar penuh:", // LOCALIZATION_ID_FULLSCREEN_BACKGROUND_COLOR_STATIC, + "Atur Ulang Ukuran && Posisi Jendela", // LOCALIZATION_ID_OPTIONS_VIEW_RESET_WINDOW_BUTTON, + "Aksi klik &kiri:", // LOCALIZATION_ID_LEFT_CLICK_ACTION_STATIC, + "Aksi klik &kanan:", // LOCALIZATION_ID_RIGHT_CLICK_ACTION_STATIC, + "Aksi roda &mouse:", // LOCALIZATION_ID_MOUSE_WHEEL_ACTION_STATIC, + "&Perintah:", // LOCALIZATION_ID_COMMANDS_STATIC, + "Pengaturan untuk perintah terpilih", // LOCALIZATION_ID_SETTINGS_FOR_SELECTED_COMMAND, + "&Tambah...", // LOCALIZATION_ID_ADD_KEY_BUTTON, + "&Ubah...", // LOCALIZATION_ID_EDIT_KEY_BUTTON, + "&Hapus", // LOCALIZATION_ID_REMOVE_KEY_BUTTON, + "Tambah Pintasan Keyboard", // LOCALIZATION_ID_ADD_KEYBOARD_SHORTCUT_CAPTION, + "Ubah Pintasan Keyboard", // LOCALIZATION_ID_EDIT_KEYBOARD_SHORTCUT_CAPTION, + "&Tombol pintasan:", // LOCALIZATION_ID_SHORTCUT_KEY, + "Tombol pintasan sedang digunakan oleh:", // LOCALIZATION_ID_SHORTCUT_KEY_CURRENTLY_USED_BY, + "&Hapus", // LOCALIZATION_ID_REMOVE, + "Atur Kecepatan Kustom", // LOCALIZATION_ID_SET_CUSTOM_RATE_CAPTION, + "Kecepatan &kustom:", // LOCALIZATION_ID_CUSTOM_RATE_STATIC, + "menit", // LOCALIZATION_ID_CUSTOM_RATE_MINUTES, + "detik", // LOCALIZATION_ID_CUSTOM_RATE_SECONDS, + "milidetik", // LOCALIZATION_ID_CUSTOM_RATE_MILLISECONDS, + "Tentang void Image Viewer", // LOCALIZATION_ID_ABOUT_CAPTION, + "Ubah Nama", // LOCALIZATION_ID_RENAME_CAPTION, + "Lompat Ke", // LOCALIZATION_ID_JUMP_TO_TITLE, + "Pencarian Everything", // LOCALIZATION_ID_SEARCH_EVERYTHING, + "Hak Cipta \xC2\xA9 %d voidtools", // LOCALIZATION_ID_ABOUT_COPYRIGHT_FORMAT, + "support@voidtools.com", // LOCALIZATION_ID_ABOUT_EMAIL, + "www.voidtools.com", // LOCALIZATION_ID_ABOUT_WEBSITE, + "Buka Gambar", // LOCALIZATION_ID_OPEN_IMAGE_CAPTION, + "Semua Berkas Gambar", // LOCALIZATION_ID_OPEN_ALL_IMAGE_FILES, + "Semua Berkas", // LOCALIZATION_ID_OPEN_ALL_FILES, + "Gulir", // LOCALIZATION_ID_OPTIONS_ACTION_SCROLL_COMBOBOXITEM, + "Putar/Jeda Slideshow", // LOCALIZATION_ID_OPTIONS_ACTION_PLAY_PAUSE_SLIDESHOW_COMBOBOXITEM, + "Putar/Jeda Animasi", // LOCALIZATION_ID_OPTIONS_ACTION_PLAY_PAUSE_ANIMATION_COMBOBOXITEM, + "Zoom Masuk", // LOCALIZATION_ID_OPTIONS_ACTION_ZOOM_IN_COMBOBOXITEM, + "Gambar Selanjutnya", // LOCALIZATION_ID_OPTIONS_ACTION_NEXT_IMAGE_COMBOBOXITEM, + "Gulir 1:1", // LOCALIZATION_ID_OPTIONS_ACTION_ONE_TO_ONE_SCROLL_COMBOBOXITEM, + "Gulir/Pindah Jendela", // LOCALIZATION_ID_OPTIONS_ACTION_SCROLL_MOVE_WINDOW_COMBOBOXITEM, + "Menu Konteks", // LOCALIZATION_ID_OPTIONS_ACTION_CONTEXT_MENU_COMBOBOXITEM, + "Zoom Keluar", // LOCALIZATION_ID_OPTIONS_ACTION_ZOOM_OUT_COMBOBOXITEM, + "Gambar Sebelumnya", // LOCALIZATION_ID_OPTIONS_ACTION_PREVIOUS_IMAGE_COMBOBOXITEM, + "Zoom", // LOCALIZATION_ID_OPTIONS_ACTION_ZOOM_COMBOBOXITEM, + "Selanjutnya/Sebelumnya", // LOCALIZATION_ID_OPTIONS_ACTION_NEXT_PREV_COMBOBOXITEM, + "Sebelumnya/Selanjutnya", // LOCALIZATION_ID_OPTIONS_ACTION_PREV_NEXT_COMBOBOXITEM, + "Format bilah &judul:", // LOCALIZATION_ID_OPTIONS_TITLE_BAR_FORMAT_STATIC, + "Path Lengkap", // LOCALIZATION_ID_OPTIONS_TITLE_BAR_FORMAT_FULL_PATH_COMBOBOXITEM, + "Hanya Nama Berkas", // LOCALIZATION_ID_OPTIONS_TITLE_BAR_FORMAT_FILENAME_ONLY_COMBOBOXITEM, + "Tidak Ada", // LOCALIZATION_ID_OPTIONS_TITLE_BAR_FORMAT_NONE_COMBOBOXITEM, + "Langkah &zoom:", // LOCALIZATION_ID_OPTIONS_ZOOM_STEP_STATIC, + "(standar: 10 %)", // LOCALIZATION_ID_OPTIONS_ZOOM_STEP_DEFAULT_STATIC, + "Metode kalkulasi zoom:", // LOCALIZATION_ID_OPTIONS_ZOOM_CALC_METHOD_STATIC, + "Langkah &relatif", // LOCALIZATION_ID_OPTIONS_ZOOM_CALC_RELATIVE_RADIO, + "Langkah &absolut", // LOCALIZATION_ID_OPTIONS_ZOOM_CALC_ABSOLUTE_RADIO, + "Gunakan tabel &tetap: (khusus ahli: maks. 50 nilai terurut dari 3 ke 4000)", // LOCALIZATION_ID_OPTIONS_ZOOM_CALC_FIXED_TABLE_RADIO, + "Muat tabel &standar", // LOCALIZATION_ID_OPTIONS_ZOOM_LOAD_DEFAULT_TABLE_BUTTON, + "Everything tidak tersedia", // LOCALIZATION_ID_EVERYTHING_NOT_AVAILABLE_MESSAGE, + "Tambah Pencarian Everything", // LOCALIZATION_ID_EVERYTHING_ADD_EVERYTHING_SEARCH_CAPTION, + "Muat Pencarian Everything", // LOCALIZATION_ID_EVERYTHING_LOAD_EVERYTHING_SEARCH_CAPTION, + "Acak", // LOCALIZATION_ID_RANDOMIZE, + "Salin Ke", // LOCALIZATION_ID_COPY_TO_CAPTION, + "Pindah Ke", // LOCALIZATION_ID_MOVE_TO_CAPTION, + + // Deskripsi Asosiasi + "Gambar Bitmap", + "Gambar GIF Animasi", + "Berkas Ikon", + "Gambar JPEG", + "Gambar JPEG", + "Gambar PNG", + "Gambar TIFF", + "Gambar TIFF", + "Gambar WebP", + + // Compare Images + "Bandingkan Gambar...", // LOCALIZATION_ID_COMPARE_IMAGES, + "Tips: Seret gambar dengan tombol mouse KIRI; Gulir RODA mouse untuk zoom; Tekan tombol Ctrl atau tombol mouse Kanan untuk memproses gambar individual.", // LOCALIZATION_ID_COMPARE_TIPS, + "Pas Otomatis", // LOCALIZATION_ID_COMPARE_AUTO_FIT, + "Halus", // LOCALIZATION_ID_COMPARE_SMOOTH, + "Nama Berkas", // LOCALIZATION_ID_COMPARE_FILENAME, + "Tutup", // LOCALIZATION_ID_COMPARE_CLOSE, + "Sebelumnya", // LOCALIZATION_ID_COMPARE_PREV, + "Berikutnya", // LOCALIZATION_ID_COMPARE_NEXT, + "Perbesar", // LOCALIZATION_ID_COMPARE_ZOOM_IN, + "Perkecil", // LOCALIZATION_ID_COMPARE_ZOOM_OUT, + "1:1", // LOCALIZATION_ID_COMPARE_ACTUAL_SIZE, + "Pas", // LOCALIZATION_ID_COMPARE_BEST_FIT, + "Putar", // LOCALIZATION_ID_COMPARE_ROTATE, + "Pilih Gambar", // LOCALIZATION_ID_COMPARE_SELECT_IMAGE, + "Gunakan Gambar Saat Ini", // LOCALIZATION_ID_COMPARE_USE_CURRENT, + "[Tidak Ada Gambar - Tarik & lepas gambar ke sini atau klik ... / Gunakan Saat Ini]", // LOCALIZATION_ID_COMPARE_NO_IMAGE_DROP, + "&Halus", // LOCALIZATION_ID_SMOOTH, +}; diff --git a/src/localization_zh_cn.h b/src/localization_zh_cn.h index 3d135697..7f1e5531 100644 --- a/src/localization_zh_cn.h +++ b/src/localization_zh_cn.h @@ -26,6 +26,8 @@ static const utf8_t *_localization_string_array_zh_cn[LOCALIZATION_ID_COUNT] = { // Language "简体中文", // LOCALIZATION_ID_LANGUAGE + "语言(&L):", // LOCALIZATION_ID_LANGUAGE_STATIC + "系统默认", // LOCALIZATION_ID_LANGUAGE_AUTO // Application name "void Image Viewer", // LOCALIZATION_ID_APP_NAME @@ -211,6 +213,7 @@ static const utf8_t *_localization_string_array_zh_cn[LOCALIZATION_ID_COUNT] = "常规", // LOCALIZATION_ID_OPTIONS_GENERAL_DIALOG, "视图", // LOCALIZATION_ID_OPTIONS_VIEW_DIALOG, "控件", // LOCALIZATION_ID_OPTIONS_CONTROLS_DIALOG, + "缩放选项", // LOCALIZATION_ID_OPTIONS_ZOOM_DIALOG, "确定", // LOCALIZATION_ID_OK_BUTTON, "取消", // LOCALIZATION_ID_CANCEL_BUTTON, "在 %APPDATA%\\voidImageViewer 中存储设置(&S)", // LOCALIZATION_ID_STORE_SETTINGS_APPDATA, @@ -233,6 +236,7 @@ static const utf8_t *_localization_string_array_zh_cn[LOCALIZATION_ID_COUNT] = "缓存最后一张图像(&L)", // LOCALIZATION_ID_CACHE_LAST_IMAGE_STATIC, "窗口背景颜色(&W):", // LOCALIZATION_ID_WINDOWED_BACKGROUND_COLOR_STATIC, "全屏背景颜色(&F):", // LOCALIZATION_ID_FULLSCREEN_BACKGROUND_COLOR_STATIC, + "重置窗口大小和位置", // LOCALIZATION_ID_OPTIONS_VIEW_RESET_WINDOW_BUTTON, "左键操作(&L):", // LOCALIZATION_ID_LEFT_CLICK_ACTION_STATIC, "右键操作(&R):", // LOCALIZATION_ID_RIGHT_CLICK_ACTION_STATIC, "鼠标滚轮操作(&M):", // LOCALIZATION_ID_MOUSE_WHEEL_ACTION_STATIC, @@ -278,6 +282,13 @@ static const utf8_t *_localization_string_array_zh_cn[LOCALIZATION_ID_COUNT] = "完整路径", // LOCALIZATION_ID_OPTIONS_TITLE_BAR_FORMAT_FULL_PATH_COMBOBOXITEM, "仅文件名", // LOCALIZATION_ID_OPTIONS_TITLE_BAR_FORMAT_FILENAME_ONLY_COMBOBOXITEM, "无", // LOCALIZATION_ID_OPTIONS_TITLE_BAR_FORMAT_NONE_COMBOBOXITEM, + "缩放步长(&S):", // LOCALIZATION_ID_OPTIONS_ZOOM_STEP_STATIC, + "(默认: 10 %)", // LOCALIZATION_ID_OPTIONS_ZOOM_STEP_DEFAULT_STATIC, + "缩放计算方式:", // LOCALIZATION_ID_OPTIONS_ZOOM_CALC_METHOD_STATIC, + "相对步长(&R)", // LOCALIZATION_ID_OPTIONS_ZOOM_CALC_RELATIVE_RADIO, + "绝对步长(&A)", // LOCALIZATION_ID_OPTIONS_ZOOM_CALC_ABSOLUTE_RADIO, + "使用固定表(&F): (专家级: 3 到 4000 之间的最多 50 个有序值)", // LOCALIZATION_ID_OPTIONS_ZOOM_CALC_FIXED_TABLE_RADIO, + "加载默认表(&L)", // LOCALIZATION_ID_OPTIONS_ZOOM_LOAD_DEFAULT_TABLE_BUTTON, "Everything 不可用", // LOCALIZATION_ID_EVERYTHING_NOT_AVAILABLE_MESSAGE, "添加 Everything 搜索", // LOCALIZATION_ID_EVERYTHING_ADD_EVERYTHING_SEARCH_CAPTION, "加载 Everything 搜索", // LOCALIZATION_ID_EVERYTHING_LOAD_EVERYTHING_SEARCH_CAPTION, @@ -295,4 +306,23 @@ static const utf8_t *_localization_string_array_zh_cn[LOCALIZATION_ID_COUNT] = "TIFF Image", "TIFF Image", "WebP Image", + + // Compare Images + "比较图像...", // LOCALIZATION_ID_COMPARE_IMAGES, + "提示: 按住鼠标左键拖动图像; 滚动鼠标滚轮缩放; 按住 Ctrl 键或鼠标右键可单独操作当前图像。", // LOCALIZATION_ID_COMPARE_TIPS, + "自动适合", // LOCALIZATION_ID_COMPARE_AUTO_FIT, + "平滑", // LOCALIZATION_ID_COMPARE_SMOOTH, + "文件名", // LOCALIZATION_ID_COMPARE_FILENAME, + "关闭", // LOCALIZATION_ID_COMPARE_CLOSE, + "上一个", // LOCALIZATION_ID_COMPARE_PREV, + "下一个", // LOCALIZATION_ID_COMPARE_NEXT, + "放大", // LOCALIZATION_ID_COMPARE_ZOOM_IN, + "缩小", // LOCALIZATION_ID_COMPARE_ZOOM_OUT, + "1:1", // LOCALIZATION_ID_COMPARE_ACTUAL_SIZE, + "适合", // LOCALIZATION_ID_COMPARE_BEST_FIT, + "旋转", // LOCALIZATION_ID_COMPARE_ROTATE, + "选择图像", // LOCALIZATION_ID_COMPARE_SELECT_IMAGE, + "使用当前图像", // LOCALIZATION_ID_COMPARE_USE_CURRENT, + "[无图像 - 拖放图像至此处或点击 ... / 使用当前图像]", // LOCALIZATION_ID_COMPARE_NO_IMAGE_DROP, + "平滑(&S)", // LOCALIZATION_ID_SMOOTH, }; diff --git a/src/version.h b/src/version.h index ea4550ee..c96d09cf 100644 --- a/src/version.h +++ b/src/version.h @@ -1,6 +1,7 @@ #define VERSION_YEAR 2026 #define VERSION_MAJOR 1 -#define VERSION_MINOR 0 -#define VERSION_REVISION 0 -#define VERSION_BUILD 15 +#define VERSION_MINOR 2 +#define VERSION_REVISION 1 +#define VERSION_BUILD 0 #define VERSION_TYPE "" + diff --git a/src/viv.c b/src/viv.c index deff4526..e70970ad 100644 --- a/src/viv.c +++ b/src/viv.c @@ -257,7 +257,7 @@ #define _VIV_ASSOCIATION_TIFF 0x00000080 #define _VIV_ASSOCIATION_WEBP 0x00000100 -#define _VIV_ZOOM_MAX 16 +#define _VIV_ZOOM_MAX 64 #define BCM_SETSHIELD 0x0000160C @@ -444,7 +444,7 @@ typedef struct _viv_webp_s typedef struct _viv_name_mapping_s { UINT count; - SHNAMEMAPPING *mappings; + SHNAMEMAPPING mappings[1]; }_viv_name_mapping_t; static void _viv_update_title(void); @@ -496,6 +496,9 @@ static INT_PTR CALLBACK _viv_options_proc(HWND hwndDlg,UINT uMsg,WPARAM wParam,L static INT_PTR CALLBACK _viv_options_general_proc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam); static INT_PTR CALLBACK _viv_options_view_proc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam); static INT_PTR CALLBACK _viv_options_controls_proc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam); +static INT_PTR CALLBACK _viv_options_zoom_proc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam); +static double _viv_calculate_next_zoom(double current_percent,int zoom_in); +static int _viv_parse_fixed_table(const wchar_t *table_str,double *values,int max_values); static void _viv_install_association_by_extension(const char *association,const char *description,const char *icon_location); static void _viv_uninstall_association_by_extension(const char *association); static int _viv_is_association(const char *association); @@ -591,6 +594,8 @@ static INT_PTR CALLBACK _viv_rename_proc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM static void _viv_shuffle_playlist(void); static void _viv_show_jumpto(void); static LRESULT CALLBACK _viv_jumpto_proc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam); +static void _viv_compare_show(void); +static LRESULT CALLBACK _viv_compare_proc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam); static void _viv_jumpto_on_size(HWND hwnd); static void _viv_jumpto_on_search(HWND hwnd); static void _viv_jumpto_open_sel(HWND hwnd); @@ -608,6 +613,7 @@ static int _viv_compare_id(const WIN32_FIND_DATA *a,const WIN32_FIND_DATA *b); static int _viv_fd_compare_name(const WIN32_FIND_DATA *a,const WIN32_FIND_DATA *b); static int _viv_fd_compare_path_and_name(const WIN32_FIND_DATA *a,const WIN32_FIND_DATA *b); static void _viv_update_1to1_scroll(int x,int y); +static HBITMAP _viv_create_bitmap(HDC hdc, int width, int height); static HBITMAP _viv_orientate_hbitmap(HBITMAP hbitmap,int counterclockwise); static void _viv_send_random_everything_search(void); static void _viv_do_mousewheel_action(int action,int delta,int x,int y); @@ -674,6 +680,10 @@ static BYTE _viv_animation_play = 1; // play or pause animations static BYTE _viv_1to1 = 0; // temporarily show the image with 100% scaling static BYTE _viv_have_old_zoom = 0; // restore this zoom level after leaving 1:1 mode. static int _viv_old_zoom_pos = 0; // restore this zoom level after leaving 1:1 mode. +static BYTE _viv_is_custom_zoom = 0; // custom zoom level active +static double _viv_custom_zoom_percent = 100.0; // custom zoom in percent +static BYTE _viv_old_is_custom_zoom = 0; +static double _viv_old_custom_zoom_percent = 100.0; WIN32_FIND_DATA *_viv_current_fd = 0; // the current image find data including the full path and filename. WIN32_FIND_DATA *_viv_preload_fd = 0; static int _viv_view_x = 0; // the current image offset in pixels @@ -681,8 +691,6 @@ static int _viv_view_y = 0; // the current image offset in pixels static double _viv_view_ix = 0.0; // the current image offset in percent, used when resizing the window static double _viv_view_iy = 0.0; // the current image offset in percent, used when resizing the window static int _viv_zoom_pos = 0; // the current zoom level -//static float _viv_zoom_presets[_VIV_ZOOM_MAX] = {0.004815f,0.019215f,0.043060f,0.076120f,0.118079f,0.168530f,0.226989f,0.292893f,0.365607f,0.444430f,0.528603f,0.617316f,0.709715f,0.804909f,0.901983f,1.000000f}; // (1 - cos(((float)(x+1) * 1.570796f) / _VIV_ZOOM_MAX)) // this is missing cos((1 * 1.570796f) / _VIV_ZOOM_MAX), which is too small -static float _viv_zoom_presets[_VIV_ZOOM_MAX] = {0.0000,0.0100,0.0225,0.0379,0.0569,0.0806,0.1098,0.1461,0.1909,0.2465,0.3154,0.4007,0.5063,0.6372,0.7993,1.0000}; // 0.01 - 0.2 curve static ULONG_PTR os_GdiplusToken; // gdiplus handle static int _viv_image_wide = 0; // current image width @@ -733,12 +741,12 @@ static volatile int _viv_load_image_terminate = 0; static _viv_reply_t *_viv_reply_start = 0; static _viv_reply_t *_viv_reply_last = 0; static wchar_t *_viv_status_temp_text = 0; -static localization_id_t _viv_options_page_localization_id_array[] = {LOCALIZATION_ID_OPTIONS_GENERAL_DIALOG,LOCALIZATION_ID_OPTIONS_VIEW_DIALOG,LOCALIZATION_ID_OPTIONS_CONTROLS_DIALOG}; -static int _viv_options_tab_ids[] = {IDC_TAB1,IDC_TAB2,IDC_TAB3}; -static int _viv_options_dialog_ids[] = {IDD_GENERAL,IDD_VIEW,IDD_CONTROLS}; +static localization_id_t _viv_options_page_localization_id_array[] = {LOCALIZATION_ID_OPTIONS_GENERAL_DIALOG,LOCALIZATION_ID_OPTIONS_VIEW_DIALOG,LOCALIZATION_ID_OPTIONS_CONTROLS_DIALOG,LOCALIZATION_ID_OPTIONS_ZOOM_DIALOG}; +static int _viv_options_tab_ids[] = {IDC_TAB1,IDC_TAB2,IDC_TAB3,IDC_TAB4}; +static int _viv_options_dialog_ids[] = {IDD_GENERAL,IDD_VIEW,IDD_CONTROLS,IDD_ZOOM}; #define _VIV_OPTIONS_PAGE_COUNT (sizeof(_viv_options_dialog_ids) / sizeof(int)) -static int _viv_options_page_ids[] = {VIV_ID_OPTIONS_GENERAL,VIV_ID_OPTIONS_VIEW,VIV_ID_OPTIONS_CONTROLS}; -static DLGPROC _viv_options_page_procs[] = {_viv_options_general_proc,_viv_options_view_proc,_viv_options_controls_proc}; +static int _viv_options_page_ids[] = {VIV_ID_OPTIONS_GENERAL,VIV_ID_OPTIONS_VIEW,VIV_ID_OPTIONS_CONTROLS,VIV_ID_OPTIONS_ZOOM}; +static DLGPROC _viv_options_page_procs[] = {_viv_options_general_proc,_viv_options_view_proc,_viv_options_controls_proc,_viv_options_zoom_proc}; static HFONT _viv_about_hfont = 0; static wchar_t *_viv_last_open_file = 0; static wchar_t *_viv_last_open_folder = 0; @@ -849,6 +857,7 @@ static _viv_command_t _viv_commands[] = {LOCALIZATION_ID_INVALID,MF_SEPARATOR,_VIV_MENU_VIEW,0}, {LOCALIZATION_ID_FULLSCREEN,MF_STRING,_VIV_MENU_VIEW,VIV_ID_VIEW_FULLSCREEN}, {LOCALIZATION_ID_SLIDESHOW,MF_STRING,_VIV_MENU_VIEW,VIV_ID_VIEW_SLIDESHOW}, + {LOCALIZATION_ID_COMPARE_IMAGES,MF_STRING,_VIV_MENU_VIEW,VIV_ID_VIEW_COMPARE_IMAGES}, {LOCALIZATION_ID_VIEW_WINDOW_SIZE,MF_POPUP,_VIV_MENU_VIEW,_VIV_MENU_VIEW_WINDOW_SIZE}, {LOCALIZATION_ID_VIEW_WINDOW_SIZE_50_PERCENT,MF_STRING,_VIV_MENU_VIEW_WINDOW_SIZE,VIV_ID_VIEW_WINDOW_SIZE_50}, {LOCALIZATION_ID_VIEW_WINDOW_SIZE_100_PERCENT,MF_STRING,_VIV_MENU_VIEW_WINDOW_SIZE,VIV_ID_VIEW_WINDOW_SIZE_100}, @@ -859,6 +868,7 @@ static _viv_command_t _viv_commands[] = {LOCALIZATION_ID_ALLOW_SHRINKING,MF_STRING,_VIV_MENU_VIEW,VIV_ID_VIEW_ALLOW_SHRINKING}, {LOCALIZATION_ID_KEEP_ASPECT_RATIO,MF_STRING,_VIV_MENU_VIEW,VIV_ID_VIEW_KEEP_ASPECT_RATIO}, {LOCALIZATION_ID_FILL_WINDOW,MF_STRING,_VIV_MENU_VIEW,VIV_ID_VIEW_FILL_WINDOW}, + {LOCALIZATION_ID_SMOOTH,MF_STRING,_VIV_MENU_VIEW,VIV_ID_VIEW_SMOOTH}, {LOCALIZATION_ID_ONE_TO_ONE,MF_STRING,_VIV_MENU_VIEW,VIV_ID_VIEW_1TO1}, {LOCALIZATION_ID_BEST_FIT,MF_STRING|MF_OWNERDRAW,_VIV_MENU_VIEW,VIV_ID_VIEW_BESTFIT}, {LOCALIZATION_ID_INVALID,MF_SEPARATOR,_VIV_MENU_VIEW,0}, @@ -988,12 +998,11 @@ _viv_default_key_t _viv_default_keys[] = {VIV_ID_EDIT_COPY,CONFIG_KEYFLAG_CTRL | 'C'}, {VIV_ID_EDIT_COPY_FILENAME,CONFIG_KEYFLAG_CTRL | CONFIG_KEYFLAG_SHIFT | 'C'}, {VIV_ID_EDIT_PASTE,CONFIG_KEYFLAG_CTRL | 'V'}, - {VIV_ID_VIEW_PRESET_1,'1'}, - {VIV_ID_VIEW_PRESET_2,'2'}, - {VIV_ID_VIEW_PRESET_3,'3'}, + {VIV_ID_VIEW_1TO1,CONFIG_KEYFLAG_CTRL | CONFIG_KEYFLAG_ALT | '0'}, {VIV_ID_VIEW_FULLSCREEN,CONFIG_KEYFLAG_ALT | VK_RETURN}, {VIV_ID_VIEW_SLIDESHOW,VK_F11}, + {VIV_ID_VIEW_COMPARE_IMAGES,CONFIG_KEYFLAG_CTRL | CONFIG_KEYFLAG_ALT | 'C'}, {VIV_ID_VIEW_WINDOW_SIZE_50,CONFIG_KEYFLAG_ALT | '1'}, {VIV_ID_VIEW_WINDOW_SIZE_100,CONFIG_KEYFLAG_ALT | '2'}, {VIV_ID_VIEW_WINDOW_SIZE_200,CONFIG_KEYFLAG_ALT | '3'}, @@ -1086,6 +1095,7 @@ WORD _viv_context_menu_items[] = VIV_ID_VIEW_ALLOW_SHRINKING, VIV_ID_VIEW_KEEP_ASPECT_RATIO, VIV_ID_VIEW_FILL_WINDOW, + VIV_ID_VIEW_SMOOTH, VIV_ID_VIEW_1TO1, // VIV_ID_VIEW_SLIDESHOW, 0, @@ -1280,12 +1290,16 @@ static void _viv_clear(void) _viv_frame_count = 0; _viv_frame_loaded_count = 0; _viv_zoom_pos = 0; + _viv_is_custom_zoom = 0; + _viv_custom_zoom_percent = 100.0; _viv_view_x = 0; _viv_view_y = 0; _viv_view_ix = 0.0; _viv_view_iy = 0.0; _viv_1to1 = 0; _viv_have_old_zoom = 0; + _viv_old_is_custom_zoom = 0; + _viv_old_custom_zoom_percent = 100.0; _viv_image_wide = 0; _viv_image_high = 0; _viv_animation_play = 1; @@ -1675,9 +1689,12 @@ static void _viv_command_with_is_key_repeat(int command_id,int is_key_repeat) case VIV_ID_VIEW_ZOOM_RESET: _viv_1to1 = 0; + _viv_is_custom_zoom = 0; + _viv_custom_zoom_percent = 100.0; _viv_zoom_pos = 0; _viv_view_set(_viv_view_x,_viv_view_y,1); InvalidateRect(_viv_hwnd,0,FALSE); + _viv_toolbar_update_buttons(); break; case VIV_ID_HELP_HELP: @@ -2051,6 +2068,12 @@ static void _viv_command_with_is_key_repeat(int command_id,int is_key_repeat) InvalidateRect(_viv_hwnd,0,FALSE); break; + case VIV_ID_VIEW_SMOOTH: + config_shrink_blit_mode = (config_shrink_blit_mode == CONFIG_SHRINK_BLIT_MODE_HALFTONE) ? CONFIG_SHRINK_BLIT_MODE_COLORONCOLOR : CONFIG_SHRINK_BLIT_MODE_HALFTONE; + config_mag_filter = (config_shrink_blit_mode == CONFIG_SHRINK_BLIT_MODE_HALFTONE) ? CONFIG_MAG_FILTER_HALFTONE : CONFIG_MAG_FILTER_COLORONCOLOR; + InvalidateRect(_viv_hwnd,0,FALSE); + break; + case VIV_ID_VIEW_FULLSCREEN: _viv_toggle_fullscreen(); @@ -2073,6 +2096,12 @@ static void _viv_command_with_is_key_repeat(int command_id,int is_key_repeat) break; + case VIV_ID_VIEW_COMPARE_IMAGES: + + _viv_compare_show(); + + break; + case VIV_ID_VIEW_WINDOW_SIZE_50: case VIV_ID_VIEW_WINDOW_SIZE_100: case VIV_ID_VIEW_WINDOW_SIZE_200: @@ -5260,6 +5289,7 @@ static int _viv_init(int nCmdShow) // load settings config_load_settings(); + localization_init(); // config_maximized will be overwritten when we show are normal window // so save it now and apply it later. @@ -6881,6 +6911,19 @@ static void _viv_get_render_size(int *prw,int *prh) return; } + if (_viv_is_custom_zoom) + { + rw = (int)((double)_viv_image_wide * _viv_custom_zoom_percent / 100.0 + 0.5); + rh = (int)((double)_viv_image_high * _viv_custom_zoom_percent / 100.0 + 0.5); + if (rw <= 0) rw = 1; + if (rh <= 0) rh = 1; + + *prw = rw; + *prh = rh; + + return; + } + if (_viv_is_fullscreen) { fill_window = config_fullscreen_fill_window; @@ -6975,23 +7018,11 @@ static void _viv_get_render_size(int *prw,int *prh) if (config_keep_aspect_ratio) { - if (rw > _viv_image_wide) - { - max_zoom_wide = rw * 16; - } - else - { - max_zoom_wide = _viv_image_wide * 16; - } + max_zoom_wide = rw * 16; + if (max_zoom_wide < _viv_image_wide * 16) max_zoom_wide = _viv_image_wide * 16; - if (rh > _viv_image_high) - { - max_zoom_high = rh * 16; - } - else - { - max_zoom_high = _viv_image_high * 16; - } + max_zoom_high = rh * 16; + if (max_zoom_high < _viv_image_high * 16) max_zoom_high = _viv_image_high * 16; } else { @@ -7011,8 +7042,9 @@ static void _viv_get_render_size(int *prw,int *prh) */ if (_viv_zoom_pos) { - rw = rw + (int)((max_zoom_wide - rw) * _viv_zoom_presets[_viv_zoom_pos]); - rh = rh + (int)((max_zoom_high - rh) * _viv_zoom_presets[_viv_zoom_pos]); + double zoom_factor = pow((double)max_zoom_wide / (double)rw, (double)_viv_zoom_pos / (double)(_VIV_ZOOM_MAX - 1)); + rw = (int)((double)rw * zoom_factor); + rh = (int)((double)rh * zoom_factor); } *prw = rw; @@ -7128,6 +7160,7 @@ static void _viv_check_menus(HMENU hmenu) CheckMenuItem(hmenu,VIV_ID_VIEW_ALLOW_SHRINKING,config_allow_shrinking ? MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hmenu,VIV_ID_VIEW_KEEP_ASPECT_RATIO,config_keep_aspect_ratio ? MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hmenu,VIV_ID_VIEW_FILL_WINDOW,fill_window ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(hmenu,VIV_ID_VIEW_SMOOTH,(config_shrink_blit_mode == CONFIG_SHRINK_BLIT_MODE_HALFTONE) ? MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hmenu,VIV_ID_VIEW_1TO1,((rw == _viv_image_wide) && (rh == _viv_image_high)) ? MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hmenu,VIV_ID_VIEW_FULLSCREEN,_viv_is_fullscreen ? MF_CHECKED : MF_UNCHECKED); CheckMenuItem(hmenu,VIV_ID_VIEW_SLIDESHOW,is_slideshow ? MF_CHECKED : MF_UNCHECKED); @@ -7212,7 +7245,7 @@ static void _viv_delete(int permanently) fo.hwnd = _viv_hwnd; fo.wFunc = FO_DELETE; fo.pFrom = filename_list; - fo.fFlags = permanently ? 0 : FOF_ALLOWUNDO; + fo.fFlags = permanently ? FOF_WANTNUKEWARNING : FOF_ALLOWUNDO; if (SHFileOperation(&fo) == 0) { @@ -7295,8 +7328,8 @@ static INT_PTR CALLBACK _viv_rename_proc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM fo.pTo = new_filename_list; fo.fFlags = FOF_ALLOWUNDO | FOF_WANTMAPPINGHANDLE; - // returns ERROR_CANCELLED if user cancelled. - if (SHFileOperation(&fo) == 0) + int sh_ret = SHFileOperation(&fo); + if (sh_ret == 0) { if (fo.fAnyOperationsAborted) { @@ -7316,7 +7349,7 @@ static INT_PTR CALLBACK _viv_rename_proc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM mappings = (_viv_name_mapping_t *)fo.hNameMappings; - if (mappings->count == 1) + if (mappings->count >= 1) { // use the resolved name incase there was a rename collision. file_op_new_name = mappings->mappings[0].pszNewPath; @@ -7340,6 +7373,11 @@ static INT_PTR CALLBACK _viv_rename_proc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM SHFreeNameMappings(fo.hNameMappings); } } + else + { + // returned non-zero (user cancelled or clicked No) + dont_end_dialog = 1; + } } if (!dont_end_dialog) @@ -7505,7 +7543,7 @@ static void _viv_set_clipboard_image(void) { HBITMAP mem1_hbitmap; - mem1_hbitmap = CreateCompatibleBitmap(screen_hdc,_viv_image_wide,_viv_image_high); + mem1_hbitmap = _viv_create_bitmap(screen_hdc,_viv_image_wide,_viv_image_high); if (mem1_hbitmap) { HGDIOBJ last_mem1_hbitmap; @@ -7936,8 +7974,26 @@ static INT_PTR CALLBACK _viv_options_general_proc(HWND hwnd,UINT msg,WPARAM wPar case WM_INITDIALOG: { int exti; - os_SetDlgItemText_localization_id(hwnd,IDC_APPDATA,LOCALIZATION_ID_STORE_SETTINGS_APPDATA); + os_SetDlgItemText_localization_id(hwnd,IDC_LANGUAGE_STATIC,LOCALIZATION_ID_LANGUAGE_STATIC); + + { + HWND hLanguage = GetDlgItem(hwnd,IDC_LANGUAGE_COMBOBOX); + int index; + + index = os_ComboBox_AddString_localization_id(hwnd,IDC_LANGUAGE_COMBOBOX,LOCALIZATION_ID_LANGUAGE_AUTO); + ComboBox_SetItemData(hLanguage,index,255); + if (config_language == 255) ComboBox_SetCurSel(hLanguage,index); + + for(int i=0;i= L'0') && (*p <= L'9')) || (*p == L'.')) + { + double val = 0.0; + double frac = 0.0; + double div = 10.0; + + while ((*p >= L'0') && (*p <= L'9')) + { + val = (val * 10.0) + (*p - L'0'); + p++; + } + if (*p == L'.') + { + p++; + while ((*p >= L'0') && (*p <= L'9')) + { + frac += (*p - L'0') / div; + div *= 10.0; + p++; + } + } + val += frac; + if (val > 0.0) + { + values[count++] = val; + } + } + else + { + p++; + } + } + + { + int i, j; + for (i = 0; i < count - 1; i++) + { + for (j = i + 1; j < count; j++) + { + if (values[j] < values[i]) + { + double tmp = values[i]; + values[i] = values[j]; + values[j] = tmp; + } + } + } + } + + return count; +} + +static double _viv_calculate_next_zoom(double current_percent,int zoom_in) +{ + double new_percent = current_percent; + int step = config_zoom_step > 0 ? config_zoom_step : CONFIG_ZOOM_DEFAULT_STEP; + + if (config_zoom_calc_method == CONFIG_ZOOM_CALC_RELATIVE) + { + if (zoom_in) + { + new_percent = current_percent * (1.0 + ((double)step / 100.0)); + } + else + { + new_percent = current_percent / (1.0 + ((double)step / 100.0)); + } + } + else if (config_zoom_calc_method == CONFIG_ZOOM_CALC_ABSOLUTE) + { + if (zoom_in) + { + new_percent = current_percent + (double)step; + } + else + { + new_percent = current_percent - (double)step; + } + } + else if (config_zoom_calc_method == CONFIG_ZOOM_CALC_FIXED_TABLE) + { + double table_values[64]; + int table_count = _viv_parse_fixed_table(config_zoom_fixed_table, table_values, 64); + if (table_count <= 0) + { + table_count = _viv_parse_fixed_table(CONFIG_ZOOM_DEFAULT_FIXED_TABLE, table_values, 64); + } + + if (table_count > 0) + { + int i; + if (zoom_in) + { + new_percent = table_values[table_count - 1]; + for (i = 0; i < table_count; i++) + { + if (table_values[i] > current_percent + 0.001) + { + new_percent = table_values[i]; + break; + } + } + } + else + { + new_percent = table_values[0]; + for (i = table_count - 1; i >= 0; i--) + { + if (table_values[i] < current_percent - 0.001) + { + new_percent = table_values[i]; + break; + } + } + } + } + } + + if (new_percent < 0.1) new_percent = 0.1; + if (new_percent > 10000.0) new_percent = 10000.0; + + return new_percent; +} + +static INT_PTR CALLBACK _viv_options_zoom_proc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) +{ + switch(msg) + { + case WM_INITDIALOG: + { + wchar_t wbuf[STRING_SIZE]; + + os_SetDlgItemText_localization_id(hwnd,IDC_ZOOM_STEP_STATIC,LOCALIZATION_ID_OPTIONS_ZOOM_STEP_STATIC); + os_SetDlgItemText_localization_id(hwnd,IDC_ZOOM_STEP_DEFAULT_STATIC,LOCALIZATION_ID_OPTIONS_ZOOM_STEP_DEFAULT_STATIC); + os_SetDlgItemText_localization_id(hwnd,IDC_ZOOM_CALC_METHOD_STATIC,LOCALIZATION_ID_OPTIONS_ZOOM_CALC_METHOD_STATIC); + os_SetDlgItemText_localization_id(hwnd,IDC_ZOOM_CALC_RELATIVE_RADIO,LOCALIZATION_ID_OPTIONS_ZOOM_CALC_RELATIVE_RADIO); + os_SetDlgItemText_localization_id(hwnd,IDC_ZOOM_CALC_ABSOLUTE_RADIO,LOCALIZATION_ID_OPTIONS_ZOOM_CALC_ABSOLUTE_RADIO); + os_SetDlgItemText_localization_id(hwnd,IDC_ZOOM_CALC_FIXED_TABLE_RADIO,LOCALIZATION_ID_OPTIONS_ZOOM_CALC_FIXED_TABLE_RADIO); + os_SetDlgItemText_localization_id(hwnd,IDC_ZOOM_LOAD_DEFAULT_TABLE_BUTTON,LOCALIZATION_ID_OPTIONS_ZOOM_LOAD_DEFAULT_TABLE_BUTTON); + + string_format_number(wbuf,config_zoom_step); + SetDlgItemText(hwnd,IDC_ZOOM_STEP_EDIT,wbuf); + + if (config_zoom_calc_method == CONFIG_ZOOM_CALC_RELATIVE) + { + CheckRadioButton(hwnd,IDC_ZOOM_CALC_RELATIVE_RADIO,IDC_ZOOM_CALC_FIXED_TABLE_RADIO,IDC_ZOOM_CALC_RELATIVE_RADIO); + } + else if (config_zoom_calc_method == CONFIG_ZOOM_CALC_ABSOLUTE) + { + CheckRadioButton(hwnd,IDC_ZOOM_CALC_RELATIVE_RADIO,IDC_ZOOM_CALC_FIXED_TABLE_RADIO,IDC_ZOOM_CALC_ABSOLUTE_RADIO); + } + else + { + CheckRadioButton(hwnd,IDC_ZOOM_CALC_RELATIVE_RADIO,IDC_ZOOM_CALC_FIXED_TABLE_RADIO,IDC_ZOOM_CALC_FIXED_TABLE_RADIO); + } + + SetDlgItemText(hwnd,IDC_ZOOM_FIXED_TABLE_EDIT,config_zoom_fixed_table); + + return FALSE; + } + + case WM_COMMAND: + switch(LOWORD(wParam)) + { + case IDC_ZOOM_LOAD_DEFAULT_TABLE_BUTTON: + SetDlgItemText(hwnd,IDC_ZOOM_FIXED_TABLE_EDIT,CONFIG_ZOOM_DEFAULT_FIXED_TABLE); + break; + } + break; + } + + return FALSE; +} + static void _viv_options_treeview_changed(HWND hwnd) { TV_ITEM tvi; @@ -8649,6 +8931,17 @@ static INT_PTR CALLBACK _viv_options_proc(HWND hwnd,UINT msg,WPARAM wParam,LPARA general_page = GetDlgItem(hwnd,VIV_ID_OPTIONS_GENERAL); view_page = GetDlgItem(hwnd,VIV_ID_OPTIONS_VIEW); controls_page = GetDlgItem(hwnd,VIV_ID_OPTIONS_CONTROLS); + + { + BYTE new_language = (BYTE)ComboBox_GetItemData(GetDlgItem(general_page,IDC_LANGUAGE_COMBOBOX),ComboBox_GetCurSel(GetDlgItem(general_page,IDC_LANGUAGE_COMBOBOX))); + if (new_language != config_language) + { + config_language = new_language; + localization_init(); + // Note: full UI refresh might be needed for dynamic change, but for now we just save it. + } + } + config_multiple_instances = 0; if (IsDlgButtonChecked(general_page,IDC_APPDATA) == BST_CHECKED) @@ -8774,6 +9067,38 @@ static INT_PTR CALLBACK _viv_options_proc(HWND hwnd,UINT msg,WPARAM wParam,LPARA config_loop_animations_once = IsDlgButtonChecked(view_page,IDC_LOOP_ANIMATIONS_ONCE_STATIC) == BST_CHECKED ? 1 : 0; config_preload_next = IsDlgButtonChecked(view_page,IDC_PRELOAD_NEXT_IMAGE_STATIC) == BST_CHECKED ? 1 : 0; config_cache_last = IsDlgButtonChecked(view_page,IDC_CACHE_LAST_IMAGE_STATIC) == BST_CHECKED ? 1 : 0; + + { + HWND zoom_page; + zoom_page = GetDlgItem(hwnd,VIV_ID_OPTIONS_ZOOM); + if (zoom_page) + { + wchar_t wbuf[STRING_SIZE]; + int step; + + GetDlgItemText(zoom_page,IDC_ZOOM_STEP_EDIT,wbuf,STRING_SIZE); + step = string_to_int(wbuf); + if (step > 0) + { + config_zoom_step = step; + } + + if (IsDlgButtonChecked(zoom_page,IDC_ZOOM_CALC_RELATIVE_RADIO) == BST_CHECKED) + { + config_zoom_calc_method = CONFIG_ZOOM_CALC_RELATIVE; + } + else if (IsDlgButtonChecked(zoom_page,IDC_ZOOM_CALC_ABSOLUTE_RADIO) == BST_CHECKED) + { + config_zoom_calc_method = CONFIG_ZOOM_CALC_ABSOLUTE; + } + else if (IsDlgButtonChecked(zoom_page,IDC_ZOOM_CALC_FIXED_TABLE_RADIO) == BST_CHECKED) + { + config_zoom_calc_method = CONFIG_ZOOM_CALC_FIXED_TABLE; + } + + GetDlgItemText(zoom_page,IDC_ZOOM_FIXED_TABLE_EDIT,config_zoom_fixed_table,STRING_SIZE); + } + } // copy keys. _viv_key_list_copy(_viv_key_list,(_viv_key_list_t *)GetWindowLongPtr(GetDlgItem(controls_page,IDC_COMMANDS_LIST),GWLP_USERDATA)); @@ -9321,9 +9646,12 @@ static void _viv_view_1to1(void) if (_viv_have_old_zoom) { _viv_1to1 = 0; + _viv_is_custom_zoom = _viv_old_is_custom_zoom; + _viv_custom_zoom_percent = _viv_old_custom_zoom_percent; _viv_zoom_pos = _viv_old_zoom_pos; _viv_view_set(_viv_view_x,_viv_view_y,1); InvalidateRect(_viv_hwnd,0,FALSE); + _viv_toolbar_update_buttons(); return; } @@ -9331,11 +9659,15 @@ static void _viv_view_1to1(void) _viv_have_old_zoom = 1; _viv_old_zoom_pos = _viv_zoom_pos; + _viv_old_is_custom_zoom = _viv_is_custom_zoom; + _viv_old_custom_zoom_percent = _viv_custom_zoom_percent; _viv_1to1 = 1; + _viv_is_custom_zoom = 0; _viv_zoom_pos = 0; _viv_view_set(_viv_view_x,_viv_view_y,1); InvalidateRect(_viv_hwnd,0,FALSE); + _viv_toolbar_update_buttons(); } static void _viv_playlist_add_current_if_empty(void) @@ -10260,7 +10592,7 @@ static int _viv_webp_frame_proc(_viv_webp_t *viv_webp,BYTE *pixels,int delay) bmi.bmiHeader.biBitCount = 24; bmi.bmiHeader.biCompression = BI_RGB; - hbitmap = CreateCompatibleBitmap(viv_webp->screen_hdc,viv_webp->wide,viv_webp->high); + hbitmap = _viv_create_bitmap(viv_webp->screen_hdc,viv_webp->wide,viv_webp->high); // Set RGB data to the bitmap if (hbitmap) @@ -10612,7 +10944,7 @@ static DWORD WINAPI _viv_load_image_thread_proc(void *param) break; } - hbitmap = CreateCompatibleBitmap(screen_hdc,load_wide,load_high); + hbitmap = _viv_create_bitmap(screen_hdc,load_wide,load_high); if (hbitmap) { UINT image_flags; @@ -11692,7 +12024,7 @@ static void _viv_toolbar_update_buttons(void) tbbinfo.cbSize = sizeof(TBBUTTONINFO); tbbinfo.dwMask = TBIF_STATE; - tbbinfo.fsState = ((_viv_zoom_pos == 0) && (!_viv_1to1)) ? (0) : (TBSTATE_ENABLED); + tbbinfo.fsState = ((!_viv_is_custom_zoom) && (_viv_zoom_pos == 0) && (!_viv_1to1)) ? (0) : (TBSTATE_ENABLED); SendMessage(_viv_toolbar_hwnd,TB_SETBUTTONINFO,VIV_ID_VIEW_BESTFIT,(LPARAM)&tbbinfo); } @@ -11974,7 +12306,7 @@ static void _viv_update_color_button_bitmap(HWND hwnd) screen_hdc = GetDC(0); mem_hdc = CreateCompatibleDC(screen_hdc); - hbitmap = CreateCompatibleBitmap(screen_hdc,wide,high); + hbitmap = _viv_create_bitmap(screen_hdc,wide,high); last_hbitmap = SelectObject(mem_hdc,hbitmap); @@ -13279,67 +13611,1715 @@ static void _viv_show_jumpto(void) } } -static void _viv_nav_item_free_all(void) -{ - if (_viv_nav_items) - { - int i; - - for(i=0;i<_viv_nav_item_count;i++) - { - mem_free(_viv_nav_items[i]); - } - - mem_free(_viv_nav_items); +// +// Compare Images Implementation +// - __viv_nav_item_start = 0; - _viv_nav_item_last = 0; - _viv_nav_items = 0; - _viv_nav_item_count = 0; - } -} +#define _VIV_COMPARE_PANE_COUNT 2 + +enum { + _VIV_COMPARE_BTN_NONE = 0, + _VIV_COMPARE_BTN_PREV, + _VIV_COMPARE_BTN_NEXT, + _VIV_COMPARE_BTN_ZOOM_IN, + _VIV_COMPARE_BTN_ZOOM_OUT, + _VIV_COMPARE_BTN_ZOOM_TEXT, + _VIV_COMPARE_BTN_1TO1, + _VIV_COMPARE_BTN_FIT, + _VIV_COMPARE_BTN_ROTATE, + _VIV_COMPARE_BTN_SELECT, + _VIV_COMPARE_BTN_USE_CURRENT, + + _VIV_COMPARE_TOP_AUTOFIT = 101, + _VIV_COMPARE_TOP_SMOOTH = 102, + _VIV_COMPARE_TOP_FILENAME = 103, + _VIV_COMPARE_TOP_CLOSE = 104 +}; -static void _viv_nav_item_add(WIN32_FIND_DATA *fd) +typedef struct _viv_compare_pane_s { - _viv_nav_item_t *navitem; - - navitem = mem_alloc(sizeof(_viv_nav_item_t)); + wchar_t filename[STRING_SIZE]; + VIV_UINT64 filesize; + int image_wide; + int image_high; + HBITMAP hbitmap; + _viv_mipmap_t *mipmap; + int orientation; - os_copy_memory(&navitem->fd,fd,sizeof(WIN32_FIND_DATA)); - - if (__viv_nav_item_start) + double zoom_percent; + int is_custom_zoom; + int is_1to1; + int is_autofit; + int view_x; + int view_y; + + RECT pane_rect; + RECT toolbar_rect; + RECT viewport_rect; + + RECT btn_prev_rect; + RECT btn_next_rect; + RECT btn_zoom_in_rect; + RECT btn_zoom_out_rect; + RECT zoom_text_rect; + RECT btn_1to1_rect; + RECT btn_fit_rect; + RECT btn_rotate_rect; + RECT btn_select_rect; + RECT use_current_rect; + + int use_current; + + _viv_playlist_t *playlist_item; +} _viv_compare_pane_t; + +typedef struct _viv_compare_state_s +{ + HWND hwnd; + int pane_count; + _viv_compare_pane_t panes[_VIV_COMPARE_PANE_COUNT]; + + int opt_autofit; + int opt_smooth; + int opt_filename; + + RECT topbar_rect; + RECT tips_rect; + RECT autofit_rect; + RECT smooth_rect; + RECT filename_rect; + RECT close_btn_rect; + + int is_dragging; + int drag_last_x; + int drag_last_y; + int drag_pane_index; + int drag_is_individual; + + int hovered_pane; + int hovered_btn; + int hovered_top; + + HFONT font_ui; + HFONT font_bold; + HFONT font_overlay; +} _viv_compare_state_t; + +static _viv_compare_state_t *_viv_compare_state = NULL; + +static void _viv_compare_free_pane_image(_viv_compare_pane_t *pane) +{ + if (pane->mipmap) { - _viv_nav_item_last->next = navitem; + _viv_mipmap_free(pane->mipmap); + pane->mipmap = NULL; } - else + if (pane->hbitmap) { - __viv_nav_item_start = navitem; + DeleteObject(pane->hbitmap); + pane->hbitmap = NULL; } - - navitem->next = 0; - _viv_nav_item_last = navitem; - _viv_nav_item_count++; + pane->image_wide = 0; + pane->image_high = 0; + pane->filesize = 0; } -static int _viv_nav_compare(const _viv_nav_item_t *a,const _viv_nav_item_t *b) +typedef struct { + _viv_compare_pane_t *pane; + DWORD wide; + DWORD high; + int has_alpha; + int orientation; +} _viv_compare_webp_ctx_t; + +static int _viv_compare_webp_info_proc(void *user_data, DWORD frame_count, DWORD wide, DWORD high, int has_alpha) { - return _viv_fd_compare_name(&a->fd,&b->fd); + _viv_compare_webp_ctx_t *ctx = (_viv_compare_webp_ctx_t *)user_data; + ctx->wide = wide; + ctx->high = high; + ctx->has_alpha = has_alpha; + return 1; } -static INT_PTR CALLBACK _viv_search_everything_proc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) +static int _viv_compare_webp_frame_proc(void *user_data, BYTE *pixels, int delay) { - switch(msg) + _viv_compare_webp_ctx_t *ctx = (_viv_compare_webp_ctx_t *)user_data; + _viv_compare_pane_t *pane = ctx->pane; + HDC screen_hdc; + + if (pane->hbitmap) { - case WM_INITDIALOG: - - os_center_dialog(hwnd); - - SetWindowLongPtr(hwnd,GWLP_USERDATA,lParam); - - os_SetWindowText_localization_id(hwnd,lParam ? LOCALIZATION_ID_EVERYTHING_ADD_EVERYTHING_SEARCH_CAPTION : LOCALIZATION_ID_EVERYTHING_LOAD_EVERYTHING_SEARCH_CAPTION); - os_SetDlgItemText_localization_id(hwnd,IDC_SEARCH_EVERYTHING_RANDOM_CHECKBOX,LOCALIZATION_ID_RANDOMIZE); - - return TRUE; + return 0; + } + + if (ctx->has_alpha) + { + BYTE *p = pixels; + DWORD run = ctx->wide * ctx->high; + while (run--) + { + int a = p[3]; + if (a != 255) + { + int r = p[0], g = p[1], b = p[2]; + p[0] = config_windowed_background_color_r + ((r - config_windowed_background_color_r) * a) / 255; + p[1] = config_windowed_background_color_g + ((g - config_windowed_background_color_g) * a) / 255; + p[2] = config_windowed_background_color_b + ((b - config_windowed_background_color_b) * a) / 255; + p[3] = 255; + } + p += 4; + } + } + + // Convert RGBA to BGR + { + const BYTE *p = pixels; + BYTE *d = pixels; + DWORD high_run = ctx->high; + + while (high_run) + { + BYTE *wd = d; + DWORD wide_run = ctx->wide; + while (wide_run) + { + int r = p[0], g = p[1], b = p[2]; + p += 4; + wd[0] = b; + wd[1] = g; + wd[2] = r; + wd += 3; + wide_run--; + } + d += (((ctx->wide * 3) + 3) / 4) * 4; + high_run--; + } + } + + screen_hdc = GetDC(NULL); + if (screen_hdc) + { + HDC mem_hdc = CreateCompatibleDC(screen_hdc); + if (mem_hdc) + { + HBITMAP hbitmap = _viv_create_bitmap(screen_hdc, ctx->wide, ctx->high); + if (hbitmap) + { + BITMAPINFO bmi; + ZeroMemory(&bmi, sizeof(BITMAPINFO)); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = ctx->wide; + bmi.bmiHeader.biHeight = -(int)ctx->high; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 24; + bmi.bmiHeader.biCompression = BI_RGB; + + SetDIBits(mem_hdc, hbitmap, 0, ctx->high, pixels, &bmi, DIB_RGB_COLORS); + + if (ctx->orientation > 1) + { + HBITMAP oriented = _viv_orientate_hbitmap(hbitmap, ctx->orientation); + if (oriented) + { + DeleteObject(hbitmap); + hbitmap = oriented; + + switch (ctx->orientation) + { + case 5: + case 6: + case 7: + case 8: + { + DWORD tmp = ctx->wide; + ctx->wide = ctx->high; + ctx->high = tmp; + break; + } + } + } + } + + pane->hbitmap = hbitmap; + pane->image_wide = ctx->wide; + pane->image_high = ctx->high; + pane->zoom_percent = 100.0; + pane->is_autofit = 1; + pane->view_x = 0; + pane->view_y = 0; + } + DeleteDC(mem_hdc); + } + ReleaseDC(NULL, screen_hdc); + } + + return 1; +} + +static BOOL _viv_compare_load_image(const wchar_t *filename, _viv_compare_pane_t *pane) +{ + HANDLE h; + DWORD size; + HANDLE global_handle; + char *buf; + IStream *stream = NULL; + void *image = NULL; + int load_ret = 1; + WIN32_FIND_DATA fd; + + _viv_compare_free_pane_image(pane); + + if (!filename || !*filename) + { + pane->filename[0] = 0; + return FALSE; + } + + string_copy(pane->filename, filename); + + if (os_GetFileAttributesExW && os_GetFileAttributesExW(filename, GetFileExInfoStandard, &fd)) + { + pane->filesize = ((VIV_UINT64)fd.nFileSizeHigh << 32) | fd.nFileSizeLow; + } + else + { + pane->filesize = 0; + } + + h = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | ((os_major_version >= 5) ? FILE_SHARE_DELETE : 0), 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0); + if (h == INVALID_HANDLE_VALUE) + { + return FALSE; + } + + size = GetFileSize(h, 0); + if (pane->filesize == 0) + { + pane->filesize = size; + } + + global_handle = GlobalAlloc(GMEM_MOVEABLE, size); + if (!global_handle) + { + CloseHandle(h); + return FALSE; + } + + buf = (char *)GlobalLock(global_handle); + if (buf) + { + DWORD numread; + DWORD totreadsize = size; + char *d = buf; + + while (totreadsize) + { + DWORD readsize = 1 * 1024 * 1024; + if (readsize > totreadsize) readsize = totreadsize; + if (ReadFile(h, d, readsize, &numread, 0)) + { + if (!numread) break; + d += numread; + totreadsize -= numread; + } + else + { + break; + } + } + GlobalUnlock(global_handle); + + if (totreadsize == 0) + { + if (SUCCEEDED(CreateStreamOnHGlobal(global_handle, TRUE, &stream))) + { + global_handle = 0; + } + } + } + + if (global_handle) + { + GlobalFree(global_handle); + } + CloseHandle(h); + + if (!stream) + { + return FALSE; + } + + if (config_orientation) + { + pane->orientation = os_get_orientation(filename); + } + else + { + pane->orientation = 0; + } + + if (os_GdipLoadImageFromStream) + { + if (config_icm && os_GdipLoadImageFromStreamICM) + { + load_ret = os_GdipLoadImageFromStreamICM(stream, &image); + } + else + { + load_ret = os_GdipLoadImageFromStream(stream, &image); + } + } + + if (load_ret == 0 && image) + { + UINT raw_wide = 0, raw_high = 0; + if (os_GdipGetImageWidth(image, &raw_wide) == 0 && os_GdipGetImageHeight(image, &raw_high) == 0) + { + HDC screen_hdc = GetDC(0); + if (screen_hdc) + { + HDC mem_hdc = CreateCompatibleDC(screen_hdc); + if (mem_hdc) + { + HBITMAP hbitmap = _viv_create_bitmap(screen_hdc, raw_wide, raw_high); + if (hbitmap) + { + HGDIOBJ last_hbitmap = SelectObject(mem_hdc, hbitmap); + void *g = NULL; + if (os_GdipCreateFromHDC(mem_hdc, &g) == 0 && g) + { + UINT image_flags = 0; + + if (os_GdipImageSelectActiveFrame) + { + os_GdipImageSelectActiveFrame(image, &_viv_FrameDimensionTime, 0); + } + + if (os_GdipGetImageFlags) + { + os_GdipGetImageFlags(image, &image_flags); + } + + // If has alpha, fill background with window background color and use SourceOver + if (image_flags & 2) + { + RECT rc = {0, 0, (int)raw_wide, (int)raw_high}; + HBRUSH bg_br = CreateSolidBrush(RGB(config_windowed_background_color_r, config_windowed_background_color_g, config_windowed_background_color_b)); + FillRect(mem_hdc, &rc, bg_br); + DeleteObject(bg_br); + os_GdipSetCompositingMode(g, 0); // CompositingModeSourceOver + } + else + { + os_GdipSetCompositingMode(g, 1); // CompositingModeSourceCopy + } + + os_GdipSetCompositingQuality(g, 1); + os_GdipSetInterpolationMode(g, 5); + os_GdipSetPixelOffsetMode(g, 3); + os_GdipSetSmoothingMode(g, 3); + os_GdipDrawImageRectI(g, image, 0, 0, raw_wide, raw_high); + os_GdipDeleteGraphics(g); + + SelectObject(mem_hdc, last_hbitmap); + + if (pane->orientation > 1) + { + HBITMAP oriented = _viv_orientate_hbitmap(hbitmap, pane->orientation); + if (oriented) + { + DeleteObject(hbitmap); + hbitmap = oriented; + + switch (pane->orientation) + { + case 5: + case 6: + case 7: + case 8: + { + UINT tmp = raw_wide; + raw_wide = raw_high; + raw_high = tmp; + break; + } + } + } + } + + pane->hbitmap = hbitmap; + pane->image_wide = raw_wide; + pane->image_high = raw_high; + pane->zoom_percent = 100.0; + pane->is_autofit = 1; + pane->view_x = 0; + pane->view_y = 0; + } + else + { + SelectObject(mem_hdc, last_hbitmap); + DeleteObject(hbitmap); + } + } + DeleteDC(mem_hdc); + } + ReleaseDC(0, screen_hdc); + } + } + os_GdipDisposeImage(image); + } + else + { + // Fallback to WebP loader + _viv_compare_webp_ctx_t ctx; + ctx.pane = pane; + ctx.wide = 0; + ctx.high = 0; + ctx.has_alpha = 0; + ctx.orientation = pane->orientation; + + webp_load(stream, &ctx, _viv_compare_webp_info_proc, _viv_compare_webp_frame_proc); + } + + IStream_Release(stream); + return (pane->hbitmap != NULL); +} + +static void _viv_compare_format_filesize(wchar_t *buf, VIV_UINT64 size) +{ + if (size >= 1024 * 1024 * 1024) + { + double gb = (double)size / (1024.0 * 1024.0 * 1024.0); + string_printf(buf, "%f GB", gb); + } + else if (size >= 1024 * 1024) + { + double mb = (double)size / (1024.0 * 1024.0); + string_printf(buf, "%f MB", mb); + } + else if (size >= 1024) + { + double kb = (double)size / 1024.0; + string_printf(buf, "%f KB", kb); + } + else + { + string_printf(buf, "%u Bytes", (DWORD)size); + } +} + +static void _viv_compare_update_layout(_viv_compare_state_t *cs, int wide, int high) +{ + int top_h = 32; + int toolbar_h = 28; + int pane_w, i; + + if (wide < 100) wide = 100; + if (high < 100) high = 100; + + cs->topbar_rect = (RECT){0, 0, wide, top_h}; + cs->tips_rect = (RECT){10, 6, wide - 420, top_h - 2}; + cs->autofit_rect = (RECT){wide - 410, 6, wide - 330, top_h - 6}; + cs->smooth_rect = (RECT){wide - 320, 6, wide - 240, top_h - 6}; + cs->filename_rect = (RECT){wide - 230, 6, wide - 130, top_h - 6}; + cs->close_btn_rect = (RECT){wide - 110, 4, wide - 10, top_h - 4}; + + pane_w = wide / cs->pane_count; + for (i = 0; i < cs->pane_count; i++) + { + int p_left = i * pane_w; + int p_right = (i == cs->pane_count - 1) ? wide : (p_left + pane_w); + _viv_compare_pane_t *pane = &cs->panes[i]; + + pane->pane_rect = (RECT){p_left, top_h, p_right, high}; + pane->toolbar_rect = (RECT){p_left, top_h, p_right, top_h + toolbar_h}; + pane->viewport_rect = (RECT){p_left, top_h + toolbar_h, p_right, high}; + + int by = top_h + 3; + int bh = toolbar_h - 6; + int bx = p_left + 6; + + pane->btn_prev_rect = (RECT){bx, by, bx + 22, by + bh}; bx += 24; + pane->btn_next_rect = (RECT){bx, by, bx + 22, by + bh}; bx += 28; + pane->btn_zoom_in_rect = (RECT){bx, by, bx + 22, by + bh}; bx += 24; + pane->btn_zoom_out_rect = (RECT){bx, by, bx + 22, by + bh}; bx += 24; + pane->zoom_text_rect = (RECT){bx, by, bx + 55, by + bh}; bx += 59; + pane->btn_1to1_rect = (RECT){bx, by, bx + 32, by + bh}; bx += 36; + pane->btn_fit_rect = (RECT){bx, by, bx + 32, by + bh}; bx += 36; + pane->btn_rotate_rect = (RECT){bx, by, bx + 32, by + bh}; bx += 36; + pane->btn_select_rect = (RECT){p_right - 30, by, p_right - 6, by + bh}; + pane->use_current_rect = (RECT){p_right - 215, by, p_right - 36, by + bh}; + } +} + +static int _viv_compare_hit_test(_viv_compare_state_t *cs, int x, int y, int *out_pane, int *out_btn) +{ + int i; + *out_pane = -1; + *out_btn = _VIV_COMPARE_BTN_NONE; + + if (PtInRect(&cs->close_btn_rect, (POINT){x, y})) return _VIV_COMPARE_TOP_CLOSE; + if (PtInRect(&cs->autofit_rect, (POINT){x, y})) return _VIV_COMPARE_TOP_AUTOFIT; + if (PtInRect(&cs->smooth_rect, (POINT){x, y})) return _VIV_COMPARE_TOP_SMOOTH; + if (PtInRect(&cs->filename_rect, (POINT){x, y})) return _VIV_COMPARE_TOP_FILENAME; + + for (i = 0; i < cs->pane_count; i++) + { + _viv_compare_pane_t *pane = &cs->panes[i]; + if (PtInRect(&pane->pane_rect, (POINT){x, y})) + { + *out_pane = i; + if (PtInRect(&pane->btn_prev_rect, (POINT){x, y})) { *out_btn = _VIV_COMPARE_BTN_PREV; return 0; } + if (PtInRect(&pane->btn_next_rect, (POINT){x, y})) { *out_btn = _VIV_COMPARE_BTN_NEXT; return 0; } + if (PtInRect(&pane->btn_zoom_in_rect, (POINT){x, y})) { *out_btn = _VIV_COMPARE_BTN_ZOOM_IN; return 0; } + if (PtInRect(&pane->btn_zoom_out_rect, (POINT){x, y})) { *out_btn = _VIV_COMPARE_BTN_ZOOM_OUT; return 0; } + if (PtInRect(&pane->zoom_text_rect, (POINT){x, y})) { *out_btn = _VIV_COMPARE_BTN_ZOOM_TEXT; return 0; } + if (PtInRect(&pane->btn_1to1_rect, (POINT){x, y})) { *out_btn = _VIV_COMPARE_BTN_1TO1; return 0; } + if (PtInRect(&pane->btn_fit_rect, (POINT){x, y})) { *out_btn = _VIV_COMPARE_BTN_FIT; return 0; } + if (PtInRect(&pane->btn_rotate_rect, (POINT){x, y})) { *out_btn = _VIV_COMPARE_BTN_ROTATE; return 0; } + if (PtInRect(&pane->use_current_rect, (POINT){x, y})) { *out_btn = _VIV_COMPARE_BTN_USE_CURRENT; return 0; } + if (PtInRect(&pane->btn_select_rect, (POINT){x, y})) { *out_btn = _VIV_COMPARE_BTN_SELECT; return 0; } + return 0; + } + } + return 0; +} + +static void _viv_compare_zoom_pane(_viv_compare_pane_t *pane, int zoom_in, int center_x, int center_y) +{ + int vw, vh, old_rw, old_rh, rx, ry, old_cursor_px, old_cursor_py; + int new_rw, new_rh; + __int64 new_cursor_px, new_cursor_py; + + if (!pane->hbitmap || pane->image_wide <= 0 || pane->image_high <= 0) + return; + + vw = pane->viewport_rect.right - pane->viewport_rect.left; + vh = pane->viewport_rect.bottom - pane->viewport_rect.top; + if (vw <= 0 || vh <= 0) return; + + old_rw = (int)((pane->image_wide * pane->zoom_percent) / 100.0 + 0.5); + old_rh = (int)((pane->image_high * pane->zoom_percent) / 100.0 + 0.5); + if (old_rw < 1) old_rw = 1; + if (old_rh < 1) old_rh = 1; + + rx = pane->viewport_rect.left + (vw / 2) - (old_rw / 2) - pane->view_x; + ry = pane->viewport_rect.top + (vh / 2) - (old_rh / 2) - pane->view_y; + + old_cursor_px = center_x - rx; + old_cursor_py = center_y - ry; + + pane->zoom_percent = _viv_calculate_next_zoom(pane->zoom_percent, zoom_in); + pane->is_autofit = 0; + pane->is_custom_zoom = 1; + pane->is_1to1 = 0; + + new_rw = (int)((pane->image_wide * pane->zoom_percent) / 100.0 + 0.5); + new_rh = (int)((pane->image_high * pane->zoom_percent) / 100.0 + 0.5); + if (new_rw < 1) new_rw = 1; + if (new_rh < 1) new_rh = 1; + + new_cursor_px = old_rw ? ((__int64)old_cursor_px * new_rw) / old_rw : 0; + new_cursor_py = old_rh ? ((__int64)old_cursor_py * new_rh) / old_rh : 0; + + pane->view_x = (vw / 2) - (new_rw / 2) - (center_x - pane->viewport_rect.left) + (int)new_cursor_px; + pane->view_y = (vh / 2) - (new_rh / 2) - (center_y - pane->viewport_rect.top) + (int)new_cursor_py; +} + +static void _viv_compare_pick_image_for_pane(HWND hwnd, _viv_compare_pane_t *pane) +{ + OPENFILENAMEW ofn; + wchar_t filename[STRING_SIZE]; + wchar_t filter[STRING_SIZE]; + int i; + + filename[0] = 0; + ZeroMemory(&ofn, sizeof(OPENFILENAMEW)); + ofn.lStructSize = sizeof(OPENFILENAMEW); + ofn.hwndOwner = hwnd; + + string_copy_utf8_string(filter, localization_get_string(LOCALIZATION_ID_OPEN_ALL_IMAGE_FILES)); + i = string_get_length(filter); + filter[i++] = 0; + string_copy(filter + i, L"*.bmp;*.gif;*.ico;*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.webp"); + i += string_get_length(filter + i); + filter[i++] = 0; + string_copy_utf8_string(filter + i, localization_get_string(LOCALIZATION_ID_OPEN_ALL_FILES)); + i += string_get_length(filter + i); + filter[i++] = 0; + string_copy(filter + i, L"*.*"); + i += string_get_length(filter + i); + filter[i++] = 0; + filter[i++] = 0; + + ofn.lpstrFilter = filter; + ofn.lpstrFile = filename; + ofn.nMaxFile = STRING_SIZE; + ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; + + if (GetOpenFileNameW(&ofn)) + { + pane->use_current = 0; + _viv_compare_load_image(filename, pane); + pane->playlist_item = NULL; + InvalidateRect(hwnd, NULL, FALSE); + } +} + +static void _viv_compare_draw_button(HDC hdc, const RECT *rect, const wchar_t *text, int is_hovered, HFONT font) +{ + HGDIOBJ old_font = SelectObject(hdc, font); + HBRUSH br = CreateSolidBrush(is_hovered ? RGB(215, 230, 250) : RGB(242, 242, 242)); + HPEN pen = CreatePen(PS_SOLID, 1, is_hovered ? RGB(100, 150, 220) : RGB(190, 190, 190)); + HGDIOBJ old_br = SelectObject(hdc, br); + HGDIOBJ old_pen = SelectObject(hdc, pen); + + RoundRect(hdc, rect->left, rect->top, rect->right, rect->bottom, 4, 4); + + SetBkMode(hdc, TRANSPARENT); + SetTextColor(hdc, RGB(20, 20, 20)); + DrawTextW(hdc, text, -1, (LPRECT)rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + + SelectObject(hdc, old_pen); + SelectObject(hdc, old_br); + SelectObject(hdc, old_font); + DeleteObject(pen); + DeleteObject(br); +} + +static void _viv_compare_draw_checkbox(HDC hdc, const RECT *rect, const wchar_t *text, int is_checked, int is_hovered, HFONT font) +{ + HGDIOBJ old_font = SelectObject(hdc, font); + RECT box_rect = {rect->left, rect->top + (rect->bottom - rect->top - 14) / 2, rect->left + 14, rect->top + (rect->bottom - rect->top - 14) / 2 + 14}; + RECT text_rect = {rect->left + 18, rect->top, rect->right, rect->bottom}; + + HBRUSH br = CreateSolidBrush(RGB(255, 255, 255)); + HPEN pen = CreatePen(PS_SOLID, 1, is_hovered ? RGB(50, 120, 220) : RGB(140, 140, 140)); + HGDIOBJ old_br = SelectObject(hdc, br); + HGDIOBJ old_pen = SelectObject(hdc, pen); + + Rectangle(hdc, box_rect.left, box_rect.top, box_rect.right, box_rect.bottom); + + if (is_checked) + { + HPEN check_pen = CreatePen(PS_SOLID, 2, RGB(20, 120, 20)); + HGDIOBJ old_cpen = SelectObject(hdc, check_pen); + MoveToEx(hdc, box_rect.left + 3, box_rect.top + 7, NULL); + LineTo(hdc, box_rect.left + 6, box_rect.top + 10); + LineTo(hdc, box_rect.left + 11, box_rect.top + 3); + SelectObject(hdc, old_cpen); + DeleteObject(check_pen); + } + + SetBkMode(hdc, TRANSPARENT); + SetTextColor(hdc, RGB(30, 30, 30)); + DrawTextW(hdc, text, -1, &text_rect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + + SelectObject(hdc, old_pen); + SelectObject(hdc, old_br); + SelectObject(hdc, old_font); + DeleteObject(pen); + DeleteObject(br); +} + +static void _viv_compare_draw_checkbox_ex(HDC hdc, const RECT *rect, const wchar_t *text, int is_checked, int is_hovered, int is_disabled, HFONT font) +{ + HGDIOBJ old_font = SelectObject(hdc, font); + RECT box_rect = {rect->left, rect->top + (rect->bottom - rect->top - 14) / 2, rect->left + 14, rect->top + (rect->bottom - rect->top - 14) / 2 + 14}; + RECT text_rect = {rect->left + 18, rect->top, rect->right, rect->bottom}; + COLORREF box_bg = is_disabled ? RGB(230, 230, 230) : RGB(255, 255, 255); + COLORREF border_col = is_disabled ? RGB(180, 180, 180) : (is_hovered ? RGB(50, 120, 220) : RGB(140, 140, 140)); + COLORREF text_col = is_disabled ? RGB(150, 150, 150) : RGB(30, 30, 30); + + HBRUSH br = CreateSolidBrush(box_bg); + HPEN pen = CreatePen(PS_SOLID, 1, border_col); + HGDIOBJ old_br = SelectObject(hdc, br); + HGDIOBJ old_pen = SelectObject(hdc, pen); + + Rectangle(hdc, box_rect.left, box_rect.top, box_rect.right, box_rect.bottom); + + if (is_checked && !is_disabled) + { + HPEN check_pen = CreatePen(PS_SOLID, 2, RGB(20, 120, 20)); + HGDIOBJ old_cpen = SelectObject(hdc, check_pen); + MoveToEx(hdc, box_rect.left + 3, box_rect.top + 7, NULL); + LineTo(hdc, box_rect.left + 6, box_rect.top + 10); + LineTo(hdc, box_rect.left + 11, box_rect.top + 3); + SelectObject(hdc, old_cpen); + DeleteObject(check_pen); + } + + SetBkMode(hdc, TRANSPARENT); + SetTextColor(hdc, text_col); + DrawTextW(hdc, text, -1, &text_rect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + + SelectObject(hdc, old_pen); + SelectObject(hdc, old_br); + SelectObject(hdc, old_font); + DeleteObject(pen); + DeleteObject(br); +} + +static void _viv_compare_paint(HWND hwnd, _viv_compare_state_t *cs) +{ + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hwnd, &ps); + RECT rc; + int wide, high, i; + HDC mem_hdc; + HBITMAP mem_hbitmap; + HGDIOBJ old_mem_bitmap; + + GetClientRect(hwnd, &rc); + wide = rc.right - rc.left; + high = rc.bottom - rc.top; + + if (wide <= 0 || high <= 0) + { + EndPaint(hwnd, &ps); + return; + } + + mem_hdc = CreateCompatibleDC(hdc); + mem_hbitmap = CreateCompatibleBitmap(hdc, wide, high); + old_mem_bitmap = SelectObject(mem_hdc, mem_hbitmap); + + _viv_compare_update_layout(cs, wide, high); + + // 1. Draw Top Bar Background + { + HBRUSH top_br = CreateSolidBrush(RGB(242, 242, 245)); + HPEN top_pen = CreatePen(PS_SOLID, 1, RGB(185, 185, 195)); + HGDIOBJ old_br = SelectObject(mem_hdc, top_br); + HGDIOBJ old_pen = SelectObject(mem_hdc, top_pen); + + FillRect(mem_hdc, &cs->topbar_rect, top_br); + MoveToEx(mem_hdc, 0, cs->topbar_rect.bottom - 1, NULL); + LineTo(mem_hdc, wide, cs->topbar_rect.bottom - 1); + + SelectObject(mem_hdc, old_pen); + SelectObject(mem_hdc, old_br); + DeleteObject(top_pen); + DeleteObject(top_br); + + // Tips Text + { + wchar_t tips_wbuf[STRING_SIZE]; + string_copy_utf8_string(tips_wbuf, localization_get_string(LOCALIZATION_ID_COMPARE_TIPS)); + HGDIOBJ old_font = SelectObject(mem_hdc, cs->font_ui); + SetBkMode(mem_hdc, TRANSPARENT); + SetTextColor(mem_hdc, RGB(60, 60, 60)); + DrawTextW(mem_hdc, tips_wbuf, -1, &cs->tips_rect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + SelectObject(mem_hdc, old_font); + } + + // Checkboxes + { + wchar_t label[STRING_SIZE]; + string_copy_utf8_string(label, localization_get_string(LOCALIZATION_ID_COMPARE_AUTO_FIT)); + _viv_compare_draw_checkbox(mem_hdc, &cs->autofit_rect, label, cs->opt_autofit, cs->hovered_top == _VIV_COMPARE_TOP_AUTOFIT, cs->font_ui); + + string_copy_utf8_string(label, localization_get_string(LOCALIZATION_ID_COMPARE_SMOOTH)); + _viv_compare_draw_checkbox(mem_hdc, &cs->smooth_rect, label, cs->opt_smooth, cs->hovered_top == _VIV_COMPARE_TOP_SMOOTH, cs->font_ui); + + string_copy_utf8_string(label, localization_get_string(LOCALIZATION_ID_COMPARE_FILENAME)); + _viv_compare_draw_checkbox(mem_hdc, &cs->filename_rect, label, cs->opt_filename, cs->hovered_top == _VIV_COMPARE_TOP_FILENAME, cs->font_ui); + } + + // Close Button + { + wchar_t close_label[STRING_SIZE]; + string_copy_utf8_string(close_label, localization_get_string(LOCALIZATION_ID_COMPARE_CLOSE)); + _viv_compare_draw_button(mem_hdc, &cs->close_btn_rect, close_label, cs->hovered_top == _VIV_COMPARE_TOP_CLOSE, cs->font_bold); + } + } + + // 2. Draw Panes + for (i = 0; i < cs->pane_count; i++) + { + _viv_compare_pane_t *pane = &cs->panes[i]; + int vw = pane->viewport_rect.right - pane->viewport_rect.left; + int vh = pane->viewport_rect.bottom - pane->viewport_rect.top; + + // Toolbar + { + HBRUSH tb_br = CreateSolidBrush(RGB(236, 238, 242)); + HPEN tb_pen = CreatePen(PS_SOLID, 1, RGB(195, 198, 205)); + HGDIOBJ old_br = SelectObject(mem_hdc, tb_br); + HGDIOBJ old_pen = SelectObject(mem_hdc, tb_pen); + + FillRect(mem_hdc, &pane->toolbar_rect, tb_br); + MoveToEx(mem_hdc, pane->toolbar_rect.left, pane->toolbar_rect.bottom - 1, NULL); + LineTo(mem_hdc, pane->toolbar_rect.right, pane->toolbar_rect.bottom - 1); + + SelectObject(mem_hdc, old_pen); + SelectObject(mem_hdc, old_br); + DeleteObject(tb_pen); + DeleteObject(tb_br); + + // Buttons + _viv_compare_draw_button(mem_hdc, &pane->btn_prev_rect, L"<", (cs->hovered_pane == i && cs->hovered_btn == _VIV_COMPARE_BTN_PREV), cs->font_bold); + _viv_compare_draw_button(mem_hdc, &pane->btn_next_rect, L">", (cs->hovered_pane == i && cs->hovered_btn == _VIV_COMPARE_BTN_NEXT), cs->font_bold); + _viv_compare_draw_button(mem_hdc, &pane->btn_zoom_in_rect, L"+", (cs->hovered_pane == i && cs->hovered_btn == _VIV_COMPARE_BTN_ZOOM_IN), cs->font_bold); + _viv_compare_draw_button(mem_hdc, &pane->btn_zoom_out_rect, L"-", (cs->hovered_pane == i && cs->hovered_btn == _VIV_COMPARE_BTN_ZOOM_OUT), cs->font_bold); + + // Zoom text + { + wchar_t ztext[64]; + string_printf(ztext, "%d%%", (int)(pane->zoom_percent + 0.5)); + _viv_compare_draw_button(mem_hdc, &pane->zoom_text_rect, ztext, (cs->hovered_pane == i && cs->hovered_btn == _VIV_COMPARE_BTN_ZOOM_TEXT), cs->font_ui); + } + + _viv_compare_draw_button(mem_hdc, &pane->btn_1to1_rect, L"1:1", (cs->hovered_pane == i && cs->hovered_btn == _VIV_COMPARE_BTN_1TO1), cs->font_ui); + _viv_compare_draw_button(mem_hdc, &pane->btn_fit_rect, L"Fit", (cs->hovered_pane == i && cs->hovered_btn == _VIV_COMPARE_BTN_FIT), cs->font_ui); + _viv_compare_draw_button(mem_hdc, &pane->btn_rotate_rect, L"90°", (cs->hovered_pane == i && cs->hovered_btn == _VIV_COMPARE_BTN_ROTATE), cs->font_ui); + + // Use Current Image checkbox — greyed out if another pane already uses current + { + int other_uses_current = 0; + int k; + wchar_t uc_label[STRING_SIZE]; + for (k = 0; k < cs->pane_count; k++) + { + if (k != i && cs->panes[k].use_current) + { + other_uses_current = 1; + break; + } + } + string_copy_utf8_string(uc_label, localization_get_string(LOCALIZATION_ID_COMPARE_USE_CURRENT)); + _viv_compare_draw_checkbox_ex(mem_hdc, &pane->use_current_rect, uc_label, + pane->use_current, + (!other_uses_current) && (cs->hovered_pane == i && cs->hovered_btn == _VIV_COMPARE_BTN_USE_CURRENT), + other_uses_current, /* disabled */ + cs->font_ui); + } + + _viv_compare_draw_button(mem_hdc, &pane->btn_select_rect, L"...", (cs->hovered_pane == i && cs->hovered_btn == _VIV_COMPARE_BTN_SELECT), cs->font_bold); + } + + // Viewport + { + HBRUSH vp_br = CreateSolidBrush(RGB(75, 75, 80)); + FillRect(mem_hdc, &pane->viewport_rect, vp_br); + DeleteObject(vp_br); + + if (pane->hbitmap && pane->image_wide > 0 && pane->image_high > 0) + { + int rw, rh, rx, ry; + HRGN clip_rgn; + + if (pane->is_autofit && vw > 0 && vh > 0) + { + double sx = (double)vw / (double)pane->image_wide; + double sy = (double)vh / (double)pane->image_high; + double sc = sx < sy ? sx : sy; + if (sc > 1.0) sc = 1.0; + pane->zoom_percent = sc * 100.0; + } + + rw = (int)((pane->image_wide * pane->zoom_percent) / 100.0 + 0.5); + rh = (int)((pane->image_high * pane->zoom_percent) / 100.0 + 0.5); + if (rw < 1) rw = 1; + if (rh < 1) rh = 1; + + rx = pane->viewport_rect.left + (vw / 2) - (rw / 2) - pane->view_x; + ry = pane->viewport_rect.top + (vh / 2) - (rh / 2) - pane->view_y; + + clip_rgn = CreateRectRgn(pane->viewport_rect.left, pane->viewport_rect.top, pane->viewport_rect.right, pane->viewport_rect.bottom); + SelectClipRgn(mem_hdc, clip_rgn); + + { + HDC img_dc = CreateCompatibleDC(mem_hdc); + int mip_w, mip_h; + HBITMAP mip = _viv_get_mipmap(pane->hbitmap, pane->image_wide, pane->image_high, rw, rh, &mip_w, &mip_h, &pane->mipmap); + if (mip) + { + HGDIOBJ old_img = SelectObject(img_dc, mip); + int last_mode = 0; + if (cs->opt_smooth) + { + last_mode = SetStretchBltMode(mem_hdc, HALFTONE); + SetBrushOrgEx(mem_hdc, -rx, -ry, NULL); + } + else + { + last_mode = SetStretchBltMode(mem_hdc, COLORONCOLOR); + } + + if (rw == mip_w && rh == mip_h) + { + BitBlt(mem_hdc, rx, ry, rw, rh, img_dc, 0, 0, SRCCOPY); + } + else + { + StretchBlt(mem_hdc, rx, ry, rw, rh, img_dc, 0, 0, mip_w, mip_h, SRCCOPY); + } + + SetStretchBltMode(mem_hdc, last_mode); + SelectObject(img_dc, old_img); + } + DeleteDC(img_dc); + } + + // Green Text Overlay + if (cs->opt_filename && *pane->filename) + { + const wchar_t *filename_part; + wchar_t size_part[64]; + wchar_t overlay_str[STRING_SIZE * 2]; + RECT text_rc; + + filename_part = string_get_filename_part(pane->filename); + _viv_compare_format_filesize(size_part, pane->filesize); + string_printf(overlay_str, "%S , %d x %d , %S", filename_part, pane->image_wide, pane->image_high, size_part); + + text_rc = (RECT){pane->viewport_rect.left + 10, pane->viewport_rect.top + 10, pane->viewport_rect.right - 10, pane->viewport_rect.top + 40}; + + HGDIOBJ old_font = SelectObject(mem_hdc, cs->font_overlay); + SetBkMode(mem_hdc, TRANSPARENT); + + // Text Shadow + SetTextColor(mem_hdc, RGB(0, 0, 0)); + OffsetRect(&text_rc, 1, 1); + DrawTextW(mem_hdc, overlay_str, -1, &text_rc, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_END_ELLIPSIS); + + // Green Text + SetTextColor(mem_hdc, RGB(0, 235, 0)); + OffsetRect(&text_rc, -1, -1); + DrawTextW(mem_hdc, overlay_str, -1, &text_rc, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_END_ELLIPSIS); + + SelectObject(mem_hdc, old_font); + } + + SelectClipRgn(mem_hdc, NULL); + DeleteObject(clip_rgn); + } + else + { + wchar_t no_img_str[STRING_SIZE]; + HGDIOBJ old_font = SelectObject(mem_hdc, cs->font_ui); + RECT center_rc = pane->viewport_rect; + string_copy_utf8_string(no_img_str, localization_get_string(LOCALIZATION_ID_COMPARE_NO_IMAGE_DROP)); + SetBkMode(mem_hdc, TRANSPARENT); + SetTextColor(mem_hdc, RGB(180, 180, 180)); + DrawTextW(mem_hdc, no_img_str, -1, ¢er_rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + SelectObject(mem_hdc, old_font); + } + } + + // Splitter line between panes + if (i < cs->pane_count - 1) + { + HPEN split_pen = CreatePen(PS_SOLID, 1, RGB(40, 40, 45)); + HGDIOBJ old_spen = SelectObject(mem_hdc, split_pen); + MoveToEx(mem_hdc, pane->pane_rect.right, cs->topbar_rect.bottom, NULL); + LineTo(mem_hdc, pane->pane_rect.right, high); + SelectObject(mem_hdc, old_spen); + DeleteObject(split_pen); + } + } + + BitBlt(hdc, 0, 0, wide, high, mem_hdc, 0, 0, SRCCOPY); + + SelectObject(mem_hdc, old_mem_bitmap); + DeleteObject(mem_hbitmap); + DeleteDC(mem_hdc); + + EndPaint(hwnd, &ps); +} + +static LRESULT CALLBACK _viv_compare_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + _viv_compare_state_t *cs = _viv_compare_state; + + switch (msg) + { + case WM_CREATE: + DragAcceptFiles(hwnd, TRUE); + return 0; + + case WM_ERASEBKGND: + return 1; + + case WM_PAINT: + if (cs) + { + _viv_compare_paint(hwnd, cs); + } + return 0; + + case WM_DROPFILES: + if (cs) + { + HDROP hDrop = (HDROP)wParam; + UINT count = DragQueryFileW(hDrop, 0xFFFFFFFF, NULL, 0); + POINT pt; + int target_pane = 0; + + if (DragQueryPoint(hDrop, &pt)) + { + int i; + for (i = 0; i < cs->pane_count; i++) + { + if (PtInRect(&cs->panes[i].pane_rect, pt)) + { + target_pane = i; + break; + } + } + } + + if (count >= 2) + { + wchar_t filename0[STRING_SIZE]; + wchar_t filename1[STRING_SIZE]; + + DragQueryFileW(hDrop, 0, filename0, STRING_SIZE); + DragQueryFileW(hDrop, 1, filename1, STRING_SIZE); + + cs->panes[0].use_current = 0; + cs->panes[0].playlist_item = NULL; + _viv_compare_load_image(filename0, &cs->panes[0]); + + cs->panes[1].use_current = 0; + cs->panes[1].playlist_item = NULL; + _viv_compare_load_image(filename1, &cs->panes[1]); + + InvalidateRect(hwnd, NULL, FALSE); + } + else if (count == 1) + { + wchar_t filename[STRING_SIZE]; + DragQueryFileW(hDrop, 0, filename, STRING_SIZE); + + if (target_pane >= 0 && target_pane < cs->pane_count) + { + int other_pane = (target_pane == 0) ? 1 : 0; + + // Reset use_current on target pane + cs->panes[target_pane].use_current = 0; + cs->panes[target_pane].playlist_item = NULL; + + // If the other pane was using current image and this dropped file is that same file, + // reset use_current on the other pane so they don't conflict + if (other_pane >= 0 && other_pane < cs->pane_count) + { + if (cs->panes[other_pane].use_current && *_viv_current_fd->cFileName) + { + if (lstrcmpiW(filename, _viv_current_fd->cFileName) == 0) + { + cs->panes[other_pane].use_current = 0; + } + } + } + + _viv_compare_load_image(filename, &cs->panes[target_pane]); + InvalidateRect(hwnd, NULL, FALSE); + } + } + + DragFinish(hDrop); + } + return 0; + + case WM_SIZE: + if (cs) + { + int wide = LOWORD(lParam); + int high = HIWORD(lParam); + _viv_compare_update_layout(cs, wide, high); + InvalidateRect(hwnd, NULL, FALSE); + } + return 0; + + case WM_MOUSEMOVE: + if (cs) + { + int x = GET_X_LPARAM(lParam); + int y = GET_Y_LPARAM(lParam); + + if (cs->is_dragging) + { + int dx = x - cs->drag_last_x; + int dy = y - cs->drag_last_y; + cs->drag_last_x = x; + cs->drag_last_y = y; + + if (cs->drag_is_individual && cs->drag_pane_index >= 0 && cs->drag_pane_index < cs->pane_count) + { + cs->panes[cs->drag_pane_index].view_x -= dx; + cs->panes[cs->drag_pane_index].view_y -= dy; + } + else + { + int i; + for (i = 0; i < cs->pane_count; i++) + { + cs->panes[i].view_x -= dx; + cs->panes[i].view_y -= dy; + } + } + InvalidateRect(hwnd, NULL, FALSE); + } + else + { + int pane_idx, btn_idx; + int top_res = _viv_compare_hit_test(cs, x, y, &pane_idx, &btn_idx); + + if (top_res != cs->hovered_top || pane_idx != cs->hovered_pane || btn_idx != cs->hovered_btn) + { + cs->hovered_top = top_res; + cs->hovered_pane = pane_idx; + cs->hovered_btn = btn_idx; + InvalidateRect(hwnd, NULL, FALSE); + } + } + } + return 0; + + case WM_LBUTTONDOWN: + if (cs) + { + int x = GET_X_LPARAM(lParam); + int y = GET_Y_LPARAM(lParam); + int pane_idx, btn_idx; + int top_res = _viv_compare_hit_test(cs, x, y, &pane_idx, &btn_idx); + + if (top_res == _VIV_COMPARE_TOP_CLOSE) + { + DestroyWindow(hwnd); + return 0; + } + else if (top_res == _VIV_COMPARE_TOP_AUTOFIT) + { + int i; + cs->opt_autofit = !cs->opt_autofit; + for (i = 0; i < cs->pane_count; i++) + { + cs->panes[i].is_autofit = cs->opt_autofit; + cs->panes[i].view_x = 0; + cs->panes[i].view_y = 0; + } + InvalidateRect(hwnd, NULL, FALSE); + return 0; + } + else if (top_res == _VIV_COMPARE_TOP_SMOOTH) + { + cs->opt_smooth = !cs->opt_smooth; + InvalidateRect(hwnd, NULL, FALSE); + return 0; + } + else if (top_res == _VIV_COMPARE_TOP_FILENAME) + { + cs->opt_filename = !cs->opt_filename; + InvalidateRect(hwnd, NULL, FALSE); + return 0; + } + + if (pane_idx >= 0 && pane_idx < cs->pane_count) + { + _viv_compare_pane_t *p = &cs->panes[pane_idx]; + if (btn_idx == _VIV_COMPARE_BTN_PREV) + { + if (p->playlist_item && p->playlist_item->prev) + { + p->playlist_item = p->playlist_item->prev; + _viv_compare_load_image(p->playlist_item->fd.cFileName, p); + InvalidateRect(hwnd, NULL, FALSE); + } + return 0; + } + else if (btn_idx == _VIV_COMPARE_BTN_NEXT) + { + if (p->playlist_item && p->playlist_item->next) + { + p->playlist_item = p->playlist_item->next; + _viv_compare_load_image(p->playlist_item->fd.cFileName, p); + InvalidateRect(hwnd, NULL, FALSE); + } + return 0; + } + else if (btn_idx == _VIV_COMPARE_BTN_ZOOM_IN) + { + int is_indiv = (GetKeyState(VK_CONTROL) < 0); + if (is_indiv) + { + int cx = (p->viewport_rect.left + p->viewport_rect.right) / 2; + int cy = (p->viewport_rect.top + p->viewport_rect.bottom) / 2; + _viv_compare_zoom_pane(p, 1, cx, cy); + } + else + { + int k; + for (k = 0; k < cs->pane_count; k++) + { + int cx = (cs->panes[k].viewport_rect.left + cs->panes[k].viewport_rect.right) / 2; + int cy = (cs->panes[k].viewport_rect.top + cs->panes[k].viewport_rect.bottom) / 2; + _viv_compare_zoom_pane(&cs->panes[k], 1, cx, cy); + } + } + InvalidateRect(hwnd, NULL, FALSE); + return 0; + } + else if (btn_idx == _VIV_COMPARE_BTN_ZOOM_OUT) + { + int is_indiv = (GetKeyState(VK_CONTROL) < 0); + if (is_indiv) + { + int cx = (p->viewport_rect.left + p->viewport_rect.right) / 2; + int cy = (p->viewport_rect.top + p->viewport_rect.bottom) / 2; + _viv_compare_zoom_pane(p, 0, cx, cy); + } + else + { + int k; + for (k = 0; k < cs->pane_count; k++) + { + int cx = (cs->panes[k].viewport_rect.left + cs->panes[k].viewport_rect.right) / 2; + int cy = (cs->panes[k].viewport_rect.top + cs->panes[k].viewport_rect.bottom) / 2; + _viv_compare_zoom_pane(&cs->panes[k], 0, cx, cy); + } + } + InvalidateRect(hwnd, NULL, FALSE); + return 0; + } + else if (btn_idx == _VIV_COMPARE_BTN_1TO1) + { + int is_indiv = (GetKeyState(VK_CONTROL) < 0); + if (is_indiv) + { + p->zoom_percent = 100.0; + p->is_1to1 = 1; + p->is_autofit = 0; + p->view_x = 0; + p->view_y = 0; + } + else + { + int k; + for (k = 0; k < cs->pane_count; k++) + { + cs->panes[k].zoom_percent = 100.0; + cs->panes[k].is_1to1 = 1; + cs->panes[k].is_autofit = 0; + cs->panes[k].view_x = 0; + cs->panes[k].view_y = 0; + } + } + InvalidateRect(hwnd, NULL, FALSE); + return 0; + } + else if (btn_idx == _VIV_COMPARE_BTN_FIT) + { + int is_indiv = (GetKeyState(VK_CONTROL) < 0); + if (is_indiv) + { + p->is_autofit = 1; + p->view_x = 0; + p->view_y = 0; + } + else + { + int k; + for (k = 0; k < cs->pane_count; k++) + { + cs->panes[k].is_autofit = 1; + cs->panes[k].view_x = 0; + cs->panes[k].view_y = 0; + } + } + InvalidateRect(hwnd, NULL, FALSE); + return 0; + } + else if (btn_idx == _VIV_COMPARE_BTN_ROTATE) + { + if (p->hbitmap) + { + HBITMAP rot = _viv_orientate_hbitmap(p->hbitmap, 6); // 90 deg cw + if (rot) + { + int tmp; + DeleteObject(p->hbitmap); + p->hbitmap = rot; + if (p->mipmap) + { + _viv_mipmap_free(p->mipmap); + p->mipmap = NULL; + } + tmp = p->image_wide; + p->image_wide = p->image_high; + p->image_high = tmp; + InvalidateRect(hwnd, NULL, FALSE); + } + } + return 0; + } + else if (btn_idx == _VIV_COMPARE_BTN_USE_CURRENT) + { + // Only allow if no other pane already uses current + int other_uses = 0; + int k; + for (k = 0; k < cs->pane_count; k++) + { + if (k != pane_idx && cs->panes[k].use_current) + { + other_uses = 1; + break; + } + } + if (!other_uses) + { + p->use_current = !p->use_current; + if (p->use_current && *_viv_current_fd->cFileName) + { + _viv_playlist_t *cur_item = _viv_playlist_from_fd(_viv_current_fd); + p->playlist_item = cur_item; + _viv_compare_load_image(_viv_current_fd->cFileName, p); + } + else if (!p->use_current) + { + _viv_compare_free_pane_image(p); + p->playlist_item = NULL; + p->filename[0] = 0; + } + InvalidateRect(hwnd, NULL, FALSE); + } + return 0; + } + else if (btn_idx == _VIV_COMPARE_BTN_SELECT) + { + _viv_compare_pick_image_for_pane(hwnd, p); + return 0; + } + + // Click inside viewport: Start Dragging + if (PtInRect(&p->viewport_rect, (POINT){x, y})) + { + cs->is_dragging = 1; + cs->drag_last_x = x; + cs->drag_last_y = y; + cs->drag_pane_index = pane_idx; + cs->drag_is_individual = (GetKeyState(VK_CONTROL) < 0); + SetCapture(hwnd); + } + } + } + return 0; + + case WM_LBUTTONUP: + case WM_RBUTTONUP: + if (cs && cs->is_dragging) + { + cs->is_dragging = 0; + ReleaseCapture(); + } + return 0; + + case WM_RBUTTONDOWN: + if (cs) + { + int x = GET_X_LPARAM(lParam); + int y = GET_Y_LPARAM(lParam); + int pane_idx, btn_idx; + _viv_compare_hit_test(cs, x, y, &pane_idx, &btn_idx); + + if (pane_idx >= 0 && pane_idx < cs->pane_count) + { + if (PtInRect(&cs->panes[pane_idx].viewport_rect, (POINT){x, y})) + { + cs->is_dragging = 1; + cs->drag_last_x = x; + cs->drag_last_y = y; + cs->drag_pane_index = pane_idx; + cs->drag_is_individual = 1; // Right-drag is always individual + SetCapture(hwnd); + } + } + } + return 0; + + case WM_MOUSEWHEEL: + if (cs) + { + POINT pt; + int delta = GET_WHEEL_DELTA_WPARAM(wParam); + int zoom_in = (delta > 0); + int is_indiv; + int pane_idx, btn_idx; + + pt.x = GET_X_LPARAM(lParam); + pt.y = GET_Y_LPARAM(lParam); + ScreenToClient(hwnd, &pt); + + _viv_compare_hit_test(cs, pt.x, pt.y, &pane_idx, &btn_idx); + is_indiv = (GetKeyState(VK_CONTROL) < 0) || (GetKeyState(VK_RBUTTON) < 0); + + if (is_indiv && pane_idx >= 0 && pane_idx < cs->pane_count) + { + _viv_compare_zoom_pane(&cs->panes[pane_idx], zoom_in, pt.x, pt.y); + } + else + { + int k; + for (k = 0; k < cs->pane_count; k++) + { + int cx = (cs->panes[k].viewport_rect.left + cs->panes[k].viewport_rect.right) / 2; + int cy = (cs->panes[k].viewport_rect.top + cs->panes[k].viewport_rect.bottom) / 2; + _viv_compare_zoom_pane(&cs->panes[k], zoom_in, cx, cy); + } + } + InvalidateRect(hwnd, NULL, FALSE); + } + return 0; + + case WM_KEYDOWN: + if (cs) + { + switch (wParam) + { + case VK_ESCAPE: + DestroyWindow(hwnd); + return 0; + + case VK_LEFT: + case VK_PRIOR: + { + int idx = (cs->hovered_pane >= 0 && cs->hovered_pane < cs->pane_count) ? cs->hovered_pane : 0; + _viv_compare_pane_t *p = &cs->panes[idx]; + if (p->playlist_item && p->playlist_item->prev) + { + p->playlist_item = p->playlist_item->prev; + _viv_compare_load_image(p->playlist_item->fd.cFileName, p); + InvalidateRect(hwnd, NULL, FALSE); + } + return 0; + } + + case VK_RIGHT: + case VK_NEXT: + case VK_SPACE: + { + int idx = (cs->hovered_pane >= 0 && cs->hovered_pane < cs->pane_count) ? cs->hovered_pane : 0; + _viv_compare_pane_t *p = &cs->panes[idx]; + if (p->playlist_item && p->playlist_item->next) + { + p->playlist_item = p->playlist_item->next; + _viv_compare_load_image(p->playlist_item->fd.cFileName, p); + InvalidateRect(hwnd, NULL, FALSE); + } + return 0; + } + + case VK_ADD: + case VK_OEM_PLUS: + { + int k; + for (k = 0; k < cs->pane_count; k++) + { + int cx = (cs->panes[k].viewport_rect.left + cs->panes[k].viewport_rect.right) / 2; + int cy = (cs->panes[k].viewport_rect.top + cs->panes[k].viewport_rect.bottom) / 2; + _viv_compare_zoom_pane(&cs->panes[k], 1, cx, cy); + } + InvalidateRect(hwnd, NULL, FALSE); + return 0; + } + + case VK_SUBTRACT: + case VK_OEM_MINUS: + { + int k; + for (k = 0; k < cs->pane_count; k++) + { + int cx = (cs->panes[k].viewport_rect.left + cs->panes[k].viewport_rect.right) / 2; + int cy = (cs->panes[k].viewport_rect.top + cs->panes[k].viewport_rect.bottom) / 2; + _viv_compare_zoom_pane(&cs->panes[k], 0, cx, cy); + } + InvalidateRect(hwnd, NULL, FALSE); + return 0; + } + + case '1': + { + int k; + for (k = 0; k < cs->pane_count; k++) + { + cs->panes[k].zoom_percent = 100.0; + cs->panes[k].is_1to1 = 1; + cs->panes[k].is_autofit = 0; + cs->panes[k].view_x = 0; + cs->panes[k].view_y = 0; + } + InvalidateRect(hwnd, NULL, FALSE); + return 0; + } + + case 'F': + { + int k; + for (k = 0; k < cs->pane_count; k++) + { + cs->panes[k].is_autofit = 1; + cs->panes[k].view_x = 0; + cs->panes[k].view_y = 0; + } + InvalidateRect(hwnd, NULL, FALSE); + return 0; + } + } + } + break; + + case WM_DESTROY: + if (cs) + { + int i; + for (i = 0; i < cs->pane_count; i++) + { + _viv_compare_free_pane_image(&cs->panes[i]); + } + if (cs->font_ui) DeleteObject(cs->font_ui); + if (cs->font_bold) DeleteObject(cs->font_bold); + if (cs->font_overlay) DeleteObject(cs->font_overlay); + + mem_free(cs); + _viv_compare_state = NULL; + } + EnableWindow(_viv_hwnd, TRUE); + SetForegroundWindow(_viv_hwnd); + return 0; + } + + return DefWindowProcW(hwnd, msg, wParam, lParam); +} + +static void _viv_compare_show(void) +{ + _viv_compare_state_t *cs; + HWND hwnd; + RECT rc; + static int class_registered = 0; + + if (_viv_compare_state && _viv_compare_state->hwnd) + { + SetForegroundWindow(_viv_compare_state->hwnd); + return; + } + + if (!class_registered) + { + os_RegisterClassEx( + CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW, + _viv_compare_proc, + (HICON)LoadImage(os_hinstance, MAKEINTRESOURCE(IDI_ICON1), IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), 0), + LoadCursor(NULL, IDC_ARROW), + (HBRUSH)(COLOR_BTNFACE + 1), + "_VIV_COMPARE", + (HICON)LoadImage(os_hinstance, MAKEINTRESOURCE(IDI_ICON1), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0)); + class_registered = 1; + } + + cs = (_viv_compare_state_t *)mem_alloc(sizeof(_viv_compare_state_t)); + ZeroMemory(cs, sizeof(_viv_compare_state_t)); + _viv_compare_state = cs; + + cs->pane_count = _VIV_COMPARE_PANE_COUNT; + cs->opt_autofit = 1; + cs->opt_smooth = (config_shrink_blit_mode == CONFIG_SHRINK_BLIT_MODE_HALFTONE); + cs->opt_filename = 1; + + cs->font_ui = CreateFontW(-12, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"Segoe UI"); + if (!cs->font_ui) cs->font_ui = (HFONT)GetStockObject(DEFAULT_GUI_FONT); + + cs->font_bold = CreateFontW(-12, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"Segoe UI"); + if (!cs->font_bold) cs->font_bold = (HFONT)GetStockObject(DEFAULT_GUI_FONT); + + cs->font_overlay = CreateFontW(-14, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"Segoe UI"); + if (!cs->font_overlay) cs->font_overlay = cs->font_bold; + + // Start with empty panes - user will select images manually via browse button + + GetWindowRect(_viv_hwnd, &rc); + + hwnd = os_CreateWindowEx( + WS_EX_APPWINDOW | WS_EX_ACCEPTFILES, + "_VIV_COMPARE", + localization_get_string(LOCALIZATION_ID_COMPARE_IMAGES), + WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN, + rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, + _viv_hwnd, 0, os_hinstance, NULL); + + cs->hwnd = hwnd; + + if (hwnd) + { + DragAcceptFiles(hwnd, TRUE); + ShowWindow(hwnd, SW_MAXIMIZE); + EnableWindow(_viv_hwnd, FALSE); + } +} + +static void _viv_nav_item_free_all(void) +{ + if (_viv_nav_items) + { + int i; + + for(i=0;i<_viv_nav_item_count;i++) + { + mem_free(_viv_nav_items[i]); + } + + mem_free(_viv_nav_items); + + __viv_nav_item_start = 0; + _viv_nav_item_last = 0; + _viv_nav_items = 0; + _viv_nav_item_count = 0; + } +} + +static void _viv_nav_item_add(WIN32_FIND_DATA *fd) +{ + _viv_nav_item_t *navitem; + + navitem = mem_alloc(sizeof(_viv_nav_item_t)); + + os_copy_memory(&navitem->fd,fd,sizeof(WIN32_FIND_DATA)); + + if (__viv_nav_item_start) + { + _viv_nav_item_last->next = navitem; + } + else + { + __viv_nav_item_start = navitem; + } + + navitem->next = 0; + _viv_nav_item_last = navitem; + _viv_nav_item_count++; +} + +static int _viv_nav_compare(const _viv_nav_item_t *a,const _viv_nav_item_t *b) +{ + return _viv_fd_compare_name(&a->fd,&b->fd); +} + +static INT_PTR CALLBACK _viv_search_everything_proc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) +{ + switch(msg) + { + case WM_INITDIALOG: + + os_center_dialog(hwnd); + + SetWindowLongPtr(hwnd,GWLP_USERDATA,lParam); + + os_SetWindowText_localization_id(hwnd,lParam ? LOCALIZATION_ID_EVERYTHING_ADD_EVERYTHING_SEARCH_CAPTION : LOCALIZATION_ID_EVERYTHING_LOAD_EVERYTHING_SEARCH_CAPTION); + os_SetDlgItemText_localization_id(hwnd,IDC_SEARCH_EVERYTHING_RANDOM_CHECKBOX,LOCALIZATION_ID_RANDOMIZE); + + return TRUE; case WM_COMMAND: @@ -13668,6 +15648,22 @@ static void _viv_update_1to1_scroll(int x,int y) // #define PHOTO_ORIENTATION_ROTATE270 6u // #define PHOTO_ORIENTATION_TRANSVERSE 7u // #define PHOTO_ORIENTATION_ROTATE90 8u +static HBITMAP _viv_create_bitmap(HDC hdc, int width, int height) +{ + BITMAPINFO bmi; + void* bits; + + ZeroMemory(&bmi, sizeof(BITMAPINFO)); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = -height; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + + return CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &bits, NULL, 0); +} + static HBITMAP _viv_orientate_hbitmap(HBITMAP hbitmap,int orientation) { BITMAP bitmap; @@ -13706,7 +15702,7 @@ static HBITMAP _viv_orientate_hbitmap(HBITMAP hbitmap,int orientation) break; } - ret_hbitmap = CreateCompatibleBitmap(screen_hdc,ret_wide,ret_high); + ret_hbitmap = _viv_create_bitmap(screen_hdc,ret_wide,ret_high); if (ret_hbitmap) { int ret; @@ -14008,70 +16004,42 @@ static void _viv_do_mousewheel_action(int action,int delta,int x,int y) if (_viv_1to1) { _viv_1to1 = 0; - - if (delta > 0) + } + + { + double current_percent; + double new_percent; + + if (_viv_image_wide > 0) { - for(_viv_zoom_pos = 0;_viv_zoom_pos<_VIV_ZOOM_MAX;_viv_zoom_pos++) - { - _viv_get_render_size(&rw,&rh); - - if (rw > old_rw) - { - break; - } - } + current_percent = ((double)old_rw / (double)_viv_image_wide) * 100.0; } else { - for(_viv_zoom_pos = _VIV_ZOOM_MAX-1;_viv_zoom_pos>=0;_viv_zoom_pos--) - { - _viv_get_render_size(&rw,&rh); - - if (rw < old_rw) - { - break; - } - } + current_percent = 100.0; } - } - else - { + if (delta > 0) { - _viv_zoom_pos++; + new_percent = _viv_calculate_next_zoom(current_percent, 1); } - else + else if (delta < 0) { - _viv_zoom_pos--; + new_percent = _viv_calculate_next_zoom(current_percent, 0); } - } - - if (_viv_zoom_pos < 0) _viv_zoom_pos = 0; - if (_viv_zoom_pos > _VIV_ZOOM_MAX-1) _viv_zoom_pos = _VIV_ZOOM_MAX-1; - - if (_viv_zoom_pos != old_zoom_pos) - { - _viv_get_render_size(&rw,&rh); - /* - if (_viv_zoom_pos == 1) + else { - if ((rw < _viv_image_wide) || (rw < _viv_image_wide)) - { - rw = _viv_image_wide; - rh = _viv_image_high; - } + new_percent = current_percent; } - */ - // - // new_cursor_x = - // rx = (wide / 2) - (rw / 2) - //debug_printf("%d %d\n",(wide / 2) - (rw / 2),(high / 2) - (rh / 2)); - //debug_printf("%d %d\n",old_cursor_px,(old_cursor_px * 100) / old_rw); - - //debug_printf("old %d %d new %d %d\n",old_cursor_px,old_cursor_py,(wide / 2) - (rw / 2) - cursor_x + ((old_cursor_px * rw) / old_rw),(high / 2) - (rh / 2) - cursor_y + ((old_cursor_py * rh) / old_rh)) ; - //debug_printf("wide / 2 = %d, rw/2=%d, old_cursor_px * rw=%d\n",wide/2,rw/2,old_cursor_px * rw) ; + _viv_is_custom_zoom = 1; + _viv_custom_zoom_percent = new_percent; + } + + _viv_get_render_size(&rw,&rh); + if ((rw != old_rw) || (rh != old_rh)) + { if (old_rw) { new_cursor_x = ((__int64)old_cursor_px * (__int64)rw) / (__int64)old_rw; @@ -14093,6 +16061,7 @@ static void _viv_do_mousewheel_action(int action,int delta,int x,int y) _viv_view_set((wide / 2) - (rw / 2) - cursor_x + new_cursor_x,(high / 2) - (rh / 2) - cursor_y + new_cursor_y,1); InvalidateRect(_viv_hwnd,0,FALSE); + _viv_toolbar_update_buttons(); } } else @@ -14223,7 +16192,7 @@ static HBITMAP _viv_get_mipmap(HBITMAP hbitmap,int image_wide,int image_high,int if (mem2_hdc) { (*pmip)->mipmap = NULL; - (*pmip)->hbitmap = CreateCompatibleBitmap(screen_hdc,mip_wide,mip_high); + (*pmip)->hbitmap = _viv_create_bitmap(screen_hdc,mip_wide,mip_high); last_hbitmap = SelectObject(mem_hdc,(*pmip)->hbitmap); last2_hbitmap = SelectObject(mem2_hdc,best_hbitmap); @@ -14883,7 +16852,7 @@ static void _viv_stretch_blt(HDC dst_hdc,int dst_x,int dst_y,int dst_wide,int ds { HBITMAP dst_hbitmap; - dst_hbitmap = CreateCompatibleBitmap(dst_hdc,clip_wide,clip_high); + dst_hbitmap = _viv_create_bitmap(dst_hdc,clip_wide,clip_high); if (dst_hbitmap) { HGDIOBJ last_dst_hbitmap; @@ -15321,3 +17290,4 @@ static int _viv_safe_copy_data(const void *base,SIZE_T src_size,const void *src, return 1; } + diff --git a/src/viv.h b/src/viv.h index 16daba6a..89801043 100644 --- a/src/viv.h +++ b/src/viv.h @@ -57,9 +57,25 @@ typedef unsigned __int64 VIV_UINT64; #include #include //#include +#define IDC_STATIC -1 +#define IDC_ASSOCIATIONS_GROUPBOX IDC_STATIC +#define IDC_ADD_KEY_BUTTON IDC_ADD_KEY +#define IDC_EDIT_KEY_BUTTON IDC_EDIT_KEY +#define IDC_REMOVE_KEY_BUTTON IDC_REMOVE_KEY +#define IDC_LEFTCLICKACTION_COMBOBOX IDC_LEFTCLICKACTION +#define IDC_RIGHTCLICKACTION_COMBOBOX IDC_RIGHTCLICKACTION +#define IDC_MOUSEWHEELACTION_COMBOBOX IDC_MOUSEWHEELACTION +#define IDC_SHRINK_BLIT_MODE_COMBOBOX IDC_COMBO1 +#define IDC_MAGNIFY_BLIT_MODE_COMBOBOX IDC_COMBO2 +#define IDC_TITLE_BAR_FORMAT_COMBOBOX IDC_TITLE_BAR_FORMAT +#define IDC_AUTO_SIZE_WINDOW_COMBOBOX IDC_COMBO4 +#define IDC_WINDOWEDBACKGROUNDCOLOR_BUTTON IDC_WINDOWEDBACKGROUNDCOLOR +#define IDC_FULLSCREENBACKGROUNDCOLOR_BUTTON IDC_FULLSCREENBACKGROUNDCOLOR +#define IDC_CUSTOM_RATE_TYPE_COMBOBOX IDC_CUSTOM_RATE_TYPE_COMBO +#define IDC_SEARCH_EVERYTHING_RANDOM_CHECKBOX IDC_SEARCH_EVERYTHING_RANDOM #include "../res/resource.h" //#include -//#include +#include //#include //#include #include // OPENFILENAME @@ -93,10 +109,12 @@ enum VIV_ID_VIEW_ALLOW_SHRINKING, VIV_ID_VIEW_KEEP_ASPECT_RATIO, VIV_ID_VIEW_FILL_WINDOW, + VIV_ID_VIEW_SMOOTH, VIV_ID_VIEW_1TO1, VIV_ID_VIEW_BESTFIT, VIV_ID_VIEW_FULLSCREEN, VIV_ID_VIEW_SLIDESHOW, + VIV_ID_VIEW_COMPARE_IMAGES, VIV_ID_VIEW_ONTOP_ALWAYS, VIV_ID_VIEW_ONTOP_WHILE_PLAYING_OR_ANIMATING, VIV_ID_VIEW_ONTOP_NEVER, @@ -200,6 +218,7 @@ enum VIV_ID_OPTIONS_GENERAL, VIV_ID_OPTIONS_VIEW, VIV_ID_OPTIONS_CONTROLS, + VIV_ID_OPTIONS_ZOOM, VIV_ID_FILE_OPEN_FILE, VIV_ID_FILE_OPEN_FOLDER, diff --git a/test_math.c b/test_math.c new file mode 100644 index 00000000..441c4d4d --- /dev/null +++ b/test_math.c @@ -0,0 +1,6 @@ +#include +#include +int main() { + printf("%f\n", pow(2.0, 3.0)); + return 0; +} diff --git a/vs2026/voidImageViewer.vcxproj b/vs2026/voidImageViewer.vcxproj index a5f40a8e..8d6323f7 100644 --- a/vs2026/voidImageViewer.vcxproj +++ b/vs2026/voidImageViewer.vcxproj @@ -465,7 +465,7 @@ /MP %(AdditionalOptions) Disabled ..\libwebp;%(AdditionalIncludeDirectories) - BZ_NO_STDIO;VERSION_DEBUG;BZ_NO_STDIO;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;BZ_NO_STDIO;VERSION_DEBUG;VERSION_X86;%(PreprocessorDefinitions) false Async MultiThreadedDebug @@ -606,7 +606,7 @@ false true ..\libwebp;%(AdditionalIncludeDirectories) - BZ_NO_STDIO;BZ_NO_STDIO;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;BZ_NO_STDIO;VERSION_X86;%(PreprocessorDefinitions) true MultiThreaded Fast