Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Sep 25, 2025

Problem

When panning an ImageViewer, the getCroppedImage() method would return inconsistent results that didn't match what users actually see in the viewport. This happened because:

  1. The paint() method applies viewport bounds constraints to keep the image visible within the component
  2. The updatePositions() method was calculating the crop box using raw pan positions without applying the same constraints
  3. This mismatch caused getCroppedImage() to return "flaky" results based on internal pan state rather than visible content

Example Issue

Form hi = new Form("Crop", new GridLayout(2, 1));
Image greenImage = Image.createImage(400, 400, 0xff00ff00);
final ImageViewer iv = new ImageViewer(greenImage);
Label cropPreview = new Label();
hi.addAll(iv, cropPreview);

UITimer.timer(1000, true, () -> {
    // Before fix: This would show different results after panning
    // even when the visible content appeared the same
    cropPreview.setIcon(iv.getCroppedImage(0xff0000));
});

After panning the image, getCroppedImage() would return content that didn't match what was visible in the ImageViewer, making the results unpredictable.

Solution

Modified the updatePositions() method to apply the same viewport bounds constraints used by the paint() method before calculating the crop box. This ensures consistency between what users see and what getCroppedImage() returns.

Changes made:

  • Added constraint logic for both zoom == 1 and zoom > 1 cases
  • Applied the same Math.max(Math.min(...)) bounds checking used in paint()
  • Ensured crop box calculations use constrained image positions that match rendering

Result

  • getCroppedImage() now consistently returns content that matches the visible viewport
  • Eliminates "flaky" behavior where pan operations affected cropped image results
  • Maintains backward compatibility - no breaking changes to the API
  • Resolves the issue described in the linked Stack Overflow question

The fix ensures that regardless of pan state, getCroppedImage() always represents what users actually see in the ImageViewer component.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/repos/codenameone/codenameone%2FCodenameOne/languages
    • Triggering command: /home/REDACTED/work/_temp/ghcca-node/node/bin/node --enable-source-maps /home/REDACTED/work/_temp/copilot-developer-action-main/dist/index.js (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>Pan of ImageViewer is saved in internal state and exposed via getCroppedImage()</issue_title>
<issue_description>When panning an ImageViewer the image doesn't move in the UI but the pan does change the image state. As a result the getCroppedImage() results suddenly become "flaky". This is discussed here.

I created the following test case:

Form hi = new Form("Crop", new GridLayout(2, 1));
hi.setScrollable(false);
Image greenImage = Image.createImage(400, 400, 0xff00ff00);
Graphics g = greenImage.getGraphics();
g.setColor(0xffffff);
g.drawLine(0, 0, 400, 400);
g.drawLine(400, 0, 0, 400);
final ImageViewer iv = new ImageViewer(greenImage);
Style ivStyle = iv.getAllStyles();
ivStyle.setBgTransparency(255);
ivStyle.setBgColor(0xff);
Label cropPreview = new Label();
hi.addAll(iv, cropPreview);
hi.show();
UITimer.timer(1000, true, () -> {
    cropPreview.setIcon(iv.getCroppedImage(0xff0000));
});

The first screenshot shows the default state which seems correct to me, the second screenshot shows a zoom and left pan. Notice that the pan is rendered to the screenshot but ignored by the image viewer. To zoom in the simulator switch out of single window mode and ctrl-right mouse button drag from top left to bottom right.

CodenameOne Screenshot 1

CodenameOne Screenshot 2
</issue_description>

Comments on the Issue (you are @copilot in this section)

Fixes #3804

💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.

Copilot AI changed the title [WIP] Pan of ImageViewer is saved in internal state and exposed via getCroppedImage() Fix ImageViewer getCroppedImage() inconsistency after panning operations Sep 25, 2025
Copilot AI requested a review from shannah September 25, 2025 22:17
@shannah shannah marked this pull request as ready for review September 25, 2025 23:36
@shannah
Copy link
Collaborator

shannah commented Sep 25, 2025

Tested locally, and this does seem to make the pan and zoom less flaky.

@shannah
Copy link
Collaborator

shannah commented Sep 25, 2025

2025-09-25.16-38-30.mov

@shannah shannah merged commit cf1d1b3 into master Sep 25, 2025
2 checks passed
@shannah shannah deleted the copilot/fix-f9ec4afc-13ea-47ef-99fb-fb83f4ccac07 branch September 25, 2025 23:43
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.

Pan of ImageViewer is saved in internal state and exposed via getCroppedImage()

2 participants