media:cpp : ui 1/2
This commit is contained in:
parent
39388eb5d9
commit
f18128f4d2
@ -16,6 +16,11 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
# MSVC: UTF-8 source/execution charset so wide string literals (L"...") match UTF-8 in .cpp files.
|
||||
if(MSVC)
|
||||
add_compile_options(/utf-8)
|
||||
endif()
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
|
||||
# Windows: official libvips dev tree (see scripts/fetch-vips-windows.ps1) — vips-dev-* under third_party/
|
||||
@ -145,7 +150,11 @@ add_executable(media-img
|
||||
src/ipc/ipc_serve.cpp
|
||||
)
|
||||
if(WIN32)
|
||||
target_sources(media-img PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/win/register_explorer.cpp")
|
||||
target_sources(media-img PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/win/register_explorer.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/win/resize_ui.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/win/media-img-win.manifest"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_include_directories(media-img PRIVATE
|
||||
|
||||
@ -109,6 +109,8 @@ Sharp wraps libvips: **decode → process → encode**. We do the same with `vip
|
||||
| `flip` / `flop` | `flip` / `flop` | |
|
||||
| Letterbox | `background` | `#rrggbb` for `contain` |
|
||||
|
||||
**Windows:** `media-img resize --ui` opens a **native Win32** dialog to choose input/output paths, max dimensions, fit mode, quality, and enlargement / autorotate / strip options. Other CLI flags seed the dialog; **`--src` / `--dst` are not allowed** with `--ui`. If you cancel, the command exits without processing.
|
||||
|
||||
**REST** `POST /v1/resize` and **IPC** use the same JSON keys as the table, plus the fields in the **“Batch paths & cache”** section below.
|
||||
|
||||
---
|
||||
|
||||
BIN
packages/media/cpp/dist/media-img.exe
vendored
BIN
packages/media/cpp/dist/media-img.exe
vendored
Binary file not shown.
BIN
packages/media/cpp/dist/media-img.pdb
vendored
BIN
packages/media/cpp/dist/media-img.pdb
vendored
Binary file not shown.
@ -19,6 +19,7 @@
|
||||
#include "ipc/ipc_serve.hpp"
|
||||
#if defined(_WIN32)
|
||||
#include "win/register_explorer.hpp"
|
||||
#include "win/resize_ui.hpp"
|
||||
#endif
|
||||
|
||||
#ifndef MEDIA_IMG_VERSION
|
||||
@ -118,6 +119,12 @@ int main(int argc, char **argv) {
|
||||
->add_option("--url-max-redirects", url_max_redirects, "Max redirects when fetching URL inputs")
|
||||
->default_val(20);
|
||||
|
||||
#if defined(_WIN32)
|
||||
bool resize_ui = false;
|
||||
resize_cmd->add_flag("--ui", resize_ui,
|
||||
"Windows: show native dialog to pick input/output and resize options (cannot be used with --src/--dst)");
|
||||
#endif
|
||||
|
||||
std::string host = "127.0.0.1";
|
||||
int port = 8080;
|
||||
bool serve_no_cache = false;
|
||||
@ -170,48 +177,103 @@ int main(int argc, char **argv) {
|
||||
CLI11_PARSE(app, argc, argv);
|
||||
|
||||
if (resize_cmd->parsed()) {
|
||||
const bool use_src_dst = !src_flag.empty() || !dst_flag.empty();
|
||||
if (use_src_dst) {
|
||||
if (src_flag.empty() || dst_flag.empty()) {
|
||||
std::cerr << "resize: --src and --dst must be used together\n";
|
||||
media::ResizeOptions opt;
|
||||
std::string in_path_e;
|
||||
std::string out_path_e;
|
||||
|
||||
#if defined(_WIN32)
|
||||
if (resize_ui) {
|
||||
if (!src_flag.empty() || !dst_flag.empty()) {
|
||||
std::cerr << "resize: --ui cannot be used with --src or --dst\n";
|
||||
return 1;
|
||||
}
|
||||
in_path = src_flag;
|
||||
out_path = dst_flag;
|
||||
} else if (in_path.empty()) {
|
||||
std::cerr << "resize: provide input (and output, or omit output to write under the current directory)\n";
|
||||
return 1;
|
||||
} else if (out_path.empty()) {
|
||||
media::ResizeOptions initial{};
|
||||
initial.max_width = max_w;
|
||||
initial.max_height = max_h;
|
||||
initial.format = format;
|
||||
initial.fit = fit;
|
||||
initial.position = position;
|
||||
initial.kernel = kernel;
|
||||
initial.background = background;
|
||||
initial.quality = quality;
|
||||
initial.png_compression = png_compression;
|
||||
initial.rotate = rotate;
|
||||
initial.flip = flip;
|
||||
initial.flop = flop;
|
||||
initial.autorotate = !no_autorotate;
|
||||
initial.strip_metadata = !no_strip;
|
||||
initial.without_enlargement = !allow_enlargement;
|
||||
initial.cache_enabled = !resize_no_cache;
|
||||
initial.cache_dir = resize_cache_dir;
|
||||
initial.url_timeout_sec = url_timeout_sec;
|
||||
initial.url_max_redirects = url_max_redirects;
|
||||
|
||||
std::string ui_in = in_path;
|
||||
std::string ui_out = out_path;
|
||||
if (!media::win::show_resize_ui(opt, ui_in, ui_out, initial))
|
||||
return 0;
|
||||
in_path_e = std::move(ui_in);
|
||||
out_path_e = std::move(ui_out);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
const bool use_src_dst = !src_flag.empty() || !dst_flag.empty();
|
||||
if (use_src_dst) {
|
||||
if (src_flag.empty() || dst_flag.empty()) {
|
||||
std::cerr << "resize: --src and --dst must be used together\n";
|
||||
return 1;
|
||||
}
|
||||
in_path_e = src_flag;
|
||||
out_path_e = dst_flag;
|
||||
} else if (in_path.empty()) {
|
||||
std::cerr << "resize: provide input (and output, or omit output to write under the current directory)\n";
|
||||
return 1;
|
||||
} else if (out_path.empty()) {
|
||||
std::string derr;
|
||||
out_path_e = media::default_output_path_for_resize(in_path, format, derr);
|
||||
if (out_path_e.empty()) {
|
||||
std::cerr << derr << "\n";
|
||||
return 1;
|
||||
}
|
||||
in_path_e = in_path;
|
||||
} else {
|
||||
in_path_e = in_path;
|
||||
out_path_e = out_path;
|
||||
}
|
||||
|
||||
opt.max_width = max_w;
|
||||
opt.max_height = max_h;
|
||||
opt.format = format;
|
||||
opt.fit = fit;
|
||||
opt.position = position;
|
||||
opt.kernel = kernel;
|
||||
opt.background = background;
|
||||
opt.quality = quality;
|
||||
opt.png_compression = png_compression;
|
||||
opt.rotate = rotate;
|
||||
opt.flip = flip;
|
||||
opt.flop = flop;
|
||||
opt.autorotate = !no_autorotate;
|
||||
opt.strip_metadata = !no_strip;
|
||||
opt.without_enlargement = !allow_enlargement;
|
||||
opt.cache_enabled = !resize_no_cache;
|
||||
opt.cache_dir = resize_cache_dir;
|
||||
opt.url_timeout_sec = url_timeout_sec;
|
||||
opt.url_max_redirects = url_max_redirects;
|
||||
}
|
||||
|
||||
if (out_path_e.empty()) {
|
||||
std::string derr;
|
||||
out_path = media::default_output_path_for_resize(in_path, format, derr);
|
||||
if (out_path.empty()) {
|
||||
out_path_e = media::default_output_path_for_resize(in_path_e, opt.format, derr);
|
||||
if (out_path_e.empty()) {
|
||||
std::cerr << derr << "\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
media::ResizeOptions opt;
|
||||
opt.max_width = max_w;
|
||||
opt.max_height = max_h;
|
||||
opt.format = format;
|
||||
opt.fit = fit;
|
||||
opt.position = position;
|
||||
opt.kernel = kernel;
|
||||
opt.background = background;
|
||||
opt.quality = quality;
|
||||
opt.png_compression = png_compression;
|
||||
opt.rotate = rotate;
|
||||
opt.flip = flip;
|
||||
opt.flop = flop;
|
||||
opt.autorotate = !no_autorotate;
|
||||
opt.strip_metadata = !no_strip;
|
||||
opt.without_enlargement = !allow_enlargement;
|
||||
opt.cache_enabled = !resize_no_cache;
|
||||
opt.cache_dir = resize_cache_dir;
|
||||
opt.url_timeout_sec = url_timeout_sec;
|
||||
opt.url_max_redirects = url_max_redirects;
|
||||
|
||||
std::string err;
|
||||
media::ResizeBatchResult batch;
|
||||
if (!media::resize_batch(in_path, out_path, opt, err, &batch)) {
|
||||
if (!media::resize_batch(in_path_e, out_path_e, opt, err, &batch)) {
|
||||
std::cerr << err << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
15
packages/media/cpp/src/win/media-img-win.manifest
Normal file
15
packages/media/cpp/src/win/media-img-win.manifest
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.Windows.Common-Controls"
|
||||
version="6.0.0.0"
|
||||
processorArchitecture="*"
|
||||
publicKeyToken="6595b64144ccf1df"
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
</assembly>
|
||||
398
packages/media/cpp/src/win/resize_ui.cpp
Normal file
398
packages/media/cpp/src/win/resize_ui.cpp
Normal file
@ -0,0 +1,398 @@
|
||||
#include "resize_ui.hpp"
|
||||
|
||||
#include <windows.h>
|
||||
#include <winerror.h>
|
||||
#include <commctrl.h>
|
||||
#include <commdlg.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#pragma comment(lib, "Comdlg32.lib")
|
||||
#pragma comment(lib, "User32.lib")
|
||||
#pragma comment(lib, "Gdi32.lib")
|
||||
#pragma comment(lib, "Comctl32.lib")
|
||||
|
||||
namespace media::win {
|
||||
|
||||
static std::string wide_to_utf8(const std::wstring &w) {
|
||||
if (w.empty())
|
||||
return {};
|
||||
int n = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast<int>(w.size()), nullptr, 0, nullptr, nullptr);
|
||||
if (n <= 0)
|
||||
return {};
|
||||
std::string s(static_cast<size_t>(n), '\0');
|
||||
WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast<int>(w.size()), s.data(), n, nullptr, nullptr);
|
||||
return s;
|
||||
}
|
||||
|
||||
static std::wstring utf8_to_wide(const std::string &s) {
|
||||
if (s.empty())
|
||||
return L"";
|
||||
int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast<int>(s.size()), nullptr, 0);
|
||||
if (n <= 0)
|
||||
return L"";
|
||||
std::wstring w(static_cast<size_t>(n), L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast<int>(s.size()), w.data(), n);
|
||||
return w;
|
||||
}
|
||||
|
||||
enum : int {
|
||||
IDC_STATIC_IN = 100,
|
||||
IDC_EDIT_IN = 101,
|
||||
IDC_BTN_IN = 102,
|
||||
IDC_STATIC_OUT = 103,
|
||||
IDC_EDIT_OUT = 104,
|
||||
IDC_BTN_OUT = 105,
|
||||
IDC_STATIC_MW = 106,
|
||||
IDC_EDIT_MW = 107,
|
||||
IDC_STATIC_MH = 108,
|
||||
IDC_EDIT_MH = 109,
|
||||
IDC_STATIC_FIT = 110,
|
||||
IDC_COMBO_FIT = 111,
|
||||
IDC_STATIC_Q = 112,
|
||||
IDC_EDIT_Q = 113,
|
||||
IDC_CHK_ENLARGE = 114,
|
||||
IDC_CHK_AUTOROT = 115,
|
||||
IDC_CHK_STRIP = 116,
|
||||
IDC_BTN_OK = IDOK,
|
||||
IDC_BTN_CANCEL = IDCANCEL,
|
||||
};
|
||||
|
||||
static HFONT g_msg_font{};
|
||||
|
||||
static void ensure_message_font() {
|
||||
if (g_msg_font)
|
||||
return;
|
||||
NONCLIENTMETRICSW ncm{};
|
||||
ncm.cbSize = sizeof(ncm);
|
||||
if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0))
|
||||
g_msg_font = CreateFontIndirectW(&ncm.lfMessageFont);
|
||||
if (!g_msg_font)
|
||||
g_msg_font = static_cast<HFONT>(GetStockObject(DEFAULT_GUI_FONT));
|
||||
}
|
||||
|
||||
static BOOL CALLBACK set_child_font(HWND child, LPARAM font) {
|
||||
SendMessageW(child, WM_SETFONT, static_cast<WPARAM>(font), TRUE);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void apply_message_font(HWND root) {
|
||||
ensure_message_font();
|
||||
SendMessageW(root, WM_SETFONT, reinterpret_cast<WPARAM>(g_msg_font), TRUE);
|
||||
EnumChildWindows(root, set_child_font, reinterpret_cast<LPARAM>(g_msg_font));
|
||||
}
|
||||
|
||||
struct UiState {
|
||||
HWND root{};
|
||||
HWND h_in{};
|
||||
HWND h_out{};
|
||||
HWND h_mw{};
|
||||
HWND h_mh{};
|
||||
HWND h_fit{};
|
||||
HWND h_q{};
|
||||
HWND h_enlarge{};
|
||||
HWND h_autorot{};
|
||||
HWND h_strip{};
|
||||
ResizeOptions *opt{};
|
||||
std::string *in_path{};
|
||||
std::string *out_path{};
|
||||
bool accepted{false};
|
||||
};
|
||||
|
||||
static void set_utf8_edit(HWND h, const std::string &utf8) {
|
||||
SetWindowTextW(h, utf8_to_wide(utf8).c_str());
|
||||
}
|
||||
|
||||
static std::string get_utf8_edit(HWND h) {
|
||||
const int n = GetWindowTextLengthW(h);
|
||||
if (n <= 0)
|
||||
return {};
|
||||
std::wstring w(static_cast<size_t>(n) + 1, L'\0');
|
||||
GetWindowTextW(h, w.data(), n + 1);
|
||||
w.resize(static_cast<size_t>(n));
|
||||
return wide_to_utf8(w);
|
||||
}
|
||||
|
||||
static void browse_open(HWND owner, HWND h_edit) {
|
||||
wchar_t buf[MAX_PATH * 4]{};
|
||||
OPENFILENAMEW of{};
|
||||
of.lStructSize = sizeof(of);
|
||||
of.hwndOwner = owner;
|
||||
of.lpstrFilter = L"Images\0*.jpg;*.jpeg;*.png;*.webp;*.tif;*.tiff;*.bmp;*.gif;*.avif;*.heic;*.jpe;*.jfif\0"
|
||||
L"All\0*.*\0\0";
|
||||
of.nFilterIndex = 1;
|
||||
of.lpstrFile = buf;
|
||||
of.nMaxFile = MAX_PATH * 4;
|
||||
of.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_EXPLORER;
|
||||
if (GetOpenFileNameW(&of))
|
||||
SetWindowTextW(h_edit, buf);
|
||||
}
|
||||
|
||||
static void browse_save(HWND owner, HWND h_edit) {
|
||||
wchar_t buf[MAX_PATH * 4]{};
|
||||
GetWindowTextW(h_edit, buf, MAX_PATH * 4);
|
||||
OPENFILENAMEW of{};
|
||||
of.lStructSize = sizeof(of);
|
||||
of.hwndOwner = owner;
|
||||
of.lpstrFilter = L"JPEG\0*.jpg;*.jpeg\0PNG\0*.png\0WebP\0*.webp\0TIFF\0*.tif;*.tiff\0All\0*.*\0\0";
|
||||
of.nFilterIndex = 1;
|
||||
of.lpstrFile = buf;
|
||||
of.nMaxFile = MAX_PATH * 4;
|
||||
of.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_EXPLORER;
|
||||
if (GetSaveFileNameW(&of))
|
||||
SetWindowTextW(h_edit, buf);
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK UiWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
UiState *st = reinterpret_cast<UiState *>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
||||
switch (msg) {
|
||||
case WM_CREATE: {
|
||||
auto *cs = reinterpret_cast<CREATESTRUCTW *>(lp);
|
||||
st = reinterpret_cast<UiState *>(cs->lpCreateParams);
|
||||
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(st));
|
||||
st->root = hwnd;
|
||||
const int pad = 12;
|
||||
const int x0 = 20;
|
||||
int y = pad + 6;
|
||||
const int lw = 128;
|
||||
const int gap = 10;
|
||||
const int ew = 340;
|
||||
const int btn_w = 96;
|
||||
const int btn_h = 28;
|
||||
const int eh = 24;
|
||||
const int edit_x = x0 + lw + gap;
|
||||
const int browse_x = edit_x + ew + gap;
|
||||
|
||||
CreateWindowExW(0, L"STATIC", L"Input image:", WS_CHILD | WS_VISIBLE, x0, y + 2, lw, 20, hwnd,
|
||||
(HMENU)IDC_STATIC_IN, nullptr, nullptr);
|
||||
st->h_in = CreateWindowExW(
|
||||
WS_EX_CLIENTEDGE, L"EDIT", L"",
|
||||
WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, edit_x, y, ew, eh + 4, hwnd, (HMENU)IDC_EDIT_IN, nullptr,
|
||||
nullptr);
|
||||
CreateWindowExW(0, L"BUTTON", L"Browse…", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, browse_x, y, btn_w, btn_h,
|
||||
hwnd, (HMENU)IDC_BTN_IN, nullptr, nullptr);
|
||||
y += 44;
|
||||
|
||||
CreateWindowExW(0, L"STATIC", L"Output (optional):", WS_CHILD | WS_VISIBLE, x0, y + 2, lw, 20, hwnd,
|
||||
(HMENU)IDC_STATIC_OUT, nullptr, nullptr);
|
||||
st->h_out = CreateWindowExW(
|
||||
WS_EX_CLIENTEDGE, L"EDIT", L"",
|
||||
WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, edit_x, y, ew, eh + 4, hwnd, (HMENU)IDC_EDIT_OUT,
|
||||
nullptr, nullptr);
|
||||
CreateWindowExW(0, L"BUTTON", L"Browse…", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, browse_x, y, btn_w, btn_h,
|
||||
hwnd, (HMENU)IDC_BTN_OUT, nullptr, nullptr);
|
||||
y += 48;
|
||||
|
||||
CreateWindowExW(0, L"STATIC", L"Max width:", WS_CHILD | WS_VISIBLE, x0, y + 2, lw, 20, hwnd,
|
||||
(HMENU)IDC_STATIC_MW, nullptr, nullptr);
|
||||
wchar_t mw[32]{};
|
||||
swprintf_s(mw, L"%d", st->opt->max_width);
|
||||
st->h_mw = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", mw,
|
||||
WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, edit_x, y, 108, eh + 4, hwnd,
|
||||
(HMENU)IDC_EDIT_MW, nullptr, nullptr);
|
||||
|
||||
const int mh_label_x = edit_x + 108 + gap + 24;
|
||||
CreateWindowExW(0, L"STATIC", L"Max height:", WS_CHILD | WS_VISIBLE, mh_label_x, y + 2, 100, 20, hwnd,
|
||||
(HMENU)IDC_STATIC_MH, nullptr, nullptr);
|
||||
wchar_t mh[32]{};
|
||||
swprintf_s(mh, L"%d", st->opt->max_height);
|
||||
st->h_mh = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", mh,
|
||||
WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, mh_label_x + 100 + gap, y, 108, eh + 4, hwnd,
|
||||
(HMENU)IDC_EDIT_MH, nullptr, nullptr);
|
||||
y += 44;
|
||||
|
||||
CreateWindowExW(0, L"STATIC", L"Fit:", WS_CHILD | WS_VISIBLE, x0, y + 2, lw, 20, hwnd, (HMENU)IDC_STATIC_FIT,
|
||||
nullptr, nullptr);
|
||||
st->h_fit = CreateWindowExW(WS_EX_CLIENTEDGE, L"COMBOBOX", L"",
|
||||
WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | CBS_HASSTRINGS, edit_x, y - 2, 240,
|
||||
200, hwnd, (HMENU)IDC_COMBO_FIT, nullptr, nullptr);
|
||||
const wchar_t *fits[] = {L"inside", L"cover", L"contain", L"fill", L"outside"};
|
||||
for (const wchar_t *f : fits)
|
||||
SendMessageW(st->h_fit, CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(f));
|
||||
{
|
||||
std::wstring cur = utf8_to_wide(st->opt->fit);
|
||||
for (auto &ch : cur) {
|
||||
if (ch >= L'A' && ch <= L'Z')
|
||||
ch = static_cast<wchar_t>(ch - L'A' + L'a');
|
||||
}
|
||||
int sel = 0;
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
if (cur == fits[i]) {
|
||||
sel = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
SendMessageW(st->h_fit, CB_SETCURSEL, static_cast<WPARAM>(sel), 0);
|
||||
}
|
||||
y += 40;
|
||||
|
||||
CreateWindowExW(0, L"STATIC", L"Quality (1–100):", WS_CHILD | WS_VISIBLE, x0, y + 2, lw + 48, 20, hwnd,
|
||||
(HMENU)IDC_STATIC_Q, nullptr, nullptr);
|
||||
wchar_t q[16]{};
|
||||
swprintf_s(q, L"%d", st->opt->quality);
|
||||
st->h_q = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", q,
|
||||
WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, edit_x, y, 72, eh + 4, hwnd,
|
||||
(HMENU)IDC_EDIT_Q, nullptr, nullptr);
|
||||
y += 44;
|
||||
|
||||
st->h_enlarge =
|
||||
CreateWindowExW(0, L"BUTTON", L"Allow enlargement", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, x0, y, 220,
|
||||
26, hwnd, (HMENU)IDC_CHK_ENLARGE, nullptr, nullptr);
|
||||
SendMessageW(st->h_enlarge, BM_SETCHECK,
|
||||
st->opt->without_enlargement ? BST_UNCHECKED : BST_CHECKED, 0);
|
||||
st->h_autorot =
|
||||
CreateWindowExW(0, L"BUTTON", L"EXIF autorotate", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, x0 + 240, y, 200,
|
||||
26, hwnd, (HMENU)IDC_CHK_AUTOROT, nullptr, nullptr);
|
||||
SendMessageW(st->h_autorot, BM_SETCHECK, st->opt->autorotate ? BST_CHECKED : BST_UNCHECKED, 0);
|
||||
y += 34;
|
||||
st->h_strip = CreateWindowExW(0, L"BUTTON", L"Strip metadata on save", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
|
||||
x0, y, 280, 26, hwnd, (HMENU)IDC_CHK_STRIP, nullptr, nullptr);
|
||||
SendMessageW(st->h_strip, BM_SETCHECK, st->opt->strip_metadata ? BST_CHECKED : BST_UNCHECKED, 0);
|
||||
y += 42;
|
||||
|
||||
const int btn_row_w = 100;
|
||||
CreateWindowExW(0, L"BUTTON", L"OK", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, browse_x - btn_row_w - gap - btn_row_w,
|
||||
y, btn_row_w, 32, hwnd, (HMENU)IDC_BTN_OK, nullptr, nullptr);
|
||||
CreateWindowExW(0, L"BUTTON", L"Cancel", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, browse_x - btn_row_w, y, btn_row_w,
|
||||
32, hwnd, (HMENU)IDC_BTN_CANCEL, nullptr, nullptr);
|
||||
|
||||
set_utf8_edit(st->h_in, *st->in_path);
|
||||
set_utf8_edit(st->h_out, *st->out_path);
|
||||
apply_message_font(hwnd);
|
||||
return 0;
|
||||
}
|
||||
case WM_COMMAND: {
|
||||
if (!st)
|
||||
return 0;
|
||||
const int id = LOWORD(wp);
|
||||
if (id == IDC_BTN_IN) {
|
||||
browse_open(hwnd, st->h_in);
|
||||
return 0;
|
||||
}
|
||||
if (id == IDC_BTN_OUT) {
|
||||
browse_save(hwnd, st->h_out);
|
||||
return 0;
|
||||
}
|
||||
if (id == IDC_BTN_CANCEL) {
|
||||
st->accepted = false;
|
||||
DestroyWindow(hwnd);
|
||||
return 0;
|
||||
}
|
||||
if (id == IDC_BTN_OK) {
|
||||
*st->in_path = get_utf8_edit(st->h_in);
|
||||
if (st->in_path->empty()) {
|
||||
MessageBoxW(hwnd, L"Choose an input image.", L"media-img", MB_OK | MB_ICONWARNING);
|
||||
return 0;
|
||||
}
|
||||
*st->out_path = get_utf8_edit(st->h_out);
|
||||
|
||||
wchar_t bmw[32]{};
|
||||
GetWindowTextW(st->h_mw, bmw, 32);
|
||||
wchar_t bmh[32]{};
|
||||
GetWindowTextW(st->h_mh, bmh, 32);
|
||||
st->opt->max_width = _wtoi(bmw);
|
||||
st->opt->max_height = _wtoi(bmh);
|
||||
if (st->opt->max_width < 0)
|
||||
st->opt->max_width = 0;
|
||||
if (st->opt->max_height < 0)
|
||||
st->opt->max_height = 0;
|
||||
|
||||
const int fi = static_cast<int>(SendMessageW(st->h_fit, CB_GETCURSEL, 0, 0));
|
||||
const wchar_t *fits[] = {L"inside", L"cover", L"contain", L"fill", L"outside"};
|
||||
if (fi >= 0 && fi < 5)
|
||||
st->opt->fit = wide_to_utf8(fits[fi]);
|
||||
|
||||
wchar_t bq[32]{};
|
||||
GetWindowTextW(st->h_q, bq, 32);
|
||||
st->opt->quality = _wtoi(bq);
|
||||
if (st->opt->quality < 1)
|
||||
st->opt->quality = 1;
|
||||
if (st->opt->quality > 100)
|
||||
st->opt->quality = 100;
|
||||
|
||||
st->opt->without_enlargement =
|
||||
SendMessageW(st->h_enlarge, BM_GETCHECK, 0, 0) != BST_CHECKED;
|
||||
st->opt->autorotate = SendMessageW(st->h_autorot, BM_GETCHECK, 0, 0) == BST_CHECKED;
|
||||
st->opt->strip_metadata = SendMessageW(st->h_strip, BM_GETCHECK, 0, 0) == BST_CHECKED;
|
||||
|
||||
st->accepted = true;
|
||||
DestroyWindow(hwnd);
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
case WM_CLOSE:
|
||||
if (st)
|
||||
st->accepted = false;
|
||||
DestroyWindow(hwnd);
|
||||
return 0;
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
default:
|
||||
return DefWindowProcW(hwnd, msg, wp, lp);
|
||||
}
|
||||
}
|
||||
|
||||
static const wchar_t kClassName[] = L"MediaImgResizeUi";
|
||||
|
||||
bool show_resize_ui(ResizeOptions &opt, std::string &input_path, std::string &output_path, const ResizeOptions &initial) {
|
||||
opt = initial;
|
||||
|
||||
INITCOMMONCONTROLSEX icc{};
|
||||
icc.dwSize = sizeof(icc);
|
||||
icc.dwICC = ICC_STANDARD_CLASSES | ICC_WIN95_CLASSES;
|
||||
InitCommonControlsEx(&icc);
|
||||
|
||||
static bool reg = false;
|
||||
if (!reg) {
|
||||
WNDCLASSEXW wc{};
|
||||
wc.cbSize = sizeof(wc);
|
||||
wc.lpfnWndProc = UiWndProc;
|
||||
wc.hInstance = GetModuleHandleW(nullptr);
|
||||
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||
wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_3DFACE + 1);
|
||||
wc.lpszClassName = kClassName;
|
||||
if (!RegisterClassExW(&wc)) {
|
||||
if (GetLastError() != ERROR_CLASS_ALREADY_EXISTS)
|
||||
return false;
|
||||
}
|
||||
reg = true;
|
||||
}
|
||||
|
||||
UiState state{};
|
||||
state.opt = &opt;
|
||||
state.in_path = &input_path;
|
||||
state.out_path = &output_path;
|
||||
|
||||
const int W = 620;
|
||||
const int H = 452;
|
||||
RECT r{};
|
||||
SystemParametersInfoW(SPI_GETWORKAREA, 0, &r, 0);
|
||||
const int sx = r.left + ((r.right - r.left) - W) / 2;
|
||||
const int sy = r.top + ((r.bottom - r.top) - H) / 2;
|
||||
|
||||
HWND hwnd =
|
||||
CreateWindowExW(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE, kClassName, L"media-img — resize",
|
||||
WS_CAPTION | WS_SYSMENU | WS_CLIPCHILDREN, sx, sy, W, H, nullptr, nullptr, GetModuleHandleW(nullptr),
|
||||
&state);
|
||||
if (!hwnd)
|
||||
return false;
|
||||
|
||||
ShowWindow(hwnd, SW_SHOW);
|
||||
UpdateWindow(hwnd);
|
||||
|
||||
MSG msg;
|
||||
while (GetMessageW(&msg, nullptr, 0, 0) > 0) {
|
||||
if (!IsDialogMessageW(hwnd, &msg)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
return state.accepted;
|
||||
}
|
||||
|
||||
} // namespace media::win
|
||||
16
packages/media/cpp/src/win/resize_ui.hpp
Normal file
16
packages/media/cpp/src/win/resize_ui.hpp
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "core/resize.hpp"
|
||||
|
||||
namespace media::win {
|
||||
|
||||
/**
|
||||
* Show a native Win32 dialog: pick input image, optional output path, and resize options.
|
||||
* Seeds defaults from `initial` (e.g. CLI flags). On Cancel returns false.
|
||||
*/
|
||||
bool show_resize_ui(ResizeOptions &opt, std::string &input_path, std::string &output_path,
|
||||
const ResizeOptions &initial);
|
||||
|
||||
} // namespace media::win
|
||||
Loading…
Reference in New Issue
Block a user