From f708ac39ba2575b1f04541b1313ecfe467493cd6 Mon Sep 17 00:00:00 2001 From: Amartya Parijat Date: Tue, 16 Apr 2024 15:21:11 +0200 Subject: [PATCH] multi zoom level support for pattern for win32 This commit contributes to multi zoom level support of pattern using a map of zoom levels with relevant patterns. Contributes to #62 and #131 --- .../win32/org/eclipse/swt/graphics/GC.java | 10 +- .../org/eclipse/swt/graphics/Pattern.java | 139 +++++++++++------- .../swt/widgets/PatternWin32ManualTest.java | 71 +++++++++ 3 files changed, 165 insertions(+), 55 deletions(-) create mode 100644 tests/org.eclipse.swt.tests.win32/ManualTests/org/eclipse/swt/widgets/PatternWin32ManualTest.java diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java index 202e9a7e1f1..c4cfebd141c 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java @@ -198,9 +198,9 @@ void checkGC(int mask) { Pattern pattern = data.foregroundPattern; if (pattern != null) { if(data.alpha == 0xFF) { - brush = pattern.handle; + brush = pattern.getHandle(getZoom()); } else { - brush = data.gdipFgPatternBrushAlpha != 0 ? Gdip.Brush_Clone(data.gdipFgPatternBrushAlpha) : createAlphaTextureBrush(pattern.handle, data.alpha); + brush = data.gdipFgPatternBrushAlpha != 0 ? Gdip.Brush_Clone(data.gdipFgPatternBrushAlpha) : createAlphaTextureBrush(pattern.getHandle(getZoom()), data.alpha); data.gdipFgPatternBrushAlpha = brush; } if ((data.style & SWT.MIRRORED) != 0) { @@ -289,9 +289,9 @@ void checkGC(int mask) { Pattern pattern = data.backgroundPattern; if (pattern != null) { if(data.alpha == 0xFF) { - data.gdipBrush = pattern.handle; + data.gdipBrush = pattern.getHandle(getZoom()); } else { - long brush = data.gdipBgPatternBrushAlpha != 0 ? Gdip.Brush_Clone(data.gdipBgPatternBrushAlpha) : createAlphaTextureBrush(pattern.handle, data.alpha); + long brush = data.gdipBgPatternBrushAlpha != 0 ? Gdip.Brush_Clone(data.gdipBgPatternBrushAlpha) : createAlphaTextureBrush(pattern.getHandle(getZoom()), data.alpha); data.gdipBrush = data.gdipBgBrush /*= data.gdipBgPatternBrushAlpha */ = brush; } if ((data.style & SWT.MIRRORED) != 0) { @@ -3440,7 +3440,7 @@ public void getClipping (Region region) { } long getFgBrush() { - return data.foregroundPattern != null ? data.foregroundPattern.handle : data.gdipFgBrush; + return data.foregroundPattern != null ? data.foregroundPattern.getHandle(getZoom()) : data.gdipFgBrush; } /** diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Pattern.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Pattern.java index 38a9eb8629a..58ca4fc7f58 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Pattern.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Pattern.java @@ -13,6 +13,8 @@ *******************************************************************************/ package org.eclipse.swt.graphics; +import java.util.*; + import org.eclipse.swt.*; import org.eclipse.swt.internal.*; import org.eclipse.swt.internal.gdip.*; @@ -39,22 +41,18 @@ */ public class Pattern extends Resource { - /** - * the OS resource for the Pattern - * (Warning: This field is platform dependent) - *

- * IMPORTANT: This field is not part of the SWT - * public API. It is marked public only so that it can be shared - * within the packages provided by SWT. It is not available on all - * platforms and should never be accessed from application code. - *

- * - * @noreference This field is not intended to be referenced by clients. - */ - public long handle; + private int initialZoom; private Runnable bitmapDestructor; + // These are the possible fields with which a pattern can be initialized from the appropriate constructors. + private final Image image; + private float baseX1, baseY1, baseX2, baseY2; + private Color color1, color2; + private int alpha1, alpha2; + + private final Map zoomLevelToHandle = new HashMap<>(); + /** * Constructs a new Pattern given an image. Drawing with the resulting * pattern will cause the image to be tiled over the resulting area. @@ -88,22 +86,9 @@ public Pattern(Device device, Image image) { if (image == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); if (image.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); this.device.checkGDIP(); - long[] gdipImage = image.createGdipImage(); - long img = gdipImage[0]; - int width = Gdip.Image_GetWidth(img); - int height = Gdip.Image_GetHeight(img); - handle = Gdip.TextureBrush_new(img, Gdip.WrapModeTile, 0, 0, width, height); - bitmapDestructor = () -> { - Gdip.Bitmap_delete(img); - if (gdipImage[1] != 0) { - long hHeap = OS.GetProcessHeap (); - OS.HeapFree(hHeap, 0, gdipImage[1]); - } - }; - if (handle == 0) { - bitmapDestructor.run(); - SWT.error(SWT.ERROR_NO_HANDLES); - } + this.image = image; + initialZoom = DPIUtil.getDeviceZoom(); + setImageHandle(image, initialZoom); init(); } @@ -187,10 +172,36 @@ public Pattern(Device device, float x1, float y1, float x2, float y2, Color colo */ public Pattern(Device device, float x1, float y1, float x2, float y2, Color color1, int alpha1, Color color2, int alpha2) { super(device); - x1 = DPIUtil.autoScaleUp(x1); - y1 = DPIUtil.autoScaleUp(y1); - x2 = DPIUtil.autoScaleUp(x2); - y2 = DPIUtil.autoScaleUp(y2); + this.baseX1 = x1; + this.baseX2 = x2; + this.baseY1 = y1; + this.baseY2 = y2; + this.color1 = color1; + this.color2 = color2; + this.alpha1 = alpha1; + this.alpha2 = alpha2; + this.image = null; + initialZoom = DPIUtil.getDeviceZoom(); + initializeSize(initialZoom); +} + +long getHandle(int zoom) { + if (!this.zoomLevelToHandle.containsKey(zoom)) { + if (isImagePattern()) { + setImageHandle(image, zoom); + } else { + initializeSize(zoom); + } + } + return this.zoomLevelToHandle.get(zoom); +} + +private void initializeSize(int zoom) { + long handle; + float x1 = DPIUtil.autoScaleUp(this.baseX1, zoom); + float y1 = DPIUtil.autoScaleUp(this.baseY1, zoom); + float x2 = DPIUtil.autoScaleUp(this.baseX2, zoom); + float y2 = DPIUtil.autoScaleUp(this.baseY2, zoom); if (color1 == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); if (color1.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); if (color2 == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); @@ -221,27 +232,51 @@ public Pattern(Device device, float x1, float y1, float x2, float y2, Color colo Gdip.LinearGradientBrush_SetInterpolationColors(handle, new int [] {foreColor, midColor, backColor}, new float[]{0, 0.5f, 1}, 3); } } + this.zoomLevelToHandle.put(zoom, handle); init(); } +void setImageHandle(Image image, int zoom) { + long[] gdipImage = image.createGdipImage(zoom); + long img = gdipImage[0]; + int width = Gdip.Image_GetWidth(img); + int height = Gdip.Image_GetHeight(img); + long handle = Gdip.TextureBrush_new(img, Gdip.WrapModeTile, 0, 0, width, height); + bitmapDestructor = () -> { + Gdip.Bitmap_delete(img); + if (gdipImage[1] != 0) { + long hHeap = OS.GetProcessHeap (); + OS.HeapFree(hHeap, 0, gdipImage[1]); + } + }; + if (handle == 0) { + bitmapDestructor.run(); + SWT.error(SWT.ERROR_NO_HANDLES); + } else { + zoomLevelToHandle.put(zoom, handle); + } +} + @Override void destroy() { - int type = Gdip.Brush_GetType(handle); - switch (type) { - case Gdip.BrushTypeSolidColor: - Gdip.SolidBrush_delete(handle); - break; - case Gdip.BrushTypeHatchFill: - Gdip.HatchBrush_delete(handle); - break; - case Gdip.BrushTypeLinearGradient: - Gdip.LinearGradientBrush_delete(handle); - break; - case Gdip.BrushTypeTextureFill: - Gdip.TextureBrush_delete(handle); - break; + for (long handle: zoomLevelToHandle.values()) { + int type = Gdip.Brush_GetType(handle); + switch (type) { + case Gdip.BrushTypeSolidColor: + Gdip.SolidBrush_delete(handle); + break; + case Gdip.BrushTypeHatchFill: + Gdip.HatchBrush_delete(handle); + break; + case Gdip.BrushTypeLinearGradient: + Gdip.LinearGradientBrush_delete(handle); + break; + case Gdip.BrushTypeTextureFill: + Gdip.TextureBrush_delete(handle); + break; + } } - handle = 0; + zoomLevelToHandle.clear(); if (bitmapDestructor != null) { bitmapDestructor.run(); bitmapDestructor = null; @@ -260,7 +295,7 @@ void destroy() { */ @Override public boolean isDisposed() { - return handle == 0; + return zoomLevelToHandle.isEmpty(); } /** @@ -272,7 +307,11 @@ public boolean isDisposed() { @Override public String toString() { if (isDisposed()) return "Pattern {*DISPOSED*}"; - return "Pattern {" + handle + "}"; + return "Pattern {" + zoomLevelToHandle + "}"; +} + +private boolean isImagePattern() { + return image != null; } } diff --git a/tests/org.eclipse.swt.tests.win32/ManualTests/org/eclipse/swt/widgets/PatternWin32ManualTest.java b/tests/org.eclipse.swt.tests.win32/ManualTests/org/eclipse/swt/widgets/PatternWin32ManualTest.java new file mode 100644 index 00000000000..371e918f412 --- /dev/null +++ b/tests/org.eclipse.swt.tests.win32/ManualTests/org/eclipse/swt/widgets/PatternWin32ManualTest.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2024 Yatta Solutions + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Yatta Solutions - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Pattern; +import org.eclipse.swt.internal.DPIUtil; + +/* + * This Snippet tests a pattern at multiple zoom levels. + * + * It is difficult to test the pattern at multiple zoom level automatically since we also need + * to test the visual behavior. It is important to make sure that the pattern looks the same + * regardless of its size as per the zoom level. On the execution, 2 shells are + * opened at 2 different zoom levels. The pattern is a gradient of 2 colors. On both the + * shells, the pattern should be uniformly drawn and the size difference of both the patterns + * should be clearly visible without any visual difference except for the size. The size + * difference should be equal to the scalingFactor in the snippet. + * + */ +public class PatternWin32ManualTest { + private static Display display = Display.getDefault(); + + public static void main (String [] args) { + int zoom = DPIUtil.getDeviceZoom(); + int scalingFactor = 3; + int scaledZoom = zoom * scalingFactor; + int width = 400; + int height = 300; + final Pattern pat = new Pattern(display, 0, 0, width, height, new Color(null, 200, 200, 200), 0, new Color(null, 255, 0, 0), 255); + + Shell shell1 = createShellWithPattern(width, height, pat, zoom); + Shell shell2 = createShellWithPattern(width, height, pat, scaledZoom); + + while (!shell1.isDisposed() || !shell2.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } + } + pat.dispose(); + shell1.dispose(); + shell2.dispose(); + } + + private static Shell createShellWithPattern(int width, int height, final Pattern pat, int nativeZoom) { + Shell shell = new Shell(display); + shell.nativeZoom = nativeZoom; + shell.setText("Unscaled shell"); + shell.setSize(width, height); + shell.addPaintListener(e -> { + e.gc.setBackground(new Color(null, 100, 200, 0)); + e.gc.fillRectangle(0, 0, shell.getBounds().width, shell.getBounds().height); + e.gc.setBackground(new Color(null, 255, 0, 0)); + e.gc.setBackgroundPattern(pat); + e.gc.fillRectangle(0, 0, shell.getBounds().width, shell.getBounds().height); + }); + shell.open(); + return shell; + } +}