Skip to content

Commit 2147fb1

Browse files
akoch-yattaHeikoKlare
authored andcommitted
[win32] Use operations for transform manipulation
This commit refactors Transform in the win32 implementation to better support multiple handles for different zoom settings of a Transform when monitor-specific scaling is enabled. The previous implementation only applied adaptions to the initial handle of the transform and relied on the transform not to be changed afterwards. This doesn't cover all scenarios and can lead to unexpected behavior when re-using Transform objects over different zoom settings.
1 parent 4fecda7 commit 2147fb1

File tree

7 files changed

+404
-40
lines changed

7 files changed

+404
-40
lines changed

bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/TransformWin32Tests.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,21 @@ public void testShouldHaveDifferentHandlesAtDifferentZoomLevels() {
4040
@Test
4141
public void testScaledTrasformMustHaveScaledValues() {
4242
Display display = Display.getDefault();
43-
int zoom = DPIUtil.getDeviceZoom();
4443
Transform transform = new Transform(display, 0, 0, 0, 0, 4, 2);
4544
float[] elements = new float[6];
4645
transform.getElements(elements);
47-
long scaledHandle = transform.getHandle(zoom * 2);
48-
float[] scaledElements = new float[6];
49-
Gdip.Matrix_GetElements(scaledHandle, scaledElements);
50-
assertEquals(elements[4] * 2, scaledElements[4], 0);
51-
assertEquals(elements[5] * 2, scaledElements[5], 0);
46+
47+
long handle200 = transform.getHandle(200);
48+
float[] scaledElements200 = new float[6];
49+
Gdip.Matrix_GetElements(handle200, scaledElements200);
50+
assertEquals(elements[4] * 2, scaledElements200[4], 0);
51+
assertEquals(elements[5] * 2, scaledElements200[5], 0);
52+
53+
long handle300 = transform.getHandle(300);
54+
float[] scaledElements300 = new float[6];
55+
Gdip.Matrix_GetElements(handle300, scaledElements300);
56+
assertEquals(elements[4] * 3, scaledElements300[4], 0);
57+
assertEquals(elements[5] * 3, scaledElements300[5], 0);
5258
}
5359

5460
}

bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Transform.java

Lines changed: 162 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ public class Transform extends Resource {
4141

4242
private int initialZoom;
4343

44-
private HashMap<Integer, Long> zoomLevelToHandle = new HashMap<>();
44+
private Map<Integer, TransformHandle> zoomToHandle = new HashMap<>();
45+
46+
private List<Operation> operations = new ArrayList<>();
4547

4648
/**
4749
* Constructs a new identity Transform.
@@ -143,7 +145,7 @@ public Transform (Device device, float m11, float m12, float m21, float m22, flo
143145
long handle = Gdip.Matrix_new(m11, m12, m21, m22,
144146
DPIUtil.scaleUp(this.device, dx, initialZoom), DPIUtil.scaleUp(this.device, dy, initialZoom));
145147
if (handle == 0) SWT.error(SWT.ERROR_NO_HANDLES);
146-
zoomLevelToHandle.put(initialZoom, handle);
148+
zoomToHandle.put(initialZoom, new TransformHandle(handle, initialZoom));
147149
init();
148150
this.device.registerResourceWithZoomSupport(this);
149151
}
@@ -157,16 +159,17 @@ static float[] checkTransform(float[] elements) {
157159
@Override
158160
void destroy() {
159161
device.deregisterResourceWithZoomSupport(this);
160-
zoomLevelToHandle.values().forEach(Gdip::Matrix_delete);
161-
zoomLevelToHandle.clear();
162+
zoomToHandle.values().forEach(TransformHandle::destroy);
163+
zoomToHandle.clear();
164+
operations.clear();
162165
}
163166

164167
@Override
165168
void destroyHandlesExcept(Set<Integer> zoomLevels) {
166-
zoomLevelToHandle.entrySet().removeIf(entry -> {
169+
zoomToHandle.entrySet().removeIf(entry -> {
167170
final Integer zoom = entry.getKey();
168171
if (!zoomLevels.contains(zoom) && zoom != initialZoom) {
169-
Gdip.Matrix_delete(entry.getValue());
172+
entry.getValue().destroy();
170173
return true;
171174
}
172175
return false;
@@ -191,10 +194,12 @@ public void getElements(float[] elements) {
191194
if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
192195
if (elements == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
193196
if (elements.length < 6) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
194-
Gdip.Matrix_GetElements(getHandle(initialZoom), elements);
197+
TransformHandle transformHandle = getTransformHandle(initialZoom);
198+
Gdip.Matrix_GetElements(transformHandle.handle, elements);
195199
Drawable drawable = getDevice();
196-
elements[4] = DPIUtil.scaleDown(drawable, elements[4], initialZoom);
197-
elements[5] = DPIUtil.scaleDown(drawable, elements[5], initialZoom);
200+
int zoom = transformHandle.zoom;
201+
elements[4] = DPIUtil.scaleDown(drawable, elements[4], zoom);
202+
elements[5] = DPIUtil.scaleDown(drawable, elements[5], zoom);
198203
}
199204

200205
/**
@@ -209,7 +214,9 @@ public void getElements(float[] elements) {
209214
*/
210215
public void identity() {
211216
if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
212-
Gdip.Matrix_SetElements(getHandle(initialZoom), 1, 0, 0, 1, 0, 0);
217+
// identity invalidates all previous operations, so we remove them
218+
operations.clear();
219+
storeAndApplyOperationForAllHandles(new SetElementsOperation(1, 0, 0, 1, 0, 0));
213220
}
214221

215222
/**
@@ -223,7 +230,7 @@ public void identity() {
223230
*/
224231
public void invert() {
225232
if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
226-
if (Gdip.Matrix_Invert(getHandle(initialZoom)) != 0) SWT.error(SWT.ERROR_CANNOT_INVERT_MATRIX);
233+
storeAndApplyOperationForAllHandles(new InvertOperation());
227234
}
228235

229236
/**
@@ -238,7 +245,7 @@ public void invert() {
238245
*/
239246
@Override
240247
public boolean isDisposed() {
241-
return zoomLevelToHandle.isEmpty();
248+
return zoomToHandle.isEmpty();
242249
}
243250

244251
/**
@@ -249,7 +256,8 @@ public boolean isDisposed() {
249256
*/
250257
public boolean isIdentity() {
251258
if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
252-
return Gdip.Matrix_IsIdentity(getHandle(initialZoom));
259+
TransformHandle transformHandle = getTransformHandle(initialZoom);
260+
return Gdip.Matrix_IsIdentity(transformHandle.handle);
253261
}
254262

255263
/**
@@ -271,7 +279,7 @@ public void multiply(Transform matrix) {
271279
if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
272280
if (matrix == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
273281
if (matrix.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
274-
Gdip.Matrix_Multiply(getHandle(initialZoom), matrix.getHandle(initialZoom), Gdip.MatrixOrderPrepend);
282+
storeAndApplyOperationForAllHandles(new MultiplyOperation(matrix));
275283
}
276284

277285
/**
@@ -289,7 +297,7 @@ public void multiply(Transform matrix) {
289297
*/
290298
public void rotate(float angle) {
291299
if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
292-
Gdip.Matrix_Rotate(getHandle(initialZoom), angle, Gdip.MatrixOrderPrepend);
300+
storeAndApplyOperationForAllHandles(new RotateOperation(angle));
293301
}
294302

295303
/**
@@ -305,7 +313,7 @@ public void rotate(float angle) {
305313
*/
306314
public void scale(float scaleX, float scaleY) {
307315
if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
308-
Gdip.Matrix_Scale(getHandle(initialZoom), scaleX, scaleY, Gdip.MatrixOrderPrepend);
316+
storeAndApplyOperationForAllHandles(new ScaleOperation(scaleX, scaleY));
309317
}
310318

311319
/**
@@ -325,8 +333,9 @@ public void scale(float scaleX, float scaleY) {
325333
*/
326334
public void setElements(float m11, float m12, float m21, float m22, float dx, float dy) {
327335
if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
328-
Drawable drawable = getDevice();
329-
Gdip.Matrix_SetElements(getHandle(initialZoom), m11, m12, m21, m22, DPIUtil.scaleUp(drawable, dx, initialZoom), DPIUtil.scaleUp(drawable, dy, initialZoom));
336+
// setElements invalidates all previous operations, so we remove them
337+
operations.clear();
338+
storeAndApplyOperationForAllHandles(new SetElementsOperation(m11, m12, m21, m22, dx, dy));
330339
}
331340

332341
/**
@@ -344,7 +353,7 @@ public void setElements(float m11, float m12, float m21, float m22, float dx, fl
344353
*/
345354
public void shear(float shearX, float shearY) {
346355
if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
347-
Gdip.Matrix_Shear(getHandle(initialZoom), shearX, shearY, Gdip.MatrixOrderPrepend);
356+
storeAndApplyOperationForAllHandles(new ShearOperation(shearX, shearY));
348357
}
349358

350359
/**
@@ -364,14 +373,15 @@ public void shear(float shearX, float shearY) {
364373
public void transform(float[] pointArray) {
365374
if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
366375
if (pointArray == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
367-
int length = pointArray.length;
368376
Drawable drawable = getDevice();
377+
TransformHandle transformHandle = getTransformHandle(initialZoom);
378+
int length = pointArray.length;
369379
for (int i = 0; i < length; i++) {
370-
pointArray[i] = DPIUtil.scaleUp(drawable, pointArray[i], initialZoom);
380+
pointArray[i] = DPIUtil.scaleUp(drawable, pointArray[i], transformHandle.zoom);
371381
}
372-
Gdip.Matrix_TransformPoints(getHandle(initialZoom), pointArray, length / 2);
382+
Gdip.Matrix_TransformPoints(transformHandle.handle, pointArray, length / 2);
373383
for (int i = 0; i < length; i++) {
374-
pointArray[i] = DPIUtil.scaleDown(drawable, pointArray[i], initialZoom);
384+
pointArray[i] = DPIUtil.scaleDown(drawable, pointArray[i], transformHandle.zoom);
375385
}
376386
}
377387

@@ -388,8 +398,123 @@ public void transform(float[] pointArray) {
388398
*/
389399
public void translate(float offsetX, float offsetY) {
390400
if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
391-
Drawable drawable = getDevice();
392-
Gdip.Matrix_Translate(getHandle(initialZoom), DPIUtil.scaleUp(drawable, offsetX, initialZoom), DPIUtil.scaleUp(drawable, offsetY, initialZoom), Gdip.MatrixOrderPrepend);
401+
storeAndApplyOperationForAllHandles(new TranslateOperation(offsetX, offsetY));
402+
}
403+
404+
private record TransformHandle(long handle, int zoom) {
405+
void destroy() {
406+
Gdip.Matrix_delete(handle);
407+
}
408+
}
409+
410+
private record InvertOperation() implements Operation {
411+
@Override
412+
public void apply(TransformHandle transformHandle) {
413+
long handle = transformHandle.handle;
414+
if (Gdip.Matrix_Invert(handle) != 0) SWT.error(SWT.ERROR_CANNOT_INVERT_MATRIX);
415+
}
416+
}
417+
418+
private class MultiplyOperation implements Operation {
419+
private final float[] elements;
420+
421+
public MultiplyOperation(Transform matrix) {
422+
// As operation are executed on demand on new handles the passed matrix could
423+
// already be disposed. Therefore, we need to store the elements to restore
424+
// a temporary matrix for this operation on demand
425+
elements = new float[6];
426+
matrix.getElements(elements);
427+
}
428+
429+
@Override
430+
public void apply(TransformHandle transformHandle) {
431+
long handle = transformHandle.handle;
432+
int zoom = transformHandle.zoom;
433+
long newHandle = Gdip.Matrix_new(elements[0], elements[1], elements[2], elements[3], DPIUtil.scaleUp(elements[4], zoom), DPIUtil.scaleUp(elements[5], zoom));
434+
if (newHandle == 0) SWT.error(SWT.ERROR_NO_HANDLES);
435+
try {
436+
Gdip.Matrix_Multiply(handle, newHandle, Gdip.MatrixOrderPrepend);
437+
} finally {
438+
Gdip.Matrix_delete(newHandle);
439+
}
440+
}
441+
}
442+
443+
private record RotateOperation(float angle) implements Operation {
444+
@Override
445+
public void apply(TransformHandle transformHandle) {
446+
long handle = transformHandle.handle;
447+
Gdip.Matrix_Rotate(handle, angle, Gdip.MatrixOrderPrepend);
448+
}
449+
}
450+
451+
private record ScaleOperation(float scaleX, float scaleY) implements Operation {
452+
@Override
453+
public void apply(TransformHandle transformHandle) {
454+
long handle = transformHandle.handle;
455+
Gdip.Matrix_Scale(handle, scaleX, scaleY, Gdip.MatrixOrderPrepend);
456+
}
457+
}
458+
459+
private class SetElementsOperation implements Operation {
460+
private final float m11;
461+
private final float m12;
462+
private final float m21;
463+
private final float m22;
464+
private final float dx;
465+
private final float dy;
466+
467+
public SetElementsOperation(float m11, float m12, float m21, float m22, float dx, float dy) {
468+
this.m11 = m11;
469+
this.m12 = m12;
470+
this.m21 = m21;
471+
this.m22 = m22;
472+
this.dx = dx;
473+
this.dy = dy;
474+
}
475+
476+
@Override
477+
public void apply(TransformHandle transformHandle) {
478+
Drawable drawable = getDevice();
479+
long handle = transformHandle.handle;
480+
int zoom = transformHandle.zoom;
481+
Gdip.Matrix_SetElements(handle, m11, m12, m21, m22, DPIUtil.scaleUp(drawable, dx, zoom), DPIUtil.scaleUp(drawable, dy, zoom));
482+
}
483+
}
484+
485+
private record ShearOperation(float shearX, float shearY) implements Operation {
486+
@Override
487+
public void apply(TransformHandle transformHandle) {
488+
long handle = transformHandle.handle;
489+
Gdip.Matrix_Shear(handle, shearX, shearY, Gdip.MatrixOrderPrepend);
490+
}
491+
}
492+
493+
private class TranslateOperation implements Operation {
494+
private final float offsetX;
495+
private final float offsetY;
496+
497+
public TranslateOperation(float offsetX, float offsetY) {
498+
this.offsetX = offsetX;
499+
this.offsetY = offsetY;
500+
}
501+
502+
@Override
503+
public void apply(TransformHandle transformHandle) {
504+
Drawable drawable = getDevice();
505+
long handle = transformHandle.handle;
506+
int zoom = transformHandle.zoom;
507+
Gdip.Matrix_Translate(handle, DPIUtil.scaleUp(drawable, offsetX, zoom), DPIUtil.scaleUp(drawable, offsetY, zoom), Gdip.MatrixOrderPrepend);
508+
}
509+
}
510+
511+
private interface Operation {
512+
void apply(TransformHandle transformHandle);
513+
}
514+
515+
private void storeAndApplyOperationForAllHandles(Operation operation) {
516+
operations.add(operation);
517+
zoomToHandle.forEach((zoom, handle) -> operation.apply(handle));
393518
}
394519

395520
/**
@@ -405,17 +530,21 @@ public String toString() {
405530
getElements(elements);
406531
return "Transform {" + elements [0] + "," + elements [1] + "," +elements [2] + "," +elements [3] + "," +elements [4] + "," +elements [5] + "}";
407532
}
408-
409-
long getHandle(int zoomLevel) {
410-
if(zoomLevelToHandle.get(zoomLevel) == null) {
533+
private TransformHandle getTransformHandle(int zoom) {
534+
if(zoomToHandle.get(zoom) == null) {
411535
float[] elements = new float[6];
412536
getElements(elements);
413-
elements[4] = DPIUtil.scaleUp(device, DPIUtil.scaleDown(device, elements[4], initialZoom), zoomLevel);
414-
elements[5] = DPIUtil.scaleUp(device, DPIUtil.scaleDown(device, elements[5], initialZoom), zoomLevel);
415-
416-
zoomLevelToHandle.put(zoomLevel, Gdip.Matrix_new(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5]));
537+
elements[4] = DPIUtil.scaleUp(device, DPIUtil.scaleDown(device, elements[4], initialZoom), zoom);
538+
elements[5] = DPIUtil.scaleUp(device, DPIUtil.scaleDown(device, elements[5], initialZoom), zoom);
539+
long handle = Gdip.Matrix_new(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5]);
540+
TransformHandle transformHandle = new TransformHandle(handle, zoom);
541+
zoomToHandle.put(zoom, transformHandle);
542+
return transformHandle;
417543
}
418-
return zoomLevelToHandle.get(zoomLevel);
544+
return zoomToHandle.get(zoom);
419545
}
420546

547+
long getHandle(int zoomLevel) {
548+
return getTransformHandle(zoomLevel).handle;
549+
}
421550
}

examples/org.eclipse.swt.examples/src/examples_graphics.properties

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ Low=Low
104104
Default=Default
105105
High=High
106106
Transform=Transform
107+
TransformReuseOper=Transform Re-usage
107108
Cards=Cards
108109
Solid=Solid
109110
Dash=Dash
@@ -137,6 +138,9 @@ Text=Text
137138
Shapes=Shapes
138139
Next=Next
139140
Back=Back
141+
Shear=Shear
142+
Translate=Translate
143+
Identity=Identity
140144
AnimPathClipping=Animated Path Clipping
141145
##### ------------------ Tab Descriptions ------------------ #####
142146
AlphaDescription=This tab demonstrates alpha blending. It draws various shapes and images as alpha values change.
@@ -164,6 +168,7 @@ MazeDescription=This is a miscellaneous demonstration. Three objects move throu
164168
AnimPathClippingDesc=This tab demonstrates the use of path clipping. A clipping is the area onto which a drawing is made visible. The default clipping is the entire canvas (all the space in the right panel). However, one can set the clipping to be something different. In this tab, the clipping is the set of triangles, rectangles and the circle. When the blue rectangle is drawn, only the portions of the blue rectangle appearing in the clipping are visible.
165169
PathClippingDesc=This tab demonstrates the use of path clipping. A clipping is the area onto which a drawing is made visible. The default clipping is the entire canvas (all the space in the right panel). However, one can set the clipping to be something different.
166170
PathOperDescription=This tab demonstrates the use of paths. It allows the user to see the differences between filling, drawing and closing paths.
171+
TransformReuseOperDescription=This tab demonstrates the re-usage of transform. It allows the user to test with long living Transform instances.
167172
rgbDescription=Miscellaneous tab that demonstrates emerging colors from layering other colors.
168173
RegionClippingDescription=This tab demonstrates how to apply a region clipping and the effects of applying one. It also demonstrates the operations that can be applied between two regions.
169174
ShapesDescription=This tab draws 3D shapes (in 2D) using various line styles.

examples/org.eclipse.swt.examples/src/org/eclipse/swt/examples/graphics/GraphicsExample.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ GraphicsTab[] createTabs() {
462462
new ImageFlipTab(this),
463463
new ImageScaleTab(this),
464464
new PathTab(this),
465+
new TransformReuseTab(this),
465466
};
466467
}
467468

0 commit comments

Comments
 (0)