Skip to content

feat: pin static image translation results from Compact - #769

Merged
ZGGSONG merged 3 commits into
STranslate:mainfrom
Lne27:feature/pinned-image-translate
Sep 9, 2026
Merged

ZGGSONG merged 3 commits into
STranslate:mainfrom
Lne27:feature/pinned-image-translate

Conversation

@Lne27

@Lne27 Lne27 commented Aug 26, 2026 •

Copy link
Copy Markdown
Contributor

Thank you very much for taking the time to review my contribution and explain your suggestions in detail. Your feedback helped me understand where I had made the implementation unnecessarily complicated and how to better fit the existing project. I've reworked it around the flow you suggested: complete screenshot capture, OCR and translation in Compact, then use Pin to keep the result in an independent, always-on-top window. I'd be grateful for any further guidance on anything that still needs improvement.

Changes addressing the feedback:

  1. Keep the existing Standalone and Compact modes. Add a Pin button to the Compact toolbar and an optional Compact-only shortcut, unassigned by default, both using the same Pin command. Pin is available when a result is ready. The shortcut can be changed, cleared or reset under Settings → Hotkeys → OCR / image translation window, using the existing conflict checks and persistence. Pinning closes the current Compact window, and the existing entry points can start the next screenshot task. Pinned windows have no toolbar.
  2. Store a static display snapshot in each pin. It shares the frozen images and completed translation overlay, and owns its text-selection data. OCR, translation and settings adjustments remain in Compact; pinned windows do not retain a translation ViewModel or rerun processing.
  3. Support single-click/drag selection, double-click word selection, triple-click paragraph selection and copying in both text layers. Wrapped lines retain their paragraph membership, and clipped translations remain fully copyable. Right-clicking selected text provides Copy. The background menu contains Copy all, Original/Translation, Shadow and Close. Dragging the background moves the pin; double-clicking it closes the pin.
  4. Allow multiple independent pins. Service or result-setting changes do not alter existing snapshots, while menu labels follow the application language. During screenshot selection, pinned content and shadows are temporarily hidden and restored on completion or cancellation, including pins created or closed during capture.
  5. Display a black shadow and an active blue glow in a companion window with mouse passthrough. The shadow switch affects the current pin and saves the default for future pins; the active glow remains independent. Physical image bounds drive both windows, including movement across monitors with different DPI and recovery after a monitor is disconnected.

The implementation uses the existing WPF/MVVM and dependency-injection structure. The main-window dispatch, service manager and OCR plugin interfaces are unchanged. Pin extends the existing window-hotkey settings and binds to the toolbar command through WPF; it adds no global hotkey registration. PinnedWindowController manages pin lifetime and screenshot visibility; the existing screenshot methods acquire a hide/restore scope. The image-translation ViewModel exposes completed results and pin availability, with cancellation checks before publishing results. The content window reuses ImageZoom; selection helpers use paragraph membership from the existing layout analysis without changing segmentation rules, retain clipped text for copying, and clear stale selection when the text source changes. Settings gains one shadow-default value.

Static pins have no polling or background recomputation, and zoom animations are disabled. Shadow/glow effects are clipped to four edge regions, with the opaque center drawn directly. Drawing is rebuilt on size, DPI or activation changes; moving an unchanged pin reuses it.

Validation: Debug and Release builds succeed; Release completes with zero warnings and errors. The linked test code passes 326 regression tests, and the submitted code passes all 274 existing upstream tests. Rendering checks include 168 pixel comparisons against the expected shadow/glow appearance across sizes, state changes and 100%–300% DPI. Basic functional checks and manual testing cover the screenshot/OCR/translation-to-pin flow, appearance, selection, copying and menus. Shortcut checks cover saved settings, defaults, clearing, resetting and conflicts. A compiled Compact-window test with a prepared result verifies live key bindings, rejection of unfinished results, and the shared Pin command for user-defined keys, including Ctrl+Alt+F8. Resetting leaves the shortcut unassigned, and the Pin button remains usable without a shortcut. Window-level checks cover screenshot hide/restore, creation and closure during capture, and release after closing; closed-window weak-reference counts are 0 for the 1-, 10- and 30-window groups.

See Test code and Performance tests and raw results.

Performance: snapshot creation averages 8.58 μs over 1,000 iterations, with 12,032 bytes of managed allocation per snapshot. The 30-window hide/restore median is 33.09 ms, including two DWM synchronizations. Process CPU-time increments during three-second idle samples are 109.38 / 62.50 / 46.88 ms for 1 / 10 / 30 windows. Creating and initializing the windows takes 152 ms for one pin and 2.85 s for a rapid batch of 30.

Measurements use Windows 11 build 26200, x64, .NET 10.0.11, 96 DPI and WPF Tier 2, with 800×400 BGRA source/annotated image pairs and 73 characters per text layer. Benchmark windows are shown off-screen. Component-memory measurements use 30 independent image pairs and pins, with three fresh processes per configuration after a one-window warm-up.

The component measurement adds images, text/snapshots, content windows and chrome in stages, ending with 29 shadows and one active glow. Each stage waits for Dispatcher idle, 500 ms and a full GC. The median total private-memory increment is 355.79 MiB; its component breakdown is:

Component stage Private-memory increment Share of total
Source images 26.77 MiB 7.52%
Annotated images, translated text and snapshots 33.68 MiB 9.47%
Content windows, ImageZoom and WPF rendering 244.02 MiB 68.59%
Shadow and active-glow companion windows 51.33 MiB 14.43%
Total 355.79 MiB 100%

Percentages are rounded. These process increments include rendering resources and runtime caches. The component measurement starts with image creation; actual pinning shares Compact's existing images. A separate measurement using the production controller to open 30 pins sequentially, allowing each to render while active, records a median total increment of 354.36 MiB after a two-second wait and full GC. Content rendering retains ImageZoom's hardware rendering and transparent-image support.

Below is a screenshot from testing in a real usage scenario:

Screenshot from testing Compact pinning in a real usage scenario

@Lne27

Lne27 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author
image

@ZGGSONG

ZGGSONG commented Sep 4, 2026 •

Copy link
Copy Markdown
Collaborator

First of all, thank you very much for contributing the code. I think the overall idea is great.

I have one suggestion regarding the user experience and implementation complexity:

Rather than treating Pinned as a third full-featured image translation mode, I think it would be better to let users complete OCR and translation in Compact mode first, and then provide a Pin button and keyboard shortcut to turn the current result into a standalone pinned window.

Screenshot → OCR/translation in Compact → Click Pin
                                      ↓
                         Freeze as a lightweight
                            standalone window
                                      ↓
                      Compact is free to handle
                         the next screenshot
  1. Use Compact mode to take a screenshot and complete OCR/translation.
  2. After clicking Pin, freeze the current result into a separate, lightweight always-on-top window.
  3. The Compact window is then detached from that result and can continue processing new screenshots.
  4. Multiple pinned windows can coexist without affecting subsequent screenshots.

A pinned window would only need to retain:

  • The original screenshot and translation overlay
  • Text selection and copying for both source and translated text
  • Switching between the original image and the translated overlay
  • Dragging from empty areas
  • Closing the window
  • Optionally hiding temporarily while taking a screenshot

Once pinned, the window will no longer support changes to OCR services, translation services, language, or word segmentation settings, nor will it rerun OCR or translation. (Alternatively, we could consider adding support for modifying these settings again in the future, in which case it would be acceptable to revert to the "compact" logic.)

Those operations would remain the responsibility of the Compact window. If users need to make adjustments, they can reprocess the image in Compact mode and Pin the updated result again.

This would make the responsibilities much clearer:

  • Compact: responsible for generating and adjusting translation results
  • Pinned: responsible for statically displaying already-generated results

It could also significantly reduce the state-management and lifecycle complexity of the current implementation. For example, each pinned window would no longer need its own full ViewModel, automatic recomputation, debouncing, task generation tracking, service execution coordinator, or delayed service-release logic.

A pinned window would only hold an immutable display snapshot, making it much less likely to be affected by service removal, settings changes, or background tasks.

@Lne27 Lne27 changed the title feat: add pinned in-place image translation mode feat: pin static image translation results from Compact Sep 5, 2026
Lne27 and others added 3 commits September 5, 2026 23:36
Add the Compact Pin button and independent static windows with source/translation selection, copying, context menus, shadow and active glow. Share frozen display results, release Compact after pinning, and hide pins during screenshot selection. Keep OCR and translation in Compact without adding a third mode.
Clip WPF effects to the four visible edge regions and draw the opaque center directly. Preserve the existing colors, blur, opacity and DPI sampling while reusing drawing during movement.
Bind the Compact shortcut to the existing Pin button command. Use the existing hotkey settings, conflict checks and persistence, with localized settings labels. Leave the shortcut unassigned by default and after reset.
@Lne27
Lne27 force-pushed the feature/pinned-image-translate branch from 29da2fb to 6837f32 Compare September 5, 2026 15:38
@ZGGSONG
ZGGSONG merged commit 90ed6ef into STranslate:main Sep 9, 2026
pasharm added a commit to pasharm/STranslate that referenced this pull request Sep 9, 2026
`uk.xaml` was written against `main` @ `2a75118f`. Three commits that landed
while STranslate#782 was open added keys to every other language, but could not add them
to Ukrainian, because `uk.xaml` did not exist upstream yet:

- 8 keys in `src/STranslate/Languages/uk.xaml` — pinned image translation (STranslate#769)
  and the notification-only fallback for text selection;
- 78 keys in the Youdao plugin dictionary — the localized API error messages.

So Ukrainian users saw English text in those places: the missing keys resolve
through the English dictionaries, which stay merged for the whole session
(`LoadDefaultLanguage` loads every plugin's `en.xaml` and then clears
`_oldResources`, so `RemoveOldLanguageFiles` never takes them out). The one case
where the raw `No Translation for key ...` shows up is a plugin installed in the
current session: `LoadInstalledPluginLanguages` merges only the active language
for it until the next restart.

Both files are back to the `en.xaml` key set *and* order: 792/792 for the main
dictionary, 82/82 for the plugin, with the blank-line section breaks in the same
places, so each file is now line-for-line structurally identical to its
`en.xaml`. Insertions only, no existing line is touched.

Verified: `dotnet build -c Debug` 0 errors, `dotnet test` 296/296, every language
dictionary in the tree loads through `XamlReader.Load` with the same key count as
its `en.xaml` neighbour, and the new values pass LanguageTool with the `uk-UA`
profile with no remaining warnings.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants