Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JDK-8294680: Refactor scaled border rendering #11571

Closed
wants to merge 18 commits into from

Conversation

honkar-jdk
Copy link
Contributor

@honkar-jdk honkar-jdk commented Dec 7, 2022

In this fix, the common code required for scaled border rendering is unified and added to SwingUtilities3. This is achieved by adding a functional interface to SwingUtilities3 and calling the the respective paintBorder function passed as functional parameter to the method.

Following are the usual steps for any border scaling -

  • Resetting transform.
  • Calculating new width, height, x & y-translations.
  • Perform the required border rendering.
  • And at the end restore the previous transform.

To test the refactored code, 3 separate border scaling instances were taken (details below) and the SwingUtilities3.paintBorder, (containing the common functionality) was applied. All the tests associated with the respective border changes pass.

  1. EtchedBorder - PR#7449 - Test: ScaledEtchedBorderTest.java
  2. LineBorder - PR#10681 - Test: ScaledLineBorderTest & ScaledTextFieldBorderTest.java
  3. JInternalFrame Border - PR#10274 - Test: InternalFrameBorderTest.java

The advantage of this solution is - it avoids code repetition and can be reused across all the border classes requiring border scaling fix.


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Issue

Reviewers

Contributors

  • Alexey Ivanov <aivanov@openjdk.org>

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk pull/11571/head:pull/11571
$ git checkout pull/11571

Update a local copy of the PR:
$ git checkout pull/11571
$ git pull https://git.openjdk.org/jdk pull/11571/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 11571

View PR using the GUI difftool:
$ git pr show -t 11571

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/11571.diff

Sorry, something went wrong.

@bridgekeeper
Copy link

bridgekeeper bot commented Dec 7, 2022

👋 Welcome back honkar! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk openjdk bot added the rfr Pull request is ready for review label Dec 7, 2022
@openjdk
Copy link

openjdk bot commented Dec 7, 2022

@honkar-jdk The following label will be automatically applied to this pull request:

  • client

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

@openjdk openjdk bot added the client client-libs-dev@openjdk.org label Dec 7, 2022
@honkar-jdk
Copy link
Contributor Author

/contributor add @aivanov-jdk

@openjdk
Copy link

openjdk bot commented Dec 7, 2022

@honkar-jdk
Contributor Alexey Ivanov <aivanov@openjdk.org> successfully added.

@mlbridge
Copy link

mlbridge bot commented Dec 7, 2022

Webrevs

@mlbridge
Copy link

mlbridge bot commented Dec 8, 2022

Mailing list message from Alan Snyder on client-libs-dev:

Is there some reason why this should not be a public API?

Do you believe that only JDK-implemented borders need this functionality?

@mlbridge
Copy link

mlbridge bot commented Dec 10, 2022

Mailing list message from Harshitha Onkar on client-libs-dev:

Hi Alan,

Thank you for the questions. Please find the answers below.

Is there some reason why this should not be a public API?

We are still testing out this code. Since each border is different (apart from the common steps required for border scaling issue), the solution must be well-baked,
tested thoroughly and extensively before it becomes a part of public API.
After more borders in JDK are updated and the refactored solution is considered robust & stable, the question of whether to make this utility
method a public API can be discussed again. At this time, it might be too early for it.

Do you believe that only JDK-implemented borders need this functionality?

The current solution is in its initial stage and we haven't yet determined what applications may or may not need this functionality.
Additionally we haven't updated all the JDK borders yet, so we might encounter changes (if any) that may require iterative update to the current solution.
Do you have a specific use case in mind?

Thanks & Regards,
Harshitha Onkar

-----Original Message-----
From: client-libs-dev <client-libs-dev-retn at openjdk.org> On Behalf Of Alan Snyder
Sent: Wednesday, December 7, 2022 1:53 PM
To: Harshitha Onkar <honkar at openjdk.org>
Cc: client-libs-dev at openjdk.org
Subject: Re: RFR: JDK-8294680: Refactor scaled border rendering

Is there some reason why this should not be a public API?

Do you believe that only JDK-implemented borders need this functionality?

int height = 0;

if (resetTransform) {
double xx = at.getScaleX() * x + at.getTranslateX();
Copy link
Member

Choose a reason for hiding this comment

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

Can we combine this code block with the above if (resetTransform) at line 173 ? or did you keep it separate for a reason?

Copy link
Contributor Author

@honkar-jdk honkar-jdk Dec 12, 2022

Choose a reason for hiding this comment

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

@rajamah In-order to paint the border even when g is not a Graphics2D object I have added the resetTransform check here again and additionally to keep the if & else block close-by, which is easier to understand rather than having the blocks separated.

Copy link
Member

Choose a reason for hiding this comment

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

sure, makes sense. Thanks

Copy link
Member

Choose a reason for hiding this comment

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

Still, I think it makes sense to combine this block with the above one.

If resetTransform is true, the transform is reset and variables are set to the values as calculated in this block; otherwise, the variables are initialised with the default values.

resetTransform = ((at.getShearX() == 0) && (at.getShearY() == 0));

if (resetTransform) {
g2d.setTransform(new AffineTransform());
Copy link
Member

Choose a reason for hiding this comment

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

Can we keep the original comment about this:
/* Deactivate the HiDPI scaling transform,
* so we can do paint operations in the device
* pixel coordinate system instead of the logical coordinate system.
*/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure. I can add it.

Copy link
Member

@aivanov-jdk aivanov-jdk Jan 4, 2023

Choose a reason for hiding this comment

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

I believe @rajamah meant to put this comment before resetting the transform to explain why it's done.

/* Deactivate the HiDPI scaling transform,
* so we can do paint operations in the device
* pixel coordinate system instead of the logical coordinate system.
*/
g2d.setTransform(new AffineTransform());

The original comment above for resetTransform variable could also be preserved, it explains why we don't reset the transform when shear and/or rotation are used.

@mlbridge
Copy link

mlbridge bot commented Dec 11, 2022

Mailing list message from Alan Snyder on client-libs-dev:

On Dec 9, 2022, at 10:18 AM, Harshitha Onkar <harshitha.onkar at oracle.com> wrote:

Do you have a specific use case in mind?

No, but as a developer of a third party LAF and various custom Swing components I have had to duplicate ?private? Swing code multiple times, so I would like to encourage the view that Swing is an open set of components and LAFs and that the built in components and LAFs should not be considered special or privileged.

For amusement, see the JDK class AquaTabbedPaneCopyFromBasicUI where private Swing code was duplicated by Apple and remains.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/client-libs-dev/attachments/20221210/21b34089/attachment.htm>

@mlbridge
Copy link

mlbridge bot commented Dec 11, 2022

Mailing list message from Michael Hall on client-libs-dev:

On Dec 10, 2022, at 10:48 AM, Alan Snyder <javalists at cbfiddle.com> wrote:

On Dec 9, 2022, at 10:18 AM, Harshitha Onkar <harshitha.onkar at oracle.com <mailto:harshitha.onkar at oracle.com>> wrote:

Do you have a specific use case in mind?

No, but as a developer of a third party LAF and various custom Swing components I have had to duplicate ?private? Swing code multiple times, so I would like to encourage the view that Swing is an open set of components and LAFs and that the built in components and LAFs should not be considered special or privileged.

For amusement, see the JDK class AquaTabbedPaneCopyFromBasicUI where private Swing code was duplicated by Apple and remains.

As I remember a large part of the OS/X port project related to the Aqua LAF. A large undertaking where they probably made as minimal changes as possible and avoided re-inventing of wheels.
So sort of ironic but not that hard to believe.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/client-libs-dev/attachments/20221210/773502bf/attachment-0001.htm>

@mlbridge
Copy link

mlbridge bot commented Dec 12, 2022

Mailing list message from Andy Goryachev on client-libs-dev:

I second Alan's sentiment. If a helpful utility method is called multiple times from Swing code, it does make sense to make publicly available - chances are it is also useful to the application or third party component developers.

Copying code from Swing has a legal aspect - it probably makes the third party code GPL-d, which is probably not what the stakeholders want.

-andy

From: client-libs-dev <client-libs-dev-retn at openjdk.org> on behalf of Alan Snyder <javalists at cbfiddle.com>
Date: Saturday, 2022/12/10 at 08:48
To: Harshitha Onkar <harshitha.onkar at oracle.com>
Cc: client-libs-dev at openjdk.org <client-libs-dev at openjdk.org>
Subject: Re: RFR: JDK-8294680: Refactor scaled border rendering

On Dec 9, 2022, at 10:18 AM, Harshitha Onkar <harshitha.onkar at oracle.com<mailto:harshitha.onkar at oracle.com>> wrote:

Do you have a specific use case in mind?

No, but as a developer of a third party LAF and various custom Swing components I have had to duplicate ?private? Swing code multiple times, so I would like to encourage the view that Swing is an open set of components and LAFs and that the built in components and LAFs should not be considered special or privileged.

For amusement, see the JDK class AquaTabbedPaneCopyFromBasicUI where private Swing code was duplicated by Apple and remains.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/client-libs-dev/attachments/20221212/76a3131d/attachment.htm>

* so we can do paint operations in the device
* pixel coordinate system instead of the logical coordinate system.
*/
resetTransform = ((at.getShearX() == 0) && (at.getShearY() == 0));
Copy link
Member

Choose a reason for hiding this comment

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

@rajamah's code in LineBorder.java used a better condition: if the scale is 1, resetting transform can be safely skipped.

// if m01 or m10 is non-zero, then there is a rotation or shear
// or if no Scaling enabled,
// skip resetting the transform
boolean resetTransform = ((at.getShearX() == 0) && (at.getShearY() == 0)) &&
((at.getScaleX() > 1) || (at.getScaleY() > 1));

resetTransform = ((at.getShearX() == 0) && (at.getShearY() == 0));

if (resetTransform) {
g2d.setTransform(new AffineTransform());
Copy link
Member

@aivanov-jdk aivanov-jdk Jan 4, 2023

Choose a reason for hiding this comment

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

I believe @rajamah meant to put this comment before resetting the transform to explain why it's done.

/* Deactivate the HiDPI scaling transform,
* so we can do paint operations in the device
* pixel coordinate system instead of the logical coordinate system.
*/
g2d.setTransform(new AffineTransform());

The original comment above for resetTransform variable could also be preserved, it explains why we don't reset the transform when shear and/or rotation are used.

Comment on lines 177 to 179
stkWidth = c instanceof JInternalFrame ?
clipRound(scaleFactor) : (int) Math.floor(scaleFactor);
g2d.setStroke(new BasicStroke((float) stkWidth));
Copy link
Member

Choose a reason for hiding this comment

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

This is supposed to be a generic helper. If strokeWidth depends on the type of component, it's better to handle it in each component separately.

EtchedBorder uses the stoke, LineBorder doesn't use it; however, it uses the value.

So, my suggestion is to calculate the strokeWidth (can't we spell it out, it's easier to read this way, isn't it?) as Math.floor(scaleFactor). The stroke itself isn't created, nor is set here.

The stroke can still be restored here as a convenience. What do others think?

This reverts commit abed51b.
@honkar-jdk
Copy link
Contributor Author

Since strokeWidth is set differently in each case, it has been moved from common code to individual classes.

@honkar-jdk
Copy link
Contributor Author

@aivanov-jdk Please review the updated changes.

if (resetTransform) {
Graphics2D g2d = (Graphics2D) g;
g2d.setTransform(at);
g2d.setStroke(oldStk);
Copy link
Member

Choose a reason for hiding this comment

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

The stroke should always be restored even if the transform wasn't reset. Or it should be done in the implementation of paintUnscaledBorder.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@aivanov-jdk Earlier strokeWidth was set when resetTransform was true and it was restored on the same condition. Now since the stroke width is moved to individual implementation of paintUnscaledBorder, it make sense to restore it outside of resetTranform.

To avoid missing this step, I have restored it in common code than in individual paintUnscaledBorder implementations.

@@ -24,6 +24,8 @@
*/
package javax.swing.border;

import com.sun.java.swing.SwingUtilities3;
Copy link
Member

Choose a reason for hiding this comment

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

Let's be consistent: it should go after java.* and javax.* packages.

Either there should be a blank line before com.* or sun.* imports or there shouldn't be.

Comment on lines 155 to 156
private void paintUnscaledBorder(Component c, Graphics g, int x, int y,
int w, int h, double scaleFactor) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
private void paintUnscaledBorder(Component c, Graphics g, int x, int y,
int w, int h, double scaleFactor) {
private void paintUnscaledBorder(Component c, Graphics g, int x, int y,
int w, int h, double scaleFactor) {

One more space is missing to align.

Copy link
Member

@aivanov-jdk aivanov-jdk left a comment

Choose a reason for hiding this comment

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

Looks good to me.

@openjdk
Copy link

openjdk bot commented Jan 13, 2023

@honkar-jdk This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8294680: Refactor scaled border rendering

Co-authored-by: Alexey Ivanov <aivanov@openjdk.org>
Reviewed-by: rmahajan, achung, aivanov, jdv

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 154 new commits pushed to the master branch:

  • b317658: 8300399: EdDSA does not verify when there is no message
  • d85243f: 8300647: Miscellaneous hashCode improvements in java.base
  • 453dbd1: 8298377: JfrVframeStream causes deadlocks in ZGC
  • 1084fd2: 8299973: Replace NULL with nullptr in share/utilities/
  • dea58ef: 8300119: CgroupMetrics.getTotalMemorySize0() can report invalid results on 32 bit systems
  • 2e4a3c4: 8293862: javax/swing/JFileChooser/8046391/bug8046391.java failed with 'Cannot invoke "java.awt.Image.getWidth(java.awt.image.ImageObserver)" because "retVal" is null'
  • e326b86: 8300042: Improve CPU related JFR events descriptions
  • 11df6eb: 8300540: Serial: Remove obsolete comments in GenMarkSweep
  • 8f7faa6: 8300447: Parallel: Refactor PSPromotionManager::drain_stacks_depth
  • eba87a0: 8300264: Remove metaprogramming/isPointer.hpp
  • ... and 144 more: https://git.openjdk.org/jdk/compare/43847c43ad2e84999554468f79016dd33528ec14...master

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Jan 13, 2023
Comment on lines 146 to 148
/**
* Used within border classes to add specific border implementation details.
*/
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/**
* Used within border classes to add specific border implementation details.
*/
/**
* A task which paints an <i>unscaled</i> border after {@code Graphics}
* transforms are removed. It's used with the
* {@link #paintBorder(Component, Graphics, int, int, int, int, UnscaledBorderPainter)
* SwingUtilities3.paintBorder} which manages changing the transforms and calculating the
* coordinates and size of the border.
*/

*/
@FunctionalInterface
public interface UnscaledBorderPainter {
void paintUnscaledBorder(Component c, Graphics g,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
void paintUnscaledBorder(Component c, Graphics g,
/**
* Paints the border for the specified component after the
* {@code Graphics} transforms are removed.
*
* <p>
* The <i>x</i> and <i>y</i> of the painted border are zero.
*
* @param c the component for which this border is being painted
* @param g the paint graphics
* @param w the width of the painted border, in physical pixels
* @param h the height of the painted border, in physical pixels
* @param scaleFactor the scale that was in the {@code Graphics}
*
* @see #paintBorder(Component, Graphics, int, int, int, int, UnscaledBorderPainter) SwingUtilities3.paintBorder
* @see javax.swing.border.Border#paintBorder(Component, Graphics, int, int, int, int) Border.paintBorder
*/
void paintUnscaledBorder(Component c, Graphics g,

Comment on lines 156 to 159
/**
* Performs common scaling transformation steps required for rendering
* the border correctly at different scales.
*/
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/**
* Performs common scaling transformation steps required for rendering
* the border correctly at different scales.
*/
/**
* Paints the border for a component ensuring its sides have consistent
* thickness at different scales.
* <p>
* It performs the following steps:
* <ol>
* <li>Reset the scale transform on the {@code Graphics},</li>
* <li>Call {@code painter} to paint the border,</li>
* <li>Restore the transform.</li>
* </ol>
*
* @param c the component for which this border is being painted
* @param g the paint graphics
* @param x the x position of the painted border
* @param y the y position of the painted border
* @param w the width of the painted border
* @param h the height of the painted border
* @param painter the painter object which paints the border after
* the transform on the {@code Graphics} is reset
*/


// Step 1: Reset Transform
AffineTransform at = null;
Stroke oldStk = null;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Stroke oldStk = null;
Stroke oldStroke = null;

Let's spell the word in full. It's used twice in the method and I see no reason to abbreviate it.

@honkar-jdk
Copy link
Contributor Author

/integrate

@openjdk
Copy link

openjdk bot commented Jan 19, 2023

Going to push as commit 80ab50b.
Since your change was applied there have been 154 commits pushed to the master branch:

  • b317658: 8300399: EdDSA does not verify when there is no message
  • d85243f: 8300647: Miscellaneous hashCode improvements in java.base
  • 453dbd1: 8298377: JfrVframeStream causes deadlocks in ZGC
  • 1084fd2: 8299973: Replace NULL with nullptr in share/utilities/
  • dea58ef: 8300119: CgroupMetrics.getTotalMemorySize0() can report invalid results on 32 bit systems
  • 2e4a3c4: 8293862: javax/swing/JFileChooser/8046391/bug8046391.java failed with 'Cannot invoke "java.awt.Image.getWidth(java.awt.image.ImageObserver)" because "retVal" is null'
  • e326b86: 8300042: Improve CPU related JFR events descriptions
  • 11df6eb: 8300540: Serial: Remove obsolete comments in GenMarkSweep
  • 8f7faa6: 8300447: Parallel: Refactor PSPromotionManager::drain_stacks_depth
  • eba87a0: 8300264: Remove metaprogramming/isPointer.hpp
  • ... and 144 more: https://git.openjdk.org/jdk/compare/43847c43ad2e84999554468f79016dd33528ec14...master

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Jan 19, 2023
@openjdk openjdk bot closed this Jan 19, 2023
@openjdk openjdk bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review labels Jan 19, 2023
@openjdk
Copy link

openjdk bot commented Jan 19, 2023

@honkar-jdk Pushed as commit 80ab50b.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

@openjdk
Copy link

openjdk bot commented Aug 8, 2023

@lawrence-andrew The command contributor can only be used in open pull requests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
client client-libs-dev@openjdk.org integrated Pull request has been integrated
Development

Successfully merging this pull request may close these issues.

None yet

6 participants