Skip to content

[GTK] Stop decoding the image file on every draw at zoom != 100 - #3510

Open
vogella wants to merge 1 commit into
eclipse-platform:masterfrom
vogella:perf/drawimage-getimagedata
Open

[GTK] Stop decoding the image file on every draw at zoom != 100#3510
vogella wants to merge 1 commit into
eclipse-platform:masterfrom
vogella:perf/drawimage-getimagedata

Conversation

@vogella

@vogella vogella commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

The internal drawImage that every public overload funnels into read the source dimensions from srcImage.getImageData(), which is getImageData(100).
At a device zoom other than 100 that misses the fast path and does new ImageData(fileName), a full open and decode of the image file, for SVG a full re-parse and re-rasterize, just to obtain two integers the Image already knows.
An ImageGcDrawer image paid the same way, by running the drawer callback once per draw.
Taking the dimensions from the Image removes that, with a fallback for images wrapped around a native handle by Image.gtk_new, which carry none and have no provider, so the fallback reads the in-memory cairo surface and never a file.

Measured with strace -f -e trace=openat over 100 draws of one Image at zoom 200, opens per draw fall from 1.00 to 0.00 for the 9 argument overload and from 2.00 to 1.00 for the 5 argument one; the remaining open there is the CachedImageAtSize path of #3505, addressed separately in #3506.
Cocoa already takes its dimensions from NSImage.size() and win32 from getBounds(), so this brings GTK in line with both.

Two cases change behavior, both where getBounds() and getImageData() already disagreed, and in both the new value is the one that agrees with getBounds().
A non-proportional asset set (16 pixels at 100%, 33 at 200%) now paints the last point row and column that were previously clipped, and a provider handing out the same file at every zoom now validates the source rectangle against the bounds, so passing getImageData() dimensions there raises ERROR_INVALID_ARGUMENT where it previously drew.
Everything else renders identically, verified as matching SHA-256 of the drawn ImageData across both overloads, three files and zoom 100, 150 and 200.

The new test deletes the image file after the first draw, so any further read fails loudly; it was confirmed to fail before the change and pass after.
It runs on all platforms although the fix is GTK only, so the win32 and macOS workflow results are worth a look rather than a rubber stamp.

Fixes #3507

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Test Results

  212 files  ±0    212 suites  ±0   27m 58s ⏱️ + 1m 30s
4 954 tests +1  4 928 ✅ +1   26 💤 ±0  0 ❌ ±0 
7 157 runs  +6  6 977 ✅ +6  180 💤 ±0  0 ❌ ±0 

Results for commit 351470c. ± Comparison against base commit 1f55dec.

♻️ This comment has been updated with latest results.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes GTK image drawing by using cached Image dimensions instead of repeatedly decoding image data at non-default zoom levels.

Changes:

  • Reads dimensions directly from GTK Image fields, retaining a native-handle fallback.
  • Adds a regression test covering drawing after the backing file is deleted.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/graphics/GC.java Avoids unnecessary image decoding during drawing.
tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_graphics_GC.java Verifies repeated drawing does not reread the source file.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@akurtakov
akurtakov force-pushed the perf/drawimage-getimagedata branch from 3443bc6 to 7778ce6 Compare August 31, 2026 21:23
@vogella
vogella force-pushed the perf/drawimage-getimagedata branch from 7778ce6 to 7d08a94 Compare September 4, 2026 13:44
@akurtakov
akurtakov force-pushed the perf/drawimage-getimagedata branch from 7d08a94 to 6ab47e9 Compare September 8, 2026 06:32
@akurtakov
akurtakov requested a balanced review from Copilot September 8, 2026 06:32

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The implementation is focused, preserves native-handle compatibility, and includes effective cross-platform regression coverage.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@akurtakov

Copy link
Copy Markdown
Member

The added test is failing on Windows.

@vogella

vogella commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

As soon as I reboot into Window, I can look at that.

@vogella
vogella force-pushed the perf/drawimage-getimagedata branch from 6ab47e9 to 944ba51 Compare September 9, 2026 13:32
The internal drawImage that every public overload funnels into read the
source dimensions from srcImage.getImageData(), which is getImageData(100).
At currentDeviceZoom != 100 that misses the zoom == currentDeviceZoom fast
path and loads new ImageData(fileName): a full open and full decode, for
SVG a full re-parse and re-rasterize, to obtain two integers the Image
already knows. The result was used for nothing else, the drawing itself
goes through srcImage.surface. An ImageGcDrawer image paid the same way,
by running the drawer callback once per draw.

Take the dimensions from the Image, falling back to ImageData for images
wrapped around a native handle by Image.gtk_new, which carry none. Those
have no provider, so the fallback reads the cairo surface and never a
file. Cocoa already takes its dimensions from NSImage.size() and win32
from getBounds(), so this brings GTK in line with both.

Opens per draw over 100 draws of one Image at zoom 200, strace on
Linux/GTK:

  9 arg overload, PNG    1.00 -> 0.00
  5 arg overload, PNG    2.00 -> 1.00

The open left on the 5 arg overload is the CachedImageAtSize path of
issue 3505.

Rendering is unchanged wherever the Image dimensions agree with the
decoded ones, verified as identical SHA-256 of the drawn ImageData over
both overloads, three files and zoom 100, 150 and 200, and again for an
ImageFileNameProvider returning one path at every zoom.

Two cases change, both where getBounds() and getImageData() already
disagreed. For an asset set that is not exactly proportional, 16 pixels
at 100% and 33 at 200%, the width field is round(33/2) = 17 while
getImageData(100) gives 16, so at zoom 200 the unscaled draw now paints
the last point row and column that were previously clipped. For a
provider handing out the same file at every zoom, a 16 pixel file at zoom
200 gives bounds 8 and getImageData 16, so passing getImageData()
dimensions as the source rectangle now raises ERROR_INVALID_ARGUMENT
where it previously drew. In both cases the new value is the one that
agrees with getBounds().

Fixes eclipse-platform#3507


Dispose the destination GC before the file-backed source image in the
regression test, and always restore the device zoom if cleanup fails.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@vogella
vogella force-pushed the perf/drawimage-getimagedata branch from 382608e to 351470c Compare September 9, 2026 20:50
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.

GC.drawImage fully decodes the image file on every draw at zoom != 100

3 participants