--- old/src/share/classes/java/awt/Image.java 2013-10-04 21:12:05.000000000 +0400 +++ new/src/share/classes/java/awt/Image.java 2013-10-04 21:12:04.000000000 +0400 @@ -135,6 +135,17 @@ * property which was not defined for a particular image is fetched. */ public static final Object UndefinedProperty = new Object(); + + + private int hints = SCALE_NONE; + + public void setScalingHints(int hints) { + this.hints = hints; + } + + public int getScalingHints() { + return hints; + } /** * Creates a scaled version of this image. @@ -167,6 +178,11 @@ * @since JDK1.1 */ public Image getScaledInstance(int width, int height, int hints) { + + if ((hints == SCALE_NONE) || (hints & SCALE_CUSTOM) != 0) { + return this; + } + ImageFilter filter; if ((hints & (SCALE_SMOOTH | SCALE_AREA_AVERAGING)) != 0) { filter = new AreaAveragingScaleFilter(width, height); @@ -177,7 +193,11 @@ prod = new FilteredImageSource(getSource(), filter); return Toolkit.getDefaultToolkit().createImage(prod); } - + + /** + */ + public static final int SCALE_NONE = 0; + /** * Use the default image-scaling algorithm. * @since JDK1.1 @@ -220,6 +240,13 @@ public static final int SCALE_AREA_AVERAGING = 16; /** + * + * User returns custom scaled image + * @since JDK1.8 + */ + public static final int SCALE_CUSTOM = 32; + + /** * Flushes all reconstructable resources being used by this Image object. * This includes any pixel data that is being cached for rendering to * the screen as well as any system resources that are being used --- old/src/share/classes/sun/java2d/SunGraphics2D.java 2013-10-04 21:12:05.000000000 +0400 +++ new/src/share/classes/sun/java2d/SunGraphics2D.java 2013-10-04 21:12:05.000000000 +0400 @@ -3051,17 +3051,58 @@ // end of text rendering methods private static boolean isHiDPIImage(final Image img) { - return SurfaceManager.getImageScale(img) != 1; + return SurfaceManager.getImageScale(img) != 1 + || (img.getScalingHints() != Image.SCALE_NONE); } - + private boolean drawHiDPIImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer) { - final int scale = SurfaceManager.getImageScale(img); + int scale = SurfaceManager.getImageScale(img); + if ((img.getScalingHints() != Image.SCALE_NONE)) { + // get scaled destination image size + scale = surfaceData.getDefaultScale(); + float widthScale = (dx2 - dx1) / (sx2 - sx1); + float heightScale = (dy2 - dy1) / (sy2 - sy1); + + int width = img.getWidth(null); + int height = img.getHeight(null); + + int scaledWidth = Region.clipScale(width, scale * widthScale); + int scaledHeight = Region.clipScale(height, scale * heightScale); + + Image scaledImage = img.getScaledInstance(scaledWidth, scaledHeight, + img.getScalingHints()); + + if (scaledImage != null && scaledImage != img) { + // recalculate source region for the scaled image + scaledWidth = scaledImage.getWidth(null); + scaledHeight = scaledImage.getHeight(null); + + widthScale = scaledWidth / width; + heightScale = scaledHeight / height; + + sx1 = Region.clipScale(sx1, widthScale); + sy1 = Region.clipScale(sy1, heightScale); + sx2 = Region.clipScale(sx2, widthScale); + sy2 = Region.clipScale(sy2, heightScale); + img = scaledImage; + } + scale = 1; + } + + return drawHiDPIImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, + bgcolor, observer, scale); + } + + private boolean drawHiDPIImage(Image img, int dx1, int dy1, int dx2, + int dy2, int sx1, int sy1, int sx2, int sy2, + Color bgcolor, ImageObserver observer, int scale) { + sx1 = Region.clipScale(sx1, scale); sx2 = Region.clipScale(sx2, scale); sy1 = Region.clipScale(sy1, scale); - sy2 = Region.clipScale(sy2, scale); + sy2 = Region.clipScale(sy2, scale); try { return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer); --- old/src/macosx/classes/sun/lwawt/macosx/LWCToolkit.java 2013-10-04 21:12:06.000000000 +0400 +++ new/src/macosx/classes/sun/lwawt/macosx/LWCToolkit.java 2013-10-04 21:12:06.000000000 +0400 @@ -35,11 +35,13 @@ import java.awt.im.InputMethodHighlight; import java.awt.peer.*; import java.lang.reflect.*; +import java.io.File; import java.security.*; import java.util.*; import java.util.concurrent.Callable; import sun.awt.*; +import sun.awt.image.ToolkitImage; import sun.lwawt.*; import sun.lwawt.LWWindowPeer.PeerType; import sun.security.action.GetBooleanAction; @@ -491,9 +493,36 @@ final Image nsImage = checkForNSImage(filename); if (nsImage != null) return nsImage; - return super.getImage(filename); + ToolkitImage image = (ToolkitImage) super.getImage(filename); + setHighResolutionImage(image, filename); + return image; + } + + @Override + public Image createImage(String filename) { + ToolkitImage image = (ToolkitImage) super.createImage(filename); + setHighResolutionImage(image, filename); + return image; + } + + private static void setHighResolutionImage(ToolkitImage image, String filename) { + if (filename != null && !filename.contains("@2x")) { + String filename2x = getScaledImageName(filename); + File file2x = new File(filename2x); + System.out.println("[lwc toolkit] create image2x name: " + filename2x + ", exist: " + file2x.exists()); + if (new File(filename2x).exists()) { + ((ToolkitImage) image).setHighResolutionImage(filename2x); + image.setScalingHints(Image.SCALE_CUSTOM); + } + } } + private static String getScaledImageName(String name) { + int dot = name.lastIndexOf('.'); + return (dot < 0) ? name + "@2x" : name.substring(0, dot) + "@2x." + + name.substring(dot + 1, name.length()); + } + static final String nsImagePrefix = "NSImage://"; protected Image checkForNSImage(final String imageName) { if (imageName == null) return null; --- old/src/share/classes/sun/awt/image/ToolkitImage.java 2013-10-04 21:12:07.000000000 +0400 +++ new/src/share/classes/sun/awt/image/ToolkitImage.java 2013-10-04 21:12:07.000000000 +0400 @@ -32,6 +32,7 @@ import java.awt.Color; import java.awt.Graphics; import java.awt.Image; +import java.awt.Toolkit; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.ImageProducer; @@ -39,6 +40,7 @@ import java.awt.image.ImageObserver; import sun.awt.image.ImageRepresentation; import sun.awt.image.FileImageSource; +import java.net.URL; public class ToolkitImage extends Image { @@ -328,4 +330,26 @@ ImageRepresentation imageRep = getImageRep(); imageRep.setAccelerationPriority(accelerationPriority); } + + private Image highResolutionImage; + + public void setHighResolutionImage(String filename) { + this.highResolutionImage = Toolkit.getDefaultToolkit().getImage(filename); + } + + public Image getHighResolutionImage() { + return highResolutionImage; + } + + @Override + public Image getScaledInstance(int width, int height, int hints) { + if ((hints & Image.SCALE_CUSTOM) == Image.SCALE_CUSTOM) { + return (highResolutionImage != null + && (getWidth() < width || getHeight() < height)) + ? highResolutionImage : this; + } + + return super.getScaledInstance(width, height, hints); + } + } --- /dev/null 2013-10-04 21:12:08.000000000 +0400 +++ new/test/java/awt/image/ScalableImageTest.java 2013-10-04 21:12:08.000000000 +0400 @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.awt.Canvas; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Image; +import java.awt.Toolkit; +import java.awt.image.BufferedImage; +import java.awt.image.ImageObserver; +import java.io.File; +import javax.imageio.ImageIO; +import javax.swing.JFrame; +import javax.swing.SwingUtilities; +import sun.awt.OSInfo; + +/** + * @test + * @bug 8011059 + * @author Alexander Scherbatiy + * @summary [macosx] Make JDK demos look perfect on retina displays + * @run main ScalableImageTest + */ +public class ScalableImageTest { + + private static final int WIDTH = 300; + private static final int HEIGHT = 200; + private static final Color COLOR_1X = Color.GREEN; + private static final Color COLOR_2X = Color.BLUE; + private static final String IMAGE_NAME_1X = "image.png"; + private static final String IMAGE_NAME_2X = "image@2x.png"; + private static volatile boolean imageReady; + + public static void main(String[] args) throws Exception { + testCustomScaledImage(); + + if (OSInfo.getOSType() == OSInfo.OSType.MACOSX) { + testToolkitScaledImage(); + } + } + + public static void testCustomScaledImage() { + final BufferedImage testImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB) { + + BufferedImage image2x; + boolean[] drawImage = new boolean[2]; + + @Override + public Image getScaledInstance(int width, int height, int hints) { + + if ((hints & Image.SCALE_CUSTOM) == 0) { + return super.getScaledInstance(width, height, hints); + } + + int scaleFactor = (width <= WIDTH && height <= HEIGHT) ? 1 : 2; + + if (scaleFactor == 2 && image2x == null) { + image2x = new BufferedImage(2 * WIDTH, 2 * HEIGHT, BufferedImage.TYPE_INT_RGB); + } + + BufferedImage scaledImage = (scaleFactor == 1) ? this : image2x; + + if (!drawImage[scaleFactor - 1]) { + Graphics g = scaledImage.getGraphics(); + g.setColor((scaleFactor == 1) ? COLOR_1X : COLOR_2X); + g.fillRect(0, 0, scaledImage.getWidth(), scaledImage.getHeight()); + drawImage[scaleFactor - 1] = true; + } + return scaledImage; + } + }; + + testImage.setScalingHints(Image.SCALE_CUSTOM); + + + final BufferedImage bufferedImage1x = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); + Graphics g1x = bufferedImage1x.getGraphics(); + g1x.drawImage(testImage, 0, 0, null); + + + if (COLOR_1X.getRGB() != bufferedImage1x.getRGB(WIDTH / 2, HEIGHT / 2)) { + throw new RuntimeException("Wrong 1x color"); + } + + final BufferedImage bufferedImage2x = new BufferedImage(2 * WIDTH, 2 * HEIGHT, BufferedImage.TYPE_INT_RGB); + Graphics g2x = bufferedImage2x.getGraphics(); + g2x.drawImage(testImage, 0, 0, 2 * WIDTH, 2 * HEIGHT, 0, 0, WIDTH, HEIGHT, null); + + + if (COLOR_2X.getRGB() != bufferedImage2x.getRGB(WIDTH / 2, HEIGHT / 2)) { + throw new RuntimeException("Wrong 2x color"); + } + } + + static void testToolkitScaledImage() throws Exception { + generateImage(1); + generateImage(2); + + File imageFile = new File(IMAGE_NAME_1X); + + Image image = Toolkit.getDefaultToolkit().getImage(imageFile.getAbsolutePath()); + + image.getWidth(new ImageObserver() { + + @Override + public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { + if ((infoflags & ImageObserver.ALLBITS) != 0) { + imageReady = true; + return false; + } + return true; + } + }); + + long currentTime = System.currentTimeMillis(); + while (!imageReady && (System.currentTimeMillis() - currentTime) < 5000) { + Thread.sleep(100); + } + + final BufferedImage bufferedImage1x = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); + Graphics g1x = bufferedImage1x.getGraphics(); + g1x.drawImage(image, 0, 0, null); + + if (COLOR_1X.getRGB() != bufferedImage1x.getRGB(WIDTH / 2, HEIGHT / 2)) { + throw new RuntimeException("Wrong 1x color"); + } + + imageReady = false; + + Image scaledImage = image.getScaledInstance(2 * WIDTH, 2 * HEIGHT, Image.SCALE_CUSTOM); + + if (scaledImage == null) { + throw new RuntimeException("Scaled image is null"); + } + + scaledImage.getWidth(new ImageObserver() { + + @Override + public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { + if ((infoflags & ImageObserver.ALLBITS) != 0) { + imageReady = true; + return false; + } + return true; + } + }); + + currentTime = System.currentTimeMillis(); + while (!imageReady && (System.currentTimeMillis() - currentTime) < 5000) { + Thread.sleep(100); + } + + final BufferedImage bufferedImage2x = new BufferedImage(2 * WIDTH, 2 * HEIGHT, BufferedImage.TYPE_INT_RGB); + Graphics g2x = bufferedImage2x.getGraphics(); + g2x.drawImage(image, 0, 0, 2 * WIDTH, 2 * HEIGHT, 0, 0, WIDTH, HEIGHT, null); + + if (COLOR_2X.getRGB() != bufferedImage2x.getRGB(WIDTH / 2, HEIGHT / 2)) { + throw new RuntimeException("Wrong 2x color"); + } + } + + static void generateImage(int scale) throws Exception { + BufferedImage image = new BufferedImage(scale * WIDTH, scale * HEIGHT, BufferedImage.TYPE_INT_RGB); + Graphics g = image.getGraphics(); + g.setColor(scale == 1 ? COLOR_1X : COLOR_2X); + g.fillRect(0, 0, scale * WIDTH, scale * HEIGHT); + File file = new File(scale == 1 ? IMAGE_NAME_1X : IMAGE_NAME_2X); + ImageIO.write(image, "png", file); + } +}