Skip to content

Conversation

arunjose696
Copy link
Contributor

@arunjose696 arunjose696 commented Sep 19, 2025

The DrawImage API with just destination coordinates now tries to load images at the requested size.
If the requested size is not available, it falls back to the closest available size by calculating the appropriate zoom.
Images are loadable at the target size if the image is created from an SVG stream or filename, or The image is created with an ImageDataProvider that is ImageDataAtSizeProvider.

This commit also introduces the ImageDataAtSizeProvider interface to support dynamic sizing.

With this changes we need to test the following

  1. The svgs are rendered at custom sizes without blurry scaling (this can be compared with current commit vs master)
  2. Drawing images with targetHeight and targetWidth with ImageDataAtSizeProvider (comparison can be made against ImageDataProvider vs ImageDataAtSizeProvider both at this commit)
    Steps to reproduce:
snippet 1 to check drawing svgs at custom sizes (click to show snippet) **Step 1**: Copy the eclipse.svg to same path where the snippets are and run the below snippet. **Step 2**: Run the snippet

package org.eclipse.swt.snippets;

package org.eclipse.swt.snippets;

import org.eclipse.swt.*;
import org.eclipse.swt.custom.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;

public class Snippet386 {

    public static void main(String[] args) {
        Display display = new Display();

        Image[] images = new Image[] {

            new Image(display, createImageFileNameProvider("src/org/eclipse/swt/snippets/eclipse.svg")),

        };

        String[] descriptions = new String[] {

            "ImageFileNameProvider with SVGs scaled by SVG rasterization"
        };

        Slice[] slices = {
        		 new Slice("Full", 0.0, 0.0, 1.0, 1.0),
                 new Slice("Top Half", 0.0, 0.0, 1.0, 0.5)
        };

        createShellWithImages(display, images, descriptions, slices, "Snippet 386 - Flipped Layout");
    }

    private static ImageFileNameProvider createImageFileNameProvider(String fileName) {
        return zoom -> {
        	if(zoom==100)
        	return fileName;
        	return null;};
    }

    static class Slice {
        String name;
        double xFrac, yFrac, wFrac, hFrac;
        Slice(String name, double x, double y, double w, double h) {
            this.name = name;
            this.xFrac = x;
            this.yFrac = y;
            this.wFrac = w;
            this.hFrac = h;
        }
    }

    private static void createShellWithImages(Display display, Image[] images, String[] descriptions, Slice[] slices, String title) {
        Shell shell = new Shell(display, SWT.SHELL_TRIM | SWT.MAX | SWT.RESIZE);
        shell.setText(title);
        shell.setLayout(new FillLayout());

        ScrolledComposite scrolledComposite = new ScrolledComposite(shell, SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER);
        Canvas canvas = new Canvas(scrolledComposite, SWT.DOUBLE_BUFFERED);
        scrolledComposite.setContent(canvas);
        scrolledComposite.setExpandHorizontal(true);
        scrolledComposite.setExpandVertical(true);

        int boxW = 1500, boxH = 1500, gap = 20;
        int titleHeight = 20, descHeight = 40, sliceHeaderHeight = 30;

        int rows = images.length;
        int cols = slices.length;

        int canvasWidth = (boxW + gap) * cols + gap;
        int canvasHeight = (boxH + titleHeight + gap) * rows + descHeight + sliceHeaderHeight;
        canvas.setSize(canvasWidth, canvasHeight);
        scrolledComposite.setMinSize(canvasWidth, canvasHeight);

        canvas.addListener(SWT.Paint, e -> {
            // Column headers
            for (int col = 0; col < cols; col++) {
                int x = col * (boxW + gap) + gap;
                Font font = new Font(display, "Arial", 18, SWT.NORMAL);
                e.gc.setFont(font);
                e.gc.drawText(slices[col].name, x, 0, true);
                font.dispose();

            }

            // Rows
            for (int row = 0; row < rows; row++) {
                Image image = images[row];
                Rectangle rect = image.getBounds();
                int y = row * (boxH + titleHeight + gap) + descHeight + sliceHeaderHeight;

                Font font = new Font(display, "Arial", 18, SWT.NORMAL);
                e.gc.setFont(font);
                e.gc.drawText(descriptions[row], 0, y - 10, true);
                font.dispose();

                for (int col = 0; col < cols; col++) {
                    Slice s = slices[col];
                    int x = col * (boxW + gap) + gap;

                    int boxTop = y + titleHeight;
                    e.gc.drawRectangle(x, boxTop, (int) Math.round(boxW / s.wFrac), (int) Math.round(boxH / s.hFrac));

                    e.gc.drawImage(image, x, boxTop, (int) Math.round(boxW / s.wFrac), (int) Math.round(boxH / s.hFrac));
                }
            }
        });

        shell.setMaximized(true);
        shell.open();

        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) display.sleep();
        }

        for (Image img : images) img.dispose();
        display.dispose();
    }
}


before

after
**snippet 2** Drawing images with ImageDataAtSizeProvider

package org.eclipse.swt.snippets;

package org.eclipse.swt.snippets;

import org.eclipse.swt.*;
import org.eclipse.swt.custom.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;

public class Snippet386 {

    public static void main(String[] args) {
        Display display = new Display();

        Image[] images = new Image[] {
            new Image(display, createImageDataProvider()),
            new Image(display, createImageDataAtSizeProvider())
        };

        String[] descriptions = new String[] {

            "ImageDataProvider with fixed font size for a given zoom",
            "ImageDataAtSizeProvider which scales font size based on target height and width"
        };

        Slice[] slices = {
            new Slice("Full", 0.0, 0.0, 1.0, 1.0),
            new Slice("Top Half", 0.0, 0.0, 1.0, 0.5),
            new Slice("Bottom Half", 0.0, 0.5, 1.0, 0.5),
            new Slice("Left Half", 0.0, 0.0, 0.5, 1.0),
            new Slice("Right Half", 0.5, 0.0, 0.5, 1.0),
            new Slice("Top-Left Quarter", 0.0, 0.0, 0.5, 0.5)
        };

        createShellWithImages(display, images, descriptions, slices, "Snippet 386 - Flipped Layout");
    }



    private static ImageDataProvider createImageDataProvider() {
        return new ImageDataProvider() {
            @Override
            public ImageData getImageData(int zoomLevel) {
                int scaleFactor = zoomLevel / 100;
                return createScaledTextImageData(100 * scaleFactor, 100 * scaleFactor);
            }
        };
    }

    private static ImageDataProvider createImageDataAtSizeProvider() {
        return new ImageDataAtSizeProvider() {
            @Override
            public ImageData getImageData(int zoomLevel) {
                int scaleFactor = zoomLevel / 100;
                return createScaledTextImageData(100 * scaleFactor, 100 * scaleFactor);
            }

            @Override
            public ImageData getImageData(int width, int height) {
                return createScaledTextImageData(width, height);
            }
        };
    }

    private static ImageData createScaledTextImageData(int width, int height) {
        Display display = Display.getDefault();
        String text = "abcd";

        int fontSize = Math.max(1, height / 100);
        Font font = new Font(display, "Arial", fontSize, SWT.NORMAL);

        Image tmp = new Image(display, 1, 1);
        GC measureGC = new GC(tmp);
        measureGC.setFont(font);
        Point textExtent = measureGC.textExtent(text);
        measureGC.dispose();
        tmp.dispose();

        double scale = Math.min((double) width / textExtent.x, (double) height / textExtent.y);
        font.dispose();
        font = new Font(display, "Arial", Math.max(1, (int) (fontSize * scale)), SWT.NORMAL);

        Image image = new Image(display, width, height);
        GC gc = new GC(image);
        gc.setFont(font);
        gc.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
        gc.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
        gc.fillRectangle(image.getBounds());

        gc.setLineWidth(Math.max(1, width / 20));
        gc.drawLine(0, 0, width / 2, height);

        Point newTextExtent = gc.textExtent(text);
        gc.drawText(text, (width - newTextExtent.x) / 2, (height - newTextExtent.y) / 2, true);

        gc.dispose();
        ImageData data = image.getImageData();

        image.dispose();
        font.dispose();

        return data;
    }

    static class Slice {
        String name;
        double xFrac, yFrac, wFrac, hFrac;
        Slice(String name, double x, double y, double w, double h) {
            this.name = name;
            this.xFrac = x;
            this.yFrac = y;
            this.wFrac = w;
            this.hFrac = h;
        }
    }

    private static void createShellWithImages(Display display, Image[] images, String[] descriptions, Slice[] slices, String title) {
        Shell shell = new Shell(display, SWT.SHELL_TRIM | SWT.MAX | SWT.RESIZE);
        shell.setText(title);
        shell.setLayout(new FillLayout());

        ScrolledComposite scrolledComposite = new ScrolledComposite(shell, SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER);
        Canvas canvas = new Canvas(scrolledComposite, SWT.DOUBLE_BUFFERED);
        scrolledComposite.setContent(canvas);
        scrolledComposite.setExpandHorizontal(true);
        scrolledComposite.setExpandVertical(true);

        int boxW = 500, boxH = 500, gap = 20;
        int titleHeight = 20, descHeight = 40, sliceHeaderHeight = 30;

        int rows = images.length;
        int cols = slices.length;

        int canvasWidth = (boxW + gap) * cols + gap;
        int canvasHeight = (boxH + titleHeight + gap) * rows + descHeight + sliceHeaderHeight;
        canvas.setSize(canvasWidth, canvasHeight);
        scrolledComposite.setMinSize(canvasWidth, canvasHeight);

        canvas.addListener(SWT.Paint, e -> {
            // Column headers
            for (int col = 0; col < cols; col++) {
                int x = col * (boxW + gap) + gap;
                Font font = new Font(display, "Arial", 18, SWT.NORMAL);
                e.gc.setFont(font);
                e.gc.drawText(slices[col].name, x, 0, true);
                font.dispose();

            }

            // Rows
            for (int row = 0; row < rows; row++) {
                Image image = images[row];
                Rectangle rect = image.getBounds();
                int y = row * (boxH + titleHeight + gap) + descHeight + sliceHeaderHeight;

                Font font = new Font(display, "Arial", 18, SWT.NORMAL);
                e.gc.setFont(font);
                e.gc.drawText(descriptions[row], 0, y - 10, true);
                font.dispose();

                for (int col = 0; col < cols; col++) {
                    Slice s = slices[col];
                    int x = col * (boxW + gap) + gap;

                    int srcX = (int) (rect.width * s.xFrac);
                    int srcY = (int) (rect.height * s.yFrac);
                    int srcW = (int) (rect.width * s.wFrac);
                    int srcH = (int) (rect.height * s.hFrac);

                    int boxTop = y + titleHeight;
                    e.gc.drawRectangle(x, boxTop, boxW, boxH);


                    e.gc.drawImage(image, x, boxTop, boxW, boxH);

                }
            }
        });

        shell.setMaximized(true);
        shell.open();

        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) display.sleep();
        }

        for (Image img : images) img.dispose();
        display.dispose();
    }
}
image

This depends on #2514 and #2509, Only last commit is relevant to this PR

Copy link
Contributor

github-actions bot commented Sep 19, 2025

Test Results

  118 files  ±0    118 suites  ±0   12m 4s ⏱️ +59s
4 583 tests ±0  4 547 ✅ ±0  36 💤 ±0  0 ❌ ±0 
  330 runs  ±0    307 ✅ ±0  23 💤 ±0  0 ❌ ±0 

Results for commit b238dec. ± Comparison against base commit 749ed1a.

♻️ This comment has been updated with latest results.

@arunjose696 arunjose696 force-pushed the arunjose696/326/imageAtSizeProvider branch 7 times, most recently from 01766e0 to fa4a774 Compare September 22, 2025 12:44
@arunjose696 arunjose696 force-pushed the arunjose696/326/imageAtSizeProvider branch 3 times, most recently from 8cf1bfb to 063d70b Compare September 23, 2025 07:59
@arunjose696 arunjose696 force-pushed the arunjose696/326/imageAtSizeProvider branch from 063d70b to a9b9404 Compare September 24, 2025 15:22
@arunjose696 arunjose696 marked this pull request as ready for review September 25, 2025 09:35
@arunjose696 arunjose696 force-pushed the arunjose696/326/imageAtSizeProvider branch 4 times, most recently from 82c03f5 to daf247e Compare September 26, 2025 14:35
Copy link
Contributor

@HeikoKlare HeikoKlare left a comment

Choose a reason for hiding this comment

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

I scanned the PR and commented on some obvious or essential points I came across.

@arunjose696 arunjose696 force-pushed the arunjose696/326/imageAtSizeProvider branch 3 times, most recently from e7af9a1 to d25c48a Compare September 29, 2025 17:12
@arunjose696 arunjose696 force-pushed the arunjose696/326/imageAtSizeProvider branch 2 times, most recently from 0e9ab55 to d3575ff Compare September 30, 2025 08:20
@arunjose696 arunjose696 marked this pull request as draft September 30, 2025 09:08
@arunjose696 arunjose696 force-pushed the arunjose696/326/imageAtSizeProvider branch 2 times, most recently from b59aa2d to 582685a Compare September 30, 2025 09:37
@arunjose696 arunjose696 marked this pull request as ready for review September 30, 2025 09:38
@arunjose696 arunjose696 force-pushed the arunjose696/326/imageAtSizeProvider branch from 582685a to 67d9379 Compare September 30, 2025 09:40
@arunjose696 arunjose696 changed the title Adding DynamicallySizableImageDataProvider which enables loading images at custom sizes GC#DrawImage(image, destX, destY, destWidth, destHeight) now tries to load images at the requested destination size before drawing Sep 30, 2025
@arunjose696 arunjose696 force-pushed the arunjose696/326/imageAtSizeProvider branch from 67d9379 to 38f10a0 Compare September 30, 2025 14:35
Copy link
Contributor

@HeikoKlare HeikoKlare left a comment

Choose a reason for hiding this comment

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

Thank you for the latest cleanups. They made the code much easier to understand.
Reading the code again I see that, if I am not mistaken, it breaks the existing (implicit) contract for the cache image data, such that it would like lead to a regression. That's why I request changes to avoid that we break existing behavior,
In addition, all the changes related to creating a temporary handle based on zoom for executing a drawing operation on look far more complicated than they need to be to me. I wrote a longer comment trying to explain what in my opinion can/should be done to improve that.

@arunjose696 arunjose696 force-pushed the arunjose696/326/imageAtSizeProvider branch 3 times, most recently from 30de03f to fe7048d Compare October 1, 2025 15:15
@arunjose696 arunjose696 requested a review from HeikoKlare October 1, 2025 15:16
@arunjose696 arunjose696 force-pushed the arunjose696/326/imageAtSizeProvider branch 4 times, most recently from 3875d39 to d07939a Compare October 1, 2025 16:15
Copy link
Contributor

@akoch-yatta akoch-yatta left a comment

Choose a reason for hiding this comment

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

Changes look good to me and I didn't find any regression testing it with the Runtime or the CustomControlExample that already use the new GC API that triggers the changed logic

@akoch-yatta akoch-yatta force-pushed the arunjose696/326/imageAtSizeProvider branch from d07939a to db2d773 Compare October 1, 2025 16:55
@akoch-yatta akoch-yatta force-pushed the arunjose696/326/imageAtSizeProvider branch 2 times, most recently from 8c1661a to 627d661 Compare October 1, 2025 17:00
…en possible.

The DrawImage API with just destination coordinates now tries to load images at the requested size.
If the requested size is not available, it falls back to the closest available size by calculating the appropriate zoom.
Images are loadable at the target size if the image is created from an SVG stream or filename, or The image is created with an ImageDataProvider that is ImageDataAtSizeProvider.

This commit also introduces the ImageDataAtSizeProvider interface to support dynamic sizing.

Co-authored-by: Michael Bangas <[email protected]>
@HeikoKlare HeikoKlare force-pushed the arunjose696/326/imageAtSizeProvider branch from 627d661 to b238dec Compare October 1, 2025 18:39
@akoch-yatta akoch-yatta merged commit 7a53f1b into eclipse-platform:master Oct 1, 2025
17 checks passed
@akoch-yatta akoch-yatta deleted the arunjose696/326/imageAtSizeProvider branch October 2, 2025 07:27
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.

Adding DynamicallySizableImageDataProvider which enables loading images at custom sizes
3 participants