IDEA-180409 initial support for fractional scale HiDPI

This commit is contained in:
Anton Tarasov
2017-10-11 14:50:11 +03:00
parent 0adf988a99
commit a4b2916186
5 changed files with 112 additions and 59 deletions
@@ -86,10 +86,10 @@ public class IconUtil {
int imageWidth = ImageUtil.getRealWidth(image);
int imageHeight = ImageUtil.getRealHeight(image);
final int w = Math.min(imageWidth, maxWidth);
final int h = Math.min(imageHeight, maxHeight);
maxWidth = maxWidth == Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)ceil(maxWidth * scale);
maxHeight = maxHeight == Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)ceil(maxHeight * scale);
final int w = Math.min(imageWidth, maxWidth);
final int h = Math.min(imageHeight, maxHeight);
final BufferedImage img = UIUtil.createImage(g, w, h, Transparency.TRANSLUCENT);
final int offX = imageWidth > maxWidth ? (imageWidth - maxWidth) / 2 : 0;
@@ -16,9 +16,12 @@
package com.intellij.openapi.ui.impl;
import com.intellij.util.IconUtil;
import com.intellij.util.ui.ImageUtil;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.JBUI.ScaleContext;
import com.intellij.util.ui.JBUI.ScaleContextSupport;
import com.intellij.util.ui.JBUI.ScaleContextAware;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
@@ -107,25 +110,51 @@ public class ShadowPainter extends ScaleContextSupport<ScaleContext> {
myTopLeft.paintIcon(c, g, x, y);
myTopRight.paintIcon(c, g, x + width - myTopRight.getIconWidth(), y);
myBottomRight.paintIcon(c, g, x + width - myBottomRight.getIconWidth(), y + height - myBottomRight.getIconHeight());
myBottomLeft.paintIcon(c, g, x, y + height - myBottomLeft.getIconHeight());
myBottomRight.paintIcon(c, g, x + width - myBottomRight.getIconWidth(), y + height - myBottomRight.getIconHeight());
for (int _x = myTopLeft.getIconWidth(); _x < width - myTopRight.getIconWidth(); _x++) {
myCroppedTop.paintIcon(c, g, _x + x, y);
}
for (int _x = myBottomLeft.getIconWidth(); _x < width - myBottomLeft.getIconWidth(); _x++) {
myCroppedBottom.paintIcon(c, g, _x + x, y + height - bottomSize);
}
for (int _y = myTopLeft.getIconHeight(); _y < height - myBottomLeft.getIconHeight(); _y++) {
myCroppedLeft.paintIcon(c, g, x, _y + y);
}
for (int _y = myTopRight.getIconHeight(); _y < height - myBottomRight.getIconHeight(); _y++) {
myCroppedRight.paintIcon(c, g, x + width - rightSize, _y + y);
}
fill(g, myCroppedTop, x, y, myTopLeft.getIconWidth(), width - myTopRight.getIconWidth(), true);
fill(g, myCroppedBottom, x, y + height - bottomSize, myBottomLeft.getIconWidth(), width - myBottomRight.getIconWidth(), true);
fill(g, myCroppedLeft, x, y, myTopLeft.getIconHeight(), height - myBottomLeft.getIconHeight(), false);
fill(g, myCroppedRight, x + width - rightSize, y, myTopRight.getIconHeight(), height - myBottomRight.getIconHeight(), false);
if (myBorderColor != null) {
g.setColor(myBorderColor);
g.drawRect(x + leftSize - 1, y + topSize - 1, width - leftSize - rightSize + 1, height - topSize - bottomSize + 1);
}
}
private static void fill(Graphics g, Icon pattern, int x, int y, int from, int to, boolean horizontally) {
double scale = JBUI.sysScale((Graphics2D)g);
if (UIUtil.isJreHiDPIEnabled() && Math.ceil(scale) > scale) {
// Direct painting for fractional scale
BufferedImage img = ImageUtil.toBufferedImage(IconUtil.toImage(pattern));
int patternSize = horizontally ? img.getWidth() : img.getHeight();
Graphics2D g2d = (Graphics2D)g.create();
try {
g2d.scale(1 / scale, 1 / scale);
g2d.translate(x * scale, y * scale);
for (int at = (int)Math.floor(from * scale); at < to * scale; at += patternSize) {
if (horizontally) {
g2d.drawImage(img, at, 0, null);
}
else {
g2d.drawImage(img, 0, at, null);
}
}
} finally {
g2d.dispose();
}
}
else {
for (int at = from; at < to; at++) {
if (horizontally) {
pattern.paintIcon(null, g, x + at, y);
}
else {
pattern.paintIcon(null, g, x, y + at);
}
}
}
}
}
@@ -333,12 +333,12 @@ public class ImageLoader implements Serializable {
if (w <= 0 || h <= 0) {
return image;
}
int width = (int)Math.ceil(scale * w);
int height = (int)Math.ceil(scale * h);
int width = (int)Math.round(scale * w);
int height = (int)Math.round(scale * h);
// Using "QUALITY" instead of "ULTRA_QUALITY" results in images that are less blurry
// because ultra quality performs a few more passes when scaling, which introduces blurriness
// when the scaling factor is relatively small (i.e. <= 3.0f) -- which is the case here.
return Scalr.resize(ImageUtil.toBufferedImage(image), Scalr.Method.QUALITY, width, height);
return Scalr.resize(ImageUtil.toBufferedImage(image), Scalr.Method.QUALITY, Scalr.Mode.FIT_EXACT, width, height, null);
}
@Nullable
@@ -25,7 +25,7 @@ import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import static java.lang.Math.ceil;
import static java.lang.Math.round;
/**
* @author Konstantin Bulenkov
@@ -152,7 +152,7 @@ public class JBHiDPIScaledImage extends BufferedImage {
}
JBHiDPIScaledImage newImg = new JBHiDPIScaledImage(newUserWidth, newUserHeight, getType());
Graphics2D g = newImg.createGraphics();
g.drawImage(scaled, 0, 0, (int)ceil(newUserWidth), (int)ceil(newUserHeight),
g.drawImage(scaled, 0, 0, (int)round(newUserWidth), (int)round(newUserHeight),
0, 0, scaled.getWidth(null), scaled.getHeight(null), null);
g.dispose();
return newImg;
@@ -213,7 +213,7 @@ public class JBHiDPIScaledImage extends BufferedImage {
* @return the width
*/
public int getUserWidth(ImageObserver observer) {
return myImage != null ? (int)ceil(myUserWidth) : (int)ceil(super.getWidth(observer) / myScale);
return myImage != null ? (int)round(myUserWidth) : (int)round(super.getWidth(observer) / myScale);
}
/**
@@ -223,7 +223,7 @@ public class JBHiDPIScaledImage extends BufferedImage {
* @return the height
*/
public int getUserHeight(ImageObserver observer) {
return myImage != null ? (int)ceil(myUserHeight) : (int)ceil(super.getHeight(observer) / myScale);
return myImage != null ? (int)round(myUserHeight) : (int)round(super.getHeight(observer) / myScale);
}
/**
@@ -61,6 +61,7 @@ import java.awt.*;
import java.awt.event.*;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.RoundRectangle2D;
import java.awt.im.InputContext;
import java.awt.image.BufferedImage;
@@ -2056,10 +2057,8 @@ public class UIUtil {
dw = dstBounds.width;
dh = dstBounds.height;
}
if (dw == -1 && dh == -1) {
dw = ImageUtil.getUserWidth(image);
dh = ImageUtil.getUserHeight(image);
}
boolean dstSizeUndefined = (dw == -1 && dh == -1);
int sx = 0;
int sy = 0;
int sw = -1;
@@ -2070,11 +2069,21 @@ public class UIUtil {
sw = srcBounds.width;
sh = srcBounds.height;
}
if (sw == -1 && sh == -1) {
sw = ImageUtil.getRealWidth(image);
sh = ImageUtil.getRealHeight(image);
boolean srcSizeUndefined = (sw == -1 && sh == -1);
if (dstSizeUndefined && srcSizeUndefined) {
drawImage(g, image, null, dx, dy, -1, -1, observer);
} else {
if (dstSizeUndefined) {
dw = ImageUtil.getUserWidth(image);
dh = ImageUtil.getUserHeight(image);
}
if (srcSizeUndefined) {
sw = ImageUtil.getRealWidth(image);
sh = ImageUtil.getRealHeight(image);
}
g.drawImage(drawImage, dx, dy, dx + dw, dy + dh, sx, sy, sx + sw, sy + sh, observer);
}
g.drawImage(drawImage, dx, dy, dx + dw, dy + dh, sx, sy, sx + sw, sy + sh, observer);
}
/**
@@ -2085,44 +2094,59 @@ public class UIUtil {
*/
@Deprecated
public static void drawImage(Graphics g, Image image, int x, int y, int width, int height, ImageObserver observer) {
drawImage(g, image, null, x, y, width, height, observer);
}
private static void drawImage(Graphics g, Image image, @Nullable BufferedImageOp op, int x, int y, int width, int height, ImageObserver observer) {
double scale = 1d;
Graphics2D invG = null;
boolean srcSizeUndefined = (width == -1 && height == -1);
int dstw = ImageUtil.getUserWidth(image);
int dsth = ImageUtil.getUserHeight(image);
if (image instanceof JBHiDPIScaledImage) {
Image img = ((JBHiDPIScaledImage)image).getDelegate();
if (img == null) {
img = image;
JBHiDPIScaledImage hidpiImage = (JBHiDPIScaledImage)image;
Image delegate = hidpiImage.getDelegate();
if (delegate != null) image = delegate;
scale = hidpiImage.getScale();
if (srcSizeUndefined) {
AffineTransform tx = ((Graphics2D)g).getTransform();
if (scale == tx.getScaleX()) {
// The image has the same original scale as the graphics scale. However, the real image
// scale - userSize/realSize - can suffer from inaccuracy due to the image user size
// rounding to int (userSize = (int)realSize/originalImageScale). This may case quality
// loss if the image is drawn via Graphics.drawImage(image, <srcRect>, <dstRect>)
// due to scaling in Graphics. To avoid that, the image should be drawn directly via
// Graphics.drawImage(image, 0, 0) on the unscaled Graphics.
double gScaleX = tx.getScaleX();
double gScaleY = tx.getScaleY();
tx.scale(1 / gScaleX, 1 / gScaleY); // inverse the scale
tx.translate(x * gScaleX, y * gScaleY); // scale x/y with double precision
invG = (Graphics2D)g.create();
invG.setTransform(tx);
}
}
int dstw = width;
int dsth = height;
if (width == -1 && height == -1) {
dstw = ImageUtil.getUserWidth(image);
dsth = ImageUtil.getUserHeight(image);
}
int srcw = ImageUtil.getRealWidth(image);
int srch = ImageUtil.getRealHeight(image);
g.drawImage(img, x, y, x + dstw, y + dsth, 0, 0, srcw, srch, observer);
}
else if (width == -1 && height == -1) {
g.drawImage(image, x, y, observer);
if (op != null && image instanceof BufferedImage) {
image = op.filter((BufferedImage)image, null);
}
if (invG != null) {
invG.drawImage(image, 0, 0, observer);
invG.dispose();
}
else if (srcSizeUndefined) {
g.drawImage(image, x, y, dstw, dsth, observer);
}
else {
g.drawImage(image, x, y, x + width, y + height, 0, 0, width, height, observer);
int srcw = (int)Math.ceil(width * scale);
int srch = (int)Math.ceil(height * scale);
g.drawImage(image, x, y, x + width, y + height, 0, 0, srcw, srch, observer);
}
}
public static void drawImage(Graphics g, BufferedImage image, BufferedImageOp op, int x, int y) {
if (image instanceof JBHiDPIScaledImage) {
Image img = ((JBHiDPIScaledImage)image).getDelegate();
if (img == null) {
img = image;
}
if (op != null && img instanceof BufferedImage) img = op.filter((BufferedImage)img, null);
int dstw = ImageUtil.getUserWidth(image);
int dsth = ImageUtil.getUserHeight(image);
int srcw = ImageUtil.getRealWidth(image);
int srch = ImageUtil.getRealHeight(image);
g.drawImage(img, x, y, x + dstw, y + dsth, 0, 0, srcw, srch, null);
} else {
((Graphics2D)g).drawImage(image, op, x, y);
}
drawImage(g, image, op, x, y, -1, -1, null);
}
public static void paintWithXorOnRetina(@NotNull Dimension size, @NotNull Graphics g, Consumer<Graphics2D> paintRoutine) {