From f5f6850172da77cdb553b3c5970d4b47b6f8700c Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sun, 5 Feb 2023 16:56:04 +0100 Subject: [PATCH] fixed `HiDPIUtils.paintAtScale1x()`, which painted at wrong location if graphics is rotated, is scaled and `x` or `y` parameters are not zero (issue #646) --- CHANGELOG.md | 3 + .../com/formdev/flatlaf/util/HiDPIUtils.java | 38 ++++---- .../testing/FlatPaintingHiDPITest.java | 86 ++++++++++++++----- .../flatlaf/testing/FlatPaintingHiDPITest.jfd | 79 ++++++++++++----- 4 files changed, 147 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99d6b0a2..5fbb62eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,9 @@ FlatLaf Change Log - Tree: Fixed truncated node text and too small painted non-wide node background if custom cell renderer sets icon, but not disabled icon, and tree is disabled. (issue #640) +- Fixed `HiDPIUtils.paintAtScale1x()`, which painted at wrong location if + graphics is rotated, is scaled and `x` or `y` parameters are not zero. (issue + #646) ## 3.0 diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java index 735a1120..2bbb44f5 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/HiDPIUtils.java @@ -52,40 +52,47 @@ public class HiDPIUtils AffineTransform t = g.getTransform(); // get scale X/Y and shear X/Y - double scaleX = t.getScaleX(); - double scaleY = t.getScaleY(); - double shearX = t.getShearX(); - double shearY = t.getShearY(); + final double scaleX = t.getScaleX(); + final double scaleY = t.getScaleY(); + final double shearX = t.getShearX(); + final double shearY = t.getShearY(); // check whether rotated // (also check for negative scale X/Y because shear X/Y are zero for 180 degrees rotation) boolean rotated = (shearX != 0 || shearY != 0 || scaleX <= 0 || scaleY <= 0); + + // calculate non rotated scale factors + final double realScaleX, realScaleY; if( rotated ) { // resulting scale X/Y values are always positive - scaleX = Math.hypot( scaleX, shearX ); - scaleY = Math.hypot( scaleY, shearY ); + realScaleX = Math.hypot( scaleX, shearX ); + realScaleY = Math.hypot( scaleY, shearY ); } else { // make scale X/Y positive - scaleX = Math.abs( scaleX ); - scaleY = Math.abs( scaleY ); + realScaleX = Math.abs( scaleX ); + realScaleY = Math.abs( scaleY ); } // check whether scaled - if( scaleX == 1 && scaleY == 1 ) { + if( realScaleX == 1 && realScaleY == 1 ) { painter.paint( g, x, y, width, height, 1 ); return; } + // calculate x and y (this is equal to t.translate( x, y )) + double px = (x * scaleX) + (y * shearX) + t.getTranslateX(); + double py = (y * scaleY) + (x * shearY) + t.getTranslateY(); + // scale rectangle - Rectangle2D.Double scaledRect = scale( scaleX, scaleY, t, x, y, width, height ); + Rectangle2D.Double scaledRect = scale( realScaleX, realScaleY, px, py, width, height ); try { // unscale to factor 1.0, keep rotation and move origin (to whole numbers) AffineTransform t1x; if( rotated ) { - t1x = new AffineTransform( t.getScaleX(), t.getShearY(), t.getShearX(), t.getScaleY(), + t1x = new AffineTransform( scaleX, shearY, shearX, scaleY, Math.floor( scaledRect.x ), Math.floor( scaledRect.y ) ); - t1x.scale( 1. / scaleX, 1. / scaleY ); + t1x.scale( 1. / realScaleX, 1. / realScaleY ); } else t1x = new AffineTransform( 1, 0, 0, 1, Math.floor( scaledRect.x ), Math.floor( scaledRect.y ) ); g.setTransform( t1x ); @@ -94,7 +101,7 @@ public class HiDPIUtils int sheight = (int) scaledRect.height; // paint - painter.paint( g, 0, 0, swidth, sheight, scaleX ); + painter.paint( g, 0, 0, swidth, sheight, realScaleX ); } finally { // restore original transform g.setTransform( t ); @@ -106,10 +113,7 @@ public class HiDPIUtils * sun.java2d.pipe.PixelToParallelogramConverter.fillRectangle(), * which is used by Graphics.fillRect(). */ - private static Rectangle2D.Double scale( double scaleX, double scaleY, AffineTransform t, int x, int y, int width, int height ) { - double px = (x * scaleX) + t.getTranslateX(); - double py = (y * scaleY) + t.getTranslateY(); - + private static Rectangle2D.Double scale( double scaleX, double scaleY, double px, double py, int width, int height ) { double newX = normalize( px ); double newY = normalize( py ); double newWidth = normalize( px + (width * scaleX) ) - newX; diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingHiDPITest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingHiDPITest.java index 20fc53c5..deb0b9d9 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingHiDPITest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingHiDPITest.java @@ -40,10 +40,13 @@ public class FlatPaintingHiDPITest FlatPaintingHiDPITest() { initComponents(); + reset(); sliderChanged(); } private void sliderChanged() { + painter.originX = originXSlider.getValue(); + painter.originY = originYSlider.getValue(); painter.translateX = translateXSlider.getValue(); painter.translateY = translateYSlider.getValue(); painter.scaleX = scaleXSlider.getValue(); @@ -78,15 +81,23 @@ public class FlatPaintingHiDPITest } private void reset() { + AffineTransform t = getGraphicsConfiguration().getDefaultTransform(); + + originXSlider.setValue( 20 ); + originYSlider.setValue( 10 ); translateXSlider.setValue( 100 ); translateYSlider.setValue( 100 ); - scaleXSlider.setValue( 100 ); - scaleYSlider.setValue( 100 ); + scaleXSlider.setValue( (int) (t.getScaleX() * 100) ); + scaleYSlider.setValue( (int) (t.getScaleY() * 100) ); rotateSlider.setValue( 0 ); } private void initComponents() { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents + JLabel originXLabel = new JLabel(); + originXSlider = new JSlider(); + JLabel originYLabel = new JLabel(); + originYSlider = new JSlider(); JLabel translateXLabel = new JLabel(); translateXSlider = new JSlider(); JLabel translateYLabel = new JLabel(); @@ -128,30 +139,58 @@ public class FlatPaintingHiDPITest "[400,fill]para" + "[grow,fill]", // rows - "[fill]" + + "[]" + + "[]" + + "[]" + "[]" + "[]" + "[]" + "[]" + "[grow]")); + //---- originXLabel ---- + originXLabel.setText("OriginX:"); + add(originXLabel, "cell 0 0"); + + //---- originXSlider ---- + originXSlider.setMaximum(500); + originXSlider.setMajorTickSpacing(100); + originXSlider.setMinorTickSpacing(25); + originXSlider.setValue(20); + originXSlider.setPaintTicks(true); + originXSlider.addChangeListener(e -> sliderChanged()); + add(originXSlider, "cell 1 0"); + + //---- originYLabel ---- + originYLabel.setText("OriginY:"); + add(originYLabel, "cell 0 1"); + + //---- originYSlider ---- + originYSlider.setMaximum(500); + originYSlider.setPaintLabels(true); + originYSlider.setPaintTicks(true); + originYSlider.setMajorTickSpacing(100); + originYSlider.setMinorTickSpacing(25); + originYSlider.setValue(10); + originYSlider.addChangeListener(e -> sliderChanged()); + add(originYSlider, "cell 1 1"); + //---- translateXLabel ---- translateXLabel.setText("TranslateX:"); - add(translateXLabel, "cell 0 0"); + add(translateXLabel, "cell 0 2"); //---- translateXSlider ---- translateXSlider.setMaximum(500); - translateXSlider.setPaintLabels(true); translateXSlider.setPaintTicks(true); translateXSlider.setMajorTickSpacing(100); translateXSlider.setMinorTickSpacing(25); translateXSlider.setValue(100); translateXSlider.addChangeListener(e -> sliderChanged()); - add(translateXSlider, "cell 1 0"); + add(translateXSlider, "cell 1 2"); //---- translateYLabel ---- translateYLabel.setText("TranslateY:"); - add(translateYLabel, "cell 0 1"); + add(translateYLabel, "cell 0 3"); //---- translateYSlider ---- translateYSlider.setMaximum(500); @@ -161,30 +200,29 @@ public class FlatPaintingHiDPITest translateYSlider.setMinorTickSpacing(25); translateYSlider.setValue(100); translateYSlider.addChangeListener(e -> sliderChanged()); - add(translateYSlider, "cell 1 1"); + add(translateYSlider, "cell 1 3"); //---- scaleXLabel ---- scaleXLabel.setText("ScaleX:"); - add(scaleXLabel, "cell 0 2"); + add(scaleXLabel, "cell 0 4"); //---- scaleXSlider ---- - scaleXSlider.setMaximum(300); + scaleXSlider.setMaximum(400); scaleXSlider.setValue(100); scaleXSlider.setPaintTicks(true); - scaleXSlider.setPaintLabels(true); scaleXSlider.setMajorTickSpacing(50); scaleXSlider.setSnapToTicks(true); scaleXSlider.setMinorTickSpacing(10); scaleXSlider.setMinimum(-100); scaleXSlider.addChangeListener(e -> sliderChanged()); - add(scaleXSlider, "cell 1 2"); + add(scaleXSlider, "cell 1 4"); //---- scaleYLabel ---- scaleYLabel.setText("ScaleY:"); - add(scaleYLabel, "cell 0 3"); + add(scaleYLabel, "cell 0 5"); //---- scaleYSlider ---- - scaleYSlider.setMaximum(300); + scaleYSlider.setMaximum(400); scaleYSlider.setValue(100); scaleYSlider.setPaintTicks(true); scaleYSlider.setPaintLabels(true); @@ -193,11 +231,11 @@ public class FlatPaintingHiDPITest scaleYSlider.setMinorTickSpacing(10); scaleYSlider.setMinimum(-100); scaleYSlider.addChangeListener(e -> sliderChanged()); - add(scaleYSlider, "cell 1 3"); + add(scaleYSlider, "cell 1 5"); //---- rotateLabel ---- rotateLabel.setText("Rotate:"); - add(rotateLabel, "cell 0 4"); + add(rotateLabel, "cell 0 6"); //---- rotateSlider ---- rotateSlider.setMaximum(360); @@ -207,7 +245,7 @@ public class FlatPaintingHiDPITest rotateSlider.setPaintLabels(true); rotateSlider.setPaintTicks(true); rotateSlider.addChangeListener(e -> sliderChanged()); - add(rotateSlider, "cell 1 4"); + add(rotateSlider, "cell 1 6"); //======== panel1 ======== { @@ -298,17 +336,19 @@ public class FlatPaintingHiDPITest cRotationDegreesLabel.setText("text"); panel1.add(cRotationDegreesLabel, "cell 1 6"); } - add(panel1, "cell 2 0 1 4"); + add(panel1, "cell 2 2 1 4,growy"); //---- resetButton ---- resetButton.setText("Reset"); resetButton.addActionListener(e -> reset()); - add(resetButton, "cell 2 4,align left bottom,grow 0 0"); - add(painter, "cell 0 5 3 1,grow,width 600,height 400"); + add(resetButton, "cell 2 6,align left bottom,grow 0 0"); + add(painter, "cell 0 7 3 1,grow,width 600,height 400"); // JFormDesigner - End of component initialization //GEN-END:initComponents } // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables + private JSlider originXSlider; + private JSlider originYSlider; private JSlider translateXSlider; private JSlider translateYSlider; private JSlider scaleXSlider; @@ -331,6 +371,8 @@ public class FlatPaintingHiDPITest public static class HiDPI1xPainter extends JComponent { + int originX; + int originY; int translateX; int translateY; int scaleX; @@ -361,10 +403,10 @@ public class FlatPaintingHiDPITest g2.rotate( Math.toRadians( rotate ) ); g2.setColor( Color.red ); - g2.fillRect( 0, 0, 100, 50 ); + g2.fillRect( originX, originY, 100, 50 ); g2.setColor( Color.green ); - HiDPIUtils.paintAtScale1x( g2, 0, 0, 100, 50, + HiDPIUtils.paintAtScale1x( g2, originX, originY, 100, 50, (g2d, x2, y2, width2, height2, scaleFactor) -> { g2d.fillRect( x2 + 10, y2 + 10, width2 - 20, height2 - 20 ); } ); diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingHiDPITest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingHiDPITest.jfd index 3a8b048d..2e989af3 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingHiDPITest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatPaintingHiDPITest.jfd @@ -9,36 +9,76 @@ new FormModel { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "hidemode 3" "$columnConstraints": "[fill][400,fill]para[grow,fill]" - "$rowConstraints": "[fill][][][][][grow]" + "$rowConstraints": "[][][][][][][][grow]" } ) { name: "this" "border": sfield com.jformdesigner.model.FormObject NULL_VALUE add( new FormComponent( "javax.swing.JLabel" ) { - name: "translateXLabel" - "text": "TranslateX:" + name: "originXLabel" + "text": "OriginX:" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 0" } ) add( new FormComponent( "javax.swing.JSlider" ) { - name: "translateXSlider" + name: "originXSlider" + "maximum": 500 + "majorTickSpacing": 100 + "minorTickSpacing": 25 + "value": 20 + "paintTicks": true + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "sliderChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "originYLabel" + "text": "OriginY:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + add( new FormComponent( "javax.swing.JSlider" ) { + name: "originYSlider" "maximum": 500 "paintLabels": true "paintTicks": true "majorTickSpacing": 100 "minorTickSpacing": 25 + "value": 10 + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "sliderChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 1" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "translateXLabel" + "text": "TranslateX:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 2" + } ) + add( new FormComponent( "javax.swing.JSlider" ) { + name: "translateXSlider" + "maximum": 500 + "paintTicks": true + "majorTickSpacing": 100 + "minorTickSpacing": 25 "value": 100 auxiliary() { "JavaCodeGenerator.variableLocal": false } addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "sliderChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 0" + "value": "cell 1 2" } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "translateYLabel" "text": "TranslateY:" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 1" + "value": "cell 0 3" } ) add( new FormComponent( "javax.swing.JSlider" ) { name: "translateYSlider" @@ -53,20 +93,19 @@ new FormModel { } addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "sliderChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 1" + "value": "cell 1 3" } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "scaleXLabel" "text": "ScaleX:" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 2" + "value": "cell 0 4" } ) add( new FormComponent( "javax.swing.JSlider" ) { name: "scaleXSlider" - "maximum": 300 + "maximum": 400 "value": 100 "paintTicks": true - "paintLabels": true "majorTickSpacing": 50 "snapToTicks": true "minorTickSpacing": 10 @@ -76,17 +115,17 @@ new FormModel { } addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "sliderChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 2" + "value": "cell 1 4" } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "scaleYLabel" "text": "ScaleY:" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 3" + "value": "cell 0 5" } ) add( new FormComponent( "javax.swing.JSlider" ) { name: "scaleYSlider" - "maximum": 300 + "maximum": 400 "value": 100 "paintTicks": true "paintLabels": true @@ -99,13 +138,13 @@ new FormModel { } addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "sliderChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 3" + "value": "cell 1 5" } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "rotateLabel" "text": "Rotate:" }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 4" + "value": "cell 0 6" } ) add( new FormComponent( "javax.swing.JSlider" ) { name: "rotateSlider" @@ -120,7 +159,7 @@ new FormModel { } addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "sliderChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 1 4" + "value": "cell 1 6" } ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "hidemode 3" @@ -264,14 +303,14 @@ new FormModel { "value": "cell 1 6" } ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 2 0 1 4" + "value": "cell 2 2 1 4,growy" } ) add( new FormComponent( "javax.swing.JButton" ) { name: "resetButton" "text": "Reset" addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "reset", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 2 4,align left bottom,grow 0 0" + "value": "cell 2 6,align left bottom,grow 0 0" } ) add( new FormComponent( "com.formdev.flatlaf.testing.FlatPaintingHiDPITest$HiDPI1xPainter" ) { name: "painter" @@ -279,11 +318,11 @@ new FormModel { "JavaCodeGenerator.variableLocal": false } }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 5 3 1,grow,width 600,height 400" + "value": "cell 0 7 3 1,grow,width 600,height 400" } ) }, new FormLayoutConstraints( null ) { "location": new java.awt.Point( 0, 0 ) - "size": new java.awt.Dimension( 685, 680 ) + "size": new java.awt.Dimension( 840, 680 ) } ) } }