| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.swing.laf.flatlaf; |
| |
| import java.awt.Graphics; |
| import java.awt.Graphics2D; |
| import java.awt.geom.AffineTransform; |
| |
| public class HiDPIUtils { |
| |
| public interface HiDPIPainter { |
| void paint(Graphics2D g, int width, int height, double scale); |
| } |
| |
| /** |
| * Paint at scale factor 1x to avoid rounding issues at 125%, 150% and 175% scaling. |
| * <p> |
| * Scales the given Graphics2D down to 100% and invokes the |
| * given painter passing scaled deviceWidth and deviceHeight. |
| * <p> |
| * Transformation to device pixels borrowed from class |
| * {@link org.netbeans.swing.plaf.windows8.DPISafeBorder}. |
| */ |
| public static void paintAtScale1x(Graphics g0, int x, int y, int width, int height, HiDPIPainter painter) { |
| paintAtScale1x(g0, x, y, width, height, false, false, painter); |
| } |
| |
| /** |
| * @param roundXdown if true, round the starting X position down when converting to device |
| * pixels, otherwise round to the nearest device pixel position |
| * @param roundYdown if true, round the starting Y position down when converting to device |
| * pixels, otherwise round to the nearest device pixel position |
| */ |
| static void paintAtScale1x(Graphics g0, int x, int y, int width, int height, |
| boolean roundXdown, boolean roundYdown, HiDPIPainter painter) |
| { |
| Graphics2D g = (Graphics2D) g0; |
| final AffineTransform oldTransform = g.getTransform(); |
| g.translate(x, y); |
| final AffineTransform tx = g.getTransform(); |
| final int txType = tx.getType(); |
| /* On fractional DPI scaling factors, such as 150%, a logical pixel position, e.g. (5,0), |
| may end up being translated to a non-integral device pixel position, e.g. (7.5, 0). The same |
| goes for border thicknesses, which are specified in logical pixels. In this method, we do |
| all calculations and painting in device pixels, to avoid rounding errors causing visible |
| artifacts. On screens without HiDPI scaling, logical pixel values and device pixel values |
| are identical, and always integral (whole number) values. */ |
| if (txType == AffineTransform.TYPE_UNIFORM_SCALE || |
| txType == (AffineTransform.TYPE_UNIFORM_SCALE | AffineTransform.TYPE_TRANSLATION)) |
| { |
| // HiDPI scaling is active. |
| double scale = tx.getScaleX(); |
| int deviceX = (int) (roundXdown |
| ? Math.floor(tx.getTranslateX()) |
| : Math.round(tx.getTranslateX())); |
| int deviceY = (int) (roundYdown |
| ? Math.floor(tx.getTranslateY()) |
| : Math.round(tx.getTranslateY())); |
| /* Round the the end (bottom-right) position down, to ensure we are painting the |
| border in an area that will not be painted over by an adjacent component. */ |
| int deviceXend = (int) Math.floor(tx.getTranslateX() + width * scale); |
| int deviceYend = (int) Math.floor(tx.getTranslateY() + height * scale); |
| int deviceWidth = deviceXend - deviceX; |
| int deviceHeight = deviceYend - deviceY; |
| /* Deactivate the HiDPI scaling transform so we can do paint operations in the device |
| pixel coordinate system instead of in the logical coordinate system. */ |
| g.setTransform(new AffineTransform(1, 0, 0, 1, deviceX, deviceY)); |
| |
| painter.paint(g, deviceWidth, deviceHeight, scale); |
| } else { |
| painter.paint(g, width, height, 1); |
| } |
| |
| g.setTransform(oldTransform); |
| } |
| |
| /** |
| * Calculates the width of a border in device pixels. |
| * The difference to com.formdev.flatlaf.util.UIScale.scale() is that this method |
| * always rounds down the result, which gives nice small 1px borders at 150% and 175%. |
| * UIScale.scale() rounds up and would return 2px at 150% and 175%. |
| */ |
| public static int deviceBorderWidth(double scale, int logical) { |
| if (logical <= 0) { |
| return 0; |
| } |
| return Math.max(1, (int) (scale * logical)); |
| } |
| } |