Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public class Pattern extends Resource {

private final Map<Integer, PatternHandle> zoomToHandle = new HashMap<>();

private boolean isDestroyed;
private boolean disposed;

/**
* Constructs a new Pattern given an image. Drawing with the resulting
Expand Down Expand Up @@ -167,6 +167,10 @@ 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);
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);
if (color2.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
this.baseX1 = x1;
this.baseX2 = x2;
this.baseY1 = y1;
Expand All @@ -180,18 +184,8 @@ public Pattern(Device device, float x1, float y1, float x2, float y2, Color colo
this.device.registerResourceWithZoomSupport(this);
}

private PatternHandle newPatternHandle(int zoom) {
if (image != null) {
return new ImagePatternHandle(zoom);
}
return new BasePatternHandle(zoom);
}

private PatternHandle getPatternHandle(int zoom) {
if (!zoomToHandle.containsKey(zoom)) {
zoomToHandle.put(zoom, newPatternHandle(zoom));
}
return zoomToHandle.get(zoom);
return zoomToHandle.computeIfAbsent(zoom, z -> image != null ? new ImagePatternHandle(z) : new BasePatternHandle(z));
}

long getHandle(int zoom) {
Expand All @@ -203,7 +197,7 @@ void destroy() {
device.deregisterResourceWithZoomSupport(this);
zoomToHandle.values().forEach(PatternHandle::destroy);
zoomToHandle.clear();
this.isDestroyed = true;
disposed = true;
}

@Override
Expand Down Expand Up @@ -237,7 +231,7 @@ Pattern copy() {
*/
@Override
public boolean isDisposed() {
return isDestroyed;
return disposed;
}

/**
Expand All @@ -252,8 +246,16 @@ public String toString() {
return "Pattern {" + zoomToHandle + "}";
}

/**
* Converts a Win32 COLORREF value (0x00BBGGRR) and an alpha byte into a
* GDI+ ARGB color value (0xAARRGGBB).
*/
private static int colorRefToArgb(int colorRef, int alpha) {
return ((alpha & 0xFF) << 24) | ((colorRef >> 16) & 0xFF) | (colorRef & 0xFF00) | ((colorRef & 0xFF) << 16);
}

private class BasePatternHandle extends PatternHandle {
public BasePatternHandle(int zoom) {
BasePatternHandle(int zoom) {
super(zoom);
}

Expand All @@ -264,19 +266,17 @@ long createHandle(int zoom) {
float y1 = Win32DPIUtils.pointToPixel(baseY1, zoom);
float x2 = Win32DPIUtils.pointToPixel(baseX2, zoom);
float y2 = Win32DPIUtils.pointToPixel(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);
if (color2.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
device.checkGDIP();
int colorRef1 = color1.handle;
int foreColor = ((alpha1 & 0xFF) << 24) | ((colorRef1 >> 16) & 0xFF) | (colorRef1 & 0xFF00) | ((colorRef1 & 0xFF) << 16);
int foreColor = colorRefToArgb(colorRef1, alpha1);
if (x1 == x2 && y1 == y2) {
handle = Gdip.SolidBrush_new(foreColor);
if (handle == 0) SWT.error(SWT.ERROR_NO_HANDLES);
} else {
int colorRef2 = color2.handle;
int backColor = ((alpha2 & 0xFF) << 24) | ((colorRef2 >> 16) & 0xFF) | (colorRef2 & 0xFF00) | ((colorRef2 & 0xFF) << 16);
int backColor = colorRefToArgb(colorRef2, alpha2);
PointF p1 = new PointF();
p1.X = x1;
p1.Y = y1;
Expand All @@ -287,7 +287,7 @@ long createHandle(int zoom) {
if (handle == 0) SWT.error(SWT.ERROR_NO_HANDLES);
if (alpha1 != 0xFF || alpha2 != 0xFF) {
int a = (int)((alpha1 & 0xFF) * 0.5f + (alpha2 & 0xFF) * 0.5f);
int r = (int)(((colorRef1 & 0xFF) >> 0) * 0.5f + ((colorRef2 & 0xFF) >> 0) * 0.5f);
int r = (int)((colorRef1 & 0xFF) * 0.5f + (colorRef2 & 0xFF) * 0.5f);
int g = (int)(((colorRef1 & 0xFF00) >> 8) * 0.5f + ((colorRef2 & 0xFF00) >> 8) * 0.5f);
int b = (int)(((colorRef1 & 0xFF0000) >> 16) * 0.5f + ((colorRef2 & 0xFF0000) >> 16) * 0.5f);
int midColor = a << 24 | r << 16 | g << 8 | b;
Expand All @@ -301,7 +301,7 @@ long createHandle(int zoom) {
private class ImagePatternHandle extends PatternHandle {
private Image.GdipImage gdipImage;

public ImagePatternHandle(int zoom) {
ImagePatternHandle(int zoom) {
super(zoom);
}

Expand Down Expand Up @@ -336,27 +336,18 @@ private void cleanupBitmap() {
private abstract class PatternHandle {
private final long handle;

public PatternHandle(int zoom) {
PatternHandle(int zoom) {
this.handle = createHandle(zoom);
}

abstract long createHandle(int zoom);

protected 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;
switch (Gdip.Brush_GetType(handle)) {
case Gdip.BrushTypeSolidColor -> Gdip.SolidBrush_delete(handle);
case Gdip.BrushTypeHatchFill -> Gdip.HatchBrush_delete(handle);
case Gdip.BrushTypeLinearGradient -> Gdip.LinearGradientBrush_delete(handle);
case Gdip.BrushTypeTextureFill -> Gdip.TextureBrush_delete(handle);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
Test_org_eclipse_swt_graphics_ImageLoaderEvent.class, //
Test_org_eclipse_swt_graphics_PaletteData.class, //
Test_org_eclipse_swt_graphics_Path.class, //
Test_org_eclipse_swt_graphics_Pattern.class, //
Test_org_eclipse_swt_graphics_Point.class, //
Test_org_eclipse_swt_graphics_RGB.class, //
Test_org_eclipse_swt_graphics_RGBA.class, //
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*******************************************************************************
* Copyright (c) 2026 Vector Informatik GmbH and others.
*
* 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
*******************************************************************************/
package org.eclipse.swt.tests.junit;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Pattern;
import org.eclipse.swt.widgets.Display;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

/**
* Automated Test Suite for class org.eclipse.swt.graphics.Pattern
*
* @see org.eclipse.swt.graphics.Pattern
*/
public class Test_org_eclipse_swt_graphics_Pattern {

private Display display;

@BeforeEach
public void setUp() {
display = Display.getDefault();
}

@AfterEach
public void tearDown() {
// display is shared; do not dispose
}

// --- Image-based constructor ---

@Test
public void test_Constructor_image_valid() {
Image image = new Image(display, 10, 10);
try {
Pattern pattern = new Pattern(display, image);
assertFalse(pattern.isDisposed(), "Newly constructed Pattern must not be disposed");
pattern.dispose();
} finally {
image.dispose();
}
}

@Test
public void test_Constructor_image_nullImage() {
assertThrows(IllegalArgumentException.class, () -> new Pattern(display, (Image) null));
}

@Test
public void test_Constructor_image_disposedImage() {
Image image = new Image(display, 10, 10);
image.dispose();
assertThrows(IllegalArgumentException.class, () -> new Pattern(display, image));
}

// --- Gradient constructors ---

@Test
public void test_Constructor_gradient_valid() {
Color red = display.getSystemColor(SWT.COLOR_RED);
Color blue = display.getSystemColor(SWT.COLOR_BLUE);
Pattern pattern = new Pattern(display, 0, 0, 100, 100, red, blue);
assertFalse(pattern.isDisposed(), "Newly constructed Pattern must not be disposed");
pattern.dispose();
}

@Test
public void test_Constructor_gradientAlpha_valid() {
Color red = display.getSystemColor(SWT.COLOR_RED);
Color blue = display.getSystemColor(SWT.COLOR_BLUE);
Pattern pattern = new Pattern(display, 0, 0, 100, 100, red, 128, blue, 255);
assertFalse(pattern.isDisposed(), "Newly constructed Pattern must not be disposed");
pattern.dispose();
}

@Test
public void test_Constructor_gradient_nullColor1() {
Color blue = display.getSystemColor(SWT.COLOR_BLUE);
assertThrows(IllegalArgumentException.class,
() -> new Pattern(display, 0, 0, 100, 100, null, blue));
}

@Test
public void test_Constructor_gradient_nullColor2() {
Color red = display.getSystemColor(SWT.COLOR_RED);
assertThrows(IllegalArgumentException.class,
() -> new Pattern(display, 0, 0, 100, 100, red, null));
}

@Test
public void test_Constructor_gradientAlpha_nullColor1() {
Color blue = display.getSystemColor(SWT.COLOR_BLUE);
assertThrows(IllegalArgumentException.class,
() -> new Pattern(display, 0, 0, 100, 100, null, 255, blue, 255));
}

@Test
public void test_Constructor_gradientAlpha_nullColor2() {
Color red = display.getSystemColor(SWT.COLOR_RED);
assertThrows(IllegalArgumentException.class,
() -> new Pattern(display, 0, 0, 100, 100, red, 255, null, 255));
}

@Test
public void test_Constructor_gradient_disposedColor1() {
Color red = new Color(display, 255, 0, 0);
Color blue = display.getSystemColor(SWT.COLOR_BLUE);
red.dispose();
assertThrows(IllegalArgumentException.class,
() -> new Pattern(display, 0, 0, 100, 100, red, blue));
}

@Test
public void test_Constructor_gradient_disposedColor2() {
Color red = display.getSystemColor(SWT.COLOR_RED);
Color blue = new Color(display, 0, 0, 255);
blue.dispose();
assertThrows(IllegalArgumentException.class,
() -> new Pattern(display, 0, 0, 100, 100, red, blue));
}

@Test
public void test_Constructor_gradientAlpha_disposedColor1() {
Color red = new Color(display, 255, 0, 0);
Color blue = display.getSystemColor(SWT.COLOR_BLUE);
red.dispose();
assertThrows(IllegalArgumentException.class,
() -> new Pattern(display, 0, 0, 100, 100, red, 255, blue, 255));
}

@Test
public void test_Constructor_gradientAlpha_disposedColor2() {
Color red = display.getSystemColor(SWT.COLOR_RED);
Color blue = new Color(display, 0, 0, 255);
blue.dispose();
assertThrows(IllegalArgumentException.class,
() -> new Pattern(display, 0, 0, 100, 100, red, 255, blue, 255));
}

// --- isDisposed ---

@Test
public void test_isDisposed() {
Color red = display.getSystemColor(SWT.COLOR_RED);
Color blue = display.getSystemColor(SWT.COLOR_BLUE);
Pattern pattern = new Pattern(display, 0, 0, 100, 100, red, blue);

assertFalse(pattern.isDisposed(), "Pattern must not be disposed before dispose() is called");
pattern.dispose();
assertTrue(pattern.isDisposed(), "Pattern must be disposed after dispose() is called");
}

@Test
public void test_dispose_twice() {
Color red = display.getSystemColor(SWT.COLOR_RED);
Color blue = display.getSystemColor(SWT.COLOR_BLUE);
Pattern pattern = new Pattern(display, 0, 0, 100, 100, red, blue);

// Disposing twice must not throw
pattern.dispose();
pattern.dispose();
assertTrue(pattern.isDisposed());
}

// --- toString ---

@Test
public void test_toString() {
Color red = display.getSystemColor(SWT.COLOR_RED);
Color blue = display.getSystemColor(SWT.COLOR_BLUE);
Pattern pattern = new Pattern(display, 0, 0, 100, 100, red, blue);

String s = pattern.toString();
assertNotNull(s, "toString() must not return null");
assertFalse(s.isEmpty(), "toString() must not return an empty string");

pattern.dispose();
s = pattern.toString();
assertNotNull(s, "toString() on disposed Pattern must not return null");
assertFalse(s.isEmpty(), "toString() on disposed Pattern must not return an empty string");
}

}
Loading