--- /dev/null 2013-12-04 18:00:42.000000000 +0400 +++ new/src/share/classes/sun/awt/image/MultiResolutionImage.java 2013-12-04 18:00:42.000000000 +0400 @@ -0,0 +1,83 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package sun.awt.image; + +import java.awt.Image; +import java.util.List; + +/** + * This interface is designed to provide a set of images at various resolutions. + * + * The MultiResolutionImage interface should be implemented by any + * class whose instances are intended to provide image resolution variants + * according to the given image width and height. + * + * For example, + *
+ * {@code
+ *  public class ScaledImage extends BufferedImage
+ *         implements MultiResolutionImage {
+ *
+ *    @Override
+ *    public Image getResolutionVariant(int width, int height) {
+ *      return ((width <= getWidth() && height <= getHeight()))
+ *             ? this : highResolutionImage;
+ *    }
+ *
+ *    @Override
+ *    public List getResolutionVariants() {
+ *        return Arrays.asList(this, highResolutionImage);
+ *    }
+ *  }
+ * }
+ * + * It is recommended to cache image variants for performance reasons. + * + * WARNING: This class is an implementation detail. This API may change + * between update release, and it may even be removed or be moved in some other + * package(s)/class(es). + */ +public interface MultiResolutionImage { + + /** + * Provides an image with necessary resolution which best fits to the given + * image width and height. + * + * @param width the desired image resolution width. + * @param height the desired image resolution height. + * @return image resolution variant. + * + * @since JDK1.8 + */ + public Image getResolutionVariant(int width, int height); + + /** + * Gets list of all resolution variants including the base image + * + * @return list of resolution variants. + * @since JDK1.8 + */ + public List getResolutionVariants(); +} --- old/src/share/classes/sun/awt/SunHints.java 2013-12-04 18:00:44.000000000 +0400 +++ new/src/share/classes/sun/awt/SunHints.java 2013-12-04 18:00:44.000000000 +0400 @@ -172,7 +172,7 @@ } } - private static final int NUM_KEYS = 9; + private static final int NUM_KEYS = 10; private static final int VALS_PER_KEY = 8; /** @@ -253,6 +253,13 @@ @Native public static final int INTVAL_STROKE_PURE = 2; /** + * Image scaling hint key and values + */ + @Native public static final int INTKEY_RESOLUTION_VARIANT = 9; + @Native public static final int INTVAL_RESOLUTION_VARIANT_DEFAULT = 0; + @Native public static final int INTVAL_RESOLUTION_VARIANT_OFF = 1; + @Native public static final int INTVAL_RESOLUTION_VARIANT_ON = 2; + /** * LCD text contrast control hint key. * Value is "100" to make discontiguous with the others which * are all enumerative and are of a different class. @@ -450,6 +457,24 @@ SunHints.INTVAL_STROKE_PURE, "Pure stroke conversion for accurate paths"); + /** + * Image resolution variant hint key and value objects + */ + public static final Key KEY_RESOLUTION_VARIANT = + new SunHints.Key(SunHints.INTKEY_RESOLUTION_VARIANT, + "Global image resolution variant key"); + public static final Object VALUE_RESOLUTION_VARIANT_DEFAULT = + new SunHints.Value(KEY_RESOLUTION_VARIANT, + SunHints.INTVAL_RESOLUTION_VARIANT_DEFAULT, + "Choose image resolutions based on a default heuristic"); + public static final Object VALUE_RESOLUTION_VARIANT_OFF = + new SunHints.Value(KEY_RESOLUTION_VARIANT, + SunHints.INTVAL_RESOLUTION_VARIANT_OFF, + "Use only the standard resolution of an image"); + public static final Object VALUE_RESOLUTION_VARIANT_ON = + new SunHints.Value(KEY_RESOLUTION_VARIANT, + SunHints.INTVAL_RESOLUTION_VARIANT_ON, + "Always use resolution-specific variants of images"); public static class LCDContrastKey extends Key { --- old/src/share/classes/sun/java2d/SunGraphics2D.java 2013-12-04 18:00:46.000000000 +0400 +++ new/src/share/classes/sun/java2d/SunGraphics2D.java 2013-12-04 18:00:45.000000000 +0400 @@ -61,6 +61,7 @@ import java.awt.Rectangle; import java.text.AttributedCharacterIterator; import java.awt.Font; +import java.awt.Point; import java.awt.image.ImageObserver; import java.awt.Transparency; import java.awt.font.GlyphVector; @@ -93,6 +94,13 @@ import sun.misc.PerformanceLogger; import java.lang.annotation.Native; +import sun.awt.image.MultiResolutionImage; + +import static java.awt.geom.AffineTransform.TYPE_FLIP; +import static java.awt.geom.AffineTransform.TYPE_MASK_SCALE; +import static java.awt.geom.AffineTransform.TYPE_TRANSLATION; +import sun.awt.image.MultiResolutionToolkitImage; +import sun.awt.image.ToolkitImage; /** * This is a the master Graphics2D superclass for all of the Sun @@ -237,6 +245,7 @@ protected Region devClip; // Actual physical drawable in pixels private final int devScale; // Actual physical scale factor + private int resolutionVariantHint; // cached state for text rendering private boolean validFontInfo; @@ -274,6 +283,7 @@ lcdTextContrast = lcdTextContrastDefaultValue; interpolationHint = -1; strokeHint = SunHints.INTVAL_STROKE_DEFAULT; + resolutionVariantHint = SunHints.INTVAL_RESOLUTION_VARIANT_DEFAULT; interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR; @@ -1249,6 +1259,10 @@ stateChanged = (strokeHint != newHint); strokeHint = newHint; break; + case SunHints.INTKEY_RESOLUTION_VARIANT: + stateChanged = (resolutionVariantHint != newHint); + resolutionVariantHint = newHint; + break; default: recognized = false; stateChanged = false; @@ -1322,6 +1336,9 @@ case SunHints.INTKEY_STROKE_CONTROL: return SunHints.Value.get(SunHints.INTKEY_STROKE_CONTROL, strokeHint); + case SunHints.INTKEY_RESOLUTION_VARIANT: + return SunHints.Value.get(SunHints.INTKEY_RESOLUTION_VARIANT, + resolutionVariantHint); } return null; } @@ -3050,18 +3067,58 @@ } // end of text rendering methods - private static boolean isHiDPIImage(final Image img) { - return SurfaceManager.getImageScale(img) != 1; + private boolean isHiDPIImage(final Image img) { + return (SurfaceManager.getImageScale(img) != 1) || + (resolutionVariantHint != SunHints.INTVAL_RESOLUTION_VARIANT_OFF + && img instanceof MultiResolutionImage); } 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); - sx1 = Region.clipScale(sx1, scale); - sx2 = Region.clipScale(sx2, scale); - sy1 = Region.clipScale(sy1, scale); - sy2 = Region.clipScale(sy2, scale); + + if (SurfaceManager.getImageScale(img) != 1) { // Volatile Image + final int scale = SurfaceManager.getImageScale(img); + sx1 = Region.clipScale(sx1, scale); + sx2 = Region.clipScale(sx2, scale); + sy1 = Region.clipScale(sy1, scale); + sy2 = Region.clipScale(sy2, scale); + } else if (img instanceof MultiResolutionImage) { + // get scaled destination image size + + int width = img.getWidth(observer); + int height = img.getHeight(observer); + + Image resolutionVariant = getResolutionVariant( + (MultiResolutionImage) img, width, height, + dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2); + + if (resolutionVariant != img && resolutionVariant != null) { + // recalculate source region for the resolution variant + + ImageObserver rvObserver = MultiResolutionToolkitImage. + getResolutionVariantObserver(img, observer, + width, height, -1, -1); + + int rvWidth = resolutionVariant.getWidth(rvObserver); + int rvHeight = resolutionVariant.getHeight(rvObserver); + + if (0 < width && 0 < height && 0 < rvWidth && 0 < rvHeight) { + + float widthScale = ((float) rvWidth) / width; + float heightScale = ((float) rvHeight) / height; + + sx1 = Region.clipScale(sx1, widthScale); + sy1 = Region.clipScale(sy1, heightScale); + sx2 = Region.clipScale(sx2, widthScale); + sy2 = Region.clipScale(sy2, heightScale); + + observer = rvObserver; + img = resolutionVariant; + } + } + } + try { return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer); @@ -3081,6 +3138,54 @@ } } + private Image getResolutionVariant(MultiResolutionImage img, + int srcWidth, int srcHeight, int dx1, int dy1, int dx2, int dy2, + int sx1, int sy1, int sx2, int sy2) { + + if (srcWidth <= 0 || srcHeight <= 0) { + return null; + } + + int sw = sx2 - sx1; + int sh = sy2 - sy1; + + if (sw == 0 || sh == 0) { + return null; + } + + int type = transform.getType(); + int dw = dx2 - dx1; + int dh = dy2 - dy1; + double destRegionWidth; + double destRegionHeight; + + if ((type & ~(TYPE_TRANSLATION | TYPE_FLIP)) == 0) { + destRegionWidth = dw; + destRegionHeight = dh; + } else if ((type & ~(TYPE_TRANSLATION | TYPE_FLIP | TYPE_MASK_SCALE)) == 0) { + destRegionWidth = dw * transform.getScaleX(); + destRegionHeight = dh * transform.getScaleY(); + } else { + destRegionWidth = dw * Math.hypot( + transform.getScaleX(), transform.getShearY()); + destRegionHeight = dh * Math.hypot( + transform.getShearX(), transform.getScaleY()); + } + + int destImageWidth = (int) Math.abs(srcWidth * destRegionWidth / sw); + int destImageHeight = (int) Math.abs(srcHeight * destRegionHeight / sh); + + Image resolutionVariant + = img.getResolutionVariant(destImageWidth, destImageHeight); + + if (resolutionVariant instanceof ToolkitImage + && ((ToolkitImage) resolutionVariant).hasError()) { + return null; + } + + return resolutionVariant; + } + /** * Draws an image scaled to x,y,w,h in nonblocking mode with a * callback object. --- old/src/macosx/classes/sun/lwawt/macosx/LWCToolkit.java 2013-12-04 18:00:48.000000000 +0400 +++ new/src/macosx/classes/sun/lwawt/macosx/LWCToolkit.java 2013-12-04 18:00:47.000000000 +0400 @@ -35,14 +35,17 @@ import java.awt.im.InputMethodHighlight; import java.awt.peer.*; import java.lang.reflect.*; +import java.net.URL; import java.security.*; import java.util.*; import java.util.concurrent.Callable; +import java.net.MalformedURLException; import sun.awt.*; import sun.lwawt.*; import sun.lwawt.LWWindowPeer.PeerType; import sun.security.action.GetBooleanAction; +import sun.awt.image.MultiResolutionImage; import sun.util.CoreResourceBundleControl; @@ -489,9 +492,30 @@ @Override public Image getImage(final String filename) { final Image nsImage = checkForNSImage(filename); - if (nsImage != null) return nsImage; + if (nsImage != null) { + return nsImage; + } + + if (imageCached(filename)) { + return super.getImage(filename); + } - return super.getImage(filename); + String fileneame2x = getScaledImageName(filename); + return (imageExists(fileneame2x)) + ? getImageWithResolutionVariant(filename, fileneame2x) + : super.getImage(filename); + } + + @Override + public Image getImage(URL url) { + + if (imageCached(url)) { + return super.getImage(url); + } + + URL url2x = getScaledImageURL(url); + return (imageExists(url2x)) + ? getImageWithResolutionVariant(url, url2x) : super.getImage(url); } static final String nsImagePrefix = "NSImage://"; @@ -781,4 +805,36 @@ public boolean enableInputMethodsForTextComponent() { return true; } + + private static URL getScaledImageURL(URL url) { + try { + String scaledImagePath = getScaledImageName(url.getPath()); + return scaledImagePath == null ? null : new URL(url.getProtocol(), + url.getHost(), url.getPort(), scaledImagePath); + } catch (MalformedURLException e) { + return null; + } + } + + private static String getScaledImageName(String path) { + if (!isValidPath(path)) { + return null; + } + + int slash = path.lastIndexOf('/'); + String name = (slash < 0) ? path : path.substring(slash + 1); + + if (name.contains("@2x")) { + return null; + } + + int dot = name.lastIndexOf('.'); + String name2x = (dot < 0) ? name + "@2x" + : name.substring(0, dot) + "@2x" + name.substring(dot); + return (slash < 0) ? name2x : path.substring(0, slash + 1) + name2x; + } + + private static boolean isValidPath(String path) { + return !path.isEmpty() && !path.endsWith("/") && !path.endsWith("."); + } } --- old/src/share/classes/sun/awt/SunToolkit.java 2013-12-04 18:00:49.000000000 +0400 +++ new/src/share/classes/sun/awt/SunToolkit.java 2013-12-04 18:00:49.000000000 +0400 @@ -36,6 +36,9 @@ import java.awt.TrayIcon; import java.awt.SystemTray; import java.awt.event.InputEvent; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.net.URL; import java.util.*; import java.util.concurrent.TimeUnit; @@ -715,33 +718,7 @@ static final SoftCache imgCache = new SoftCache(); static Image getImageFromHash(Toolkit tk, URL url) { - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - try { - java.security.Permission perm = - url.openConnection().getPermission(); - if (perm != null) { - try { - sm.checkPermission(perm); - } catch (SecurityException se) { - // fallback to checkRead/checkConnect for pre 1.2 - // security managers - if ((perm instanceof java.io.FilePermission) && - perm.getActions().indexOf("read") != -1) { - sm.checkRead(perm.getName()); - } else if ((perm instanceof - java.net.SocketPermission) && - perm.getActions().indexOf("connect") != -1) { - sm.checkConnect(url.getHost(), url.getPort()); - } else { - throw se; - } - } - } - } catch (java.io.IOException ioe) { - sm.checkConnect(url.getHost(), url.getPort()); - } - } + checkPermissions(url); synchronized (imgCache) { Image img = (Image)imgCache.get(url); if (img == null) { @@ -757,10 +734,7 @@ static Image getImageFromHash(Toolkit tk, String filename) { - SecurityManager security = System.getSecurityManager(); - if (security != null) { - security.checkRead(filename); - } + checkPermissions(filename); synchronized (imgCache) { Image img = (Image)imgCache.get(filename); if (img == null) { @@ -782,42 +756,42 @@ return getImageFromHash(this, url); } - public Image createImage(String filename) { - SecurityManager security = System.getSecurityManager(); - if (security != null) { - security.checkRead(filename); + protected Image getImageWithResolutionVariant(String fileName, + String resolutionVariantName) { + synchronized (imgCache) { + Image image = getImageFromHash(this, fileName); + if (image instanceof MultiResolutionImage) { + return image; + } + Image resolutionVariant = getImageFromHash(this, resolutionVariantName); + image = createImageWithResolutionVariant(image, resolutionVariant); + imgCache.put(fileName, image); + return image; + } + } + + protected Image getImageWithResolutionVariant(URL url, + URL resolutionVariantURL) { + synchronized (imgCache) { + Image image = getImageFromHash(this, url); + if (image instanceof MultiResolutionImage) { + return image; + } + Image resolutionVariant = getImageFromHash(this, resolutionVariantURL); + image = createImageWithResolutionVariant(image, resolutionVariant); + imgCache.put(url, image); + return image; } + } + + + public Image createImage(String filename) { + checkPermissions(filename); return createImage(new FileImageSource(filename)); } public Image createImage(URL url) { - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - try { - java.security.Permission perm = - url.openConnection().getPermission(); - if (perm != null) { - try { - sm.checkPermission(perm); - } catch (SecurityException se) { - // fallback to checkRead/checkConnect for pre 1.2 - // security managers - if ((perm instanceof java.io.FilePermission) && - perm.getActions().indexOf("read") != -1) { - sm.checkRead(perm.getName()); - } else if ((perm instanceof - java.net.SocketPermission) && - perm.getActions().indexOf("connect") != -1) { - sm.checkConnect(url.getHost(), url.getPort()); - } else { - throw se; - } - } - } - } catch (java.io.IOException ioe) { - sm.checkConnect(url.getHost(), url.getPort()); - } - } + checkPermissions(url); return createImage(new URLImageSource(url)); } @@ -829,6 +803,11 @@ return new ToolkitImage(producer); } + public static Image createImageWithResolutionVariant(Image image, + Image resolutionVariant) { + return new MultiResolutionToolkitImage(image, resolutionVariant); + } + public int checkImage(Image img, int w, int h, ImageObserver o) { if (!(img instanceof ToolkitImage)) { return ImageObserver.ALLBITS; @@ -841,7 +820,7 @@ } else { repbits = tkimg.getImageRep().check(o); } - return tkimg.check(o) | repbits; + return (tkimg.check(o) | repbits) & checkResolutionVariant(img, w, h, o); } public boolean prepareImage(Image img, int w, int h, ImageObserver o) { @@ -863,7 +842,97 @@ return false; } ImageRepresentation ir = tkimg.getImageRep(); - return ir.prepare(o); + return ir.prepare(o) & prepareResolutionVariant(img, w, h, o); + } + + private int checkResolutionVariant(Image img, int w, int h, ImageObserver o) { + ToolkitImage rvImage = getResolutionVariant(img); + // Ignore the resolution variant in case of error + return (rvImage == null || rvImage.hasError()) ? 0xFFFF : + checkImage(rvImage, 2 * w, 2 * h, MultiResolutionToolkitImage. + getResolutionVariantObserver( + img, o, w, h, 2 * w, 2 * h)); + } + + private boolean prepareResolutionVariant(Image img, int w, int h, + ImageObserver o) { + + ToolkitImage rvImage = getResolutionVariant(img); + // Ignore the resolution variant in case of error + return rvImage == null || rvImage.hasError() || prepareImage( + rvImage, 2 * w, 2 * h, + MultiResolutionToolkitImage.getResolutionVariantObserver( + img, o, w, h, 2 * w, 2 * h)); + } + + private static ToolkitImage getResolutionVariant(Image image) { + if (image instanceof MultiResolutionToolkitImage) { + Image resolutionVariant = ((MultiResolutionToolkitImage) image). + getResolutionVariant(); + if (resolutionVariant instanceof ToolkitImage) { + return (ToolkitImage) resolutionVariant; + } + } + return null; + } + + protected static boolean imageCached(Object key) { + return imgCache.containsKey(key); + } + + protected static boolean imageExists(String filename) { + checkPermissions(filename); + return filename != null && new File(filename).exists(); + } + + @SuppressWarnings("try") + protected static boolean imageExists(URL url) { + checkPermissions(url); + if (url != null) { + try (InputStream is = url.openStream()) { + return true; + }catch(IOException e){ + return false; + } + } + return false; + } + + private static void checkPermissions(String filename) { + SecurityManager security = System.getSecurityManager(); + if (security != null) { + security.checkRead(filename); + } + } + + private static void checkPermissions(URL url) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + try { + java.security.Permission perm = + url.openConnection().getPermission(); + if (perm != null) { + try { + sm.checkPermission(perm); + } catch (SecurityException se) { + // fallback to checkRead/checkConnect for pre 1.2 + // security managers + if ((perm instanceof java.io.FilePermission) && + perm.getActions().indexOf("read") != -1) { + sm.checkRead(perm.getName()); + } else if ((perm instanceof + java.net.SocketPermission) && + perm.getActions().indexOf("connect") != -1) { + sm.checkConnect(url.getHost(), url.getPort()); + } else { + throw se; + } + } + } + } catch (java.io.IOException ioe) { + sm.checkConnect(url.getHost(), url.getPort()); + } + } } /** --- old/src/share/classes/java/awt/MediaTracker.java 2013-12-04 18:00:51.000000000 +0400 +++ new/src/share/classes/java/awt/MediaTracker.java 2013-12-04 18:00:51.000000000 +0400 @@ -28,6 +28,7 @@ import java.awt.Component; import java.awt.Image; import java.awt.image.ImageObserver; +import sun.awt.image.MultiResolutionToolkitImage; /** * The MediaTracker class is a utility class to track @@ -222,10 +223,17 @@ * @param h the height at which the image is rendered */ public synchronized void addImage(Image image, int id, int w, int h) { + addImageImpl(image, id, w, h); + Image rvImage = getResolutionVariant(image); + if (rvImage != null) { + addImageImpl(rvImage, id, 2 * w, 2 * h); + } + } + + private void addImageImpl(Image image, int id, int w, int h) { head = MediaEntry.insert(head, new ImageMediaEntry(this, image, id, w, h)); } - /** * Flag indicating that media is currently being loaded. * @see java.awt.MediaTracker#statusAll @@ -719,6 +727,15 @@ * @since JDK1.1 */ public synchronized void removeImage(Image image) { + removeImageImpl(image); + Image rvImage = getResolutionVariant(image); + if (rvImage != null) { + removeImageImpl(rvImage); + } + notifyAll(); // Notify in case remaining images are "done". + } + + private void removeImageImpl(Image image) { MediaEntry cur = head; MediaEntry prev = null; while (cur != null) { @@ -735,7 +752,6 @@ } cur = next; } - notifyAll(); // Notify in case remaining images are "done". } /** @@ -750,6 +766,15 @@ * @since JDK1.1 */ public synchronized void removeImage(Image image, int id) { + removeImageImpl(image, id); + Image rvImage = getResolutionVariant(image); + if (rvImage != null) { + removeImageImpl(rvImage, id); + } + notifyAll(); // Notify in case remaining images are "done". + } + + private void removeImageImpl(Image image, int id) { MediaEntry cur = head; MediaEntry prev = null; while (cur != null) { @@ -766,7 +791,6 @@ } cur = next; } - notifyAll(); // Notify in case remaining images are "done". } /** @@ -783,6 +807,16 @@ */ public synchronized void removeImage(Image image, int id, int width, int height) { + removeImageImpl(image, id, width, height); + Image rvImage = getResolutionVariant(image); + if (rvImage != null) { + removeImageImpl(rvImage, id, 2 * width, 2 * height); + + } + notifyAll(); // Notify in case remaining images are "done". + } + + private void removeImageImpl(Image image, int id, int width, int height) { MediaEntry cur = head; MediaEntry prev = null; while (cur != null) { @@ -801,12 +835,18 @@ } cur = next; } - notifyAll(); // Notify in case remaining images are "done". } synchronized void setDone() { notifyAll(); } + + private static Image getResolutionVariant(Image image) { + if (image instanceof MultiResolutionToolkitImage) { + return ((MultiResolutionToolkitImage) image).getResolutionVariant(); + } + return null; + } } abstract class MediaEntry { --- /dev/null 2013-12-04 18:00:53.000000000 +0400 +++ new/src/share/classes/sun/awt/image/MultiResolutionToolkitImage.java 2013-12-04 18:00:52.000000000 +0400 @@ -0,0 +1,104 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package sun.awt.image; + +import java.awt.Image; +import java.awt.image.ImageObserver; +import java.util.Arrays; +import java.util.List; +import sun.misc.SoftCache; + +public class MultiResolutionToolkitImage extends ToolkitImage implements MultiResolutionImage { + + Image resolutionVariant; + + public MultiResolutionToolkitImage(Image lowResolutionImage, Image resolutionVariant) { + super(lowResolutionImage.getSource()); + this.resolutionVariant = resolutionVariant; + } + + @Override + public Image getResolutionVariant(int width, int height) { + return ((width <= getWidth() && height <= getHeight())) + ? this : resolutionVariant; + } + + public Image getResolutionVariant() { + return resolutionVariant; + } + + @Override + public List getResolutionVariants() { + return Arrays.asList(this, resolutionVariant); + } + + private static final int BITS_INFO = ImageObserver.SOMEBITS + | ImageObserver.FRAMEBITS | ImageObserver.ALLBITS; + + private static class ObserverCache { + + static final SoftCache INSTANCE = new SoftCache(); + } + + public static ImageObserver getResolutionVariantObserver( + final Image image, final ImageObserver observer, + final int imgWidth, final int imgHeight, + final int rvWidth, final int rvHeight) { + + if (observer == null) { + return null; + } + + synchronized (ObserverCache.INSTANCE) { + ImageObserver o = (ImageObserver) ObserverCache.INSTANCE.get(image); + + if (o == null) { + + o = (Image resolutionVariant, int flags, + int x, int y, int width, int height) -> { + + if ((flags & (ImageObserver.WIDTH | BITS_INFO)) != 0) { + width = (width + 1) / 2; + } + + if ((flags & (ImageObserver.HEIGHT | BITS_INFO)) != 0) { + height = (height + 1) / 2; + } + + if ((flags & BITS_INFO) != 0) { + x /= 2; + y /= 2; + } + + return observer.imageUpdate( + image, flags, x, y, width, height); + }; + + ObserverCache.INSTANCE.put(image, o); + } + return o; + } + } +} --- /dev/null 2013-12-04 18:00:53.000000000 +0400 +++ new/test/java/awt/image/MultiResolutionImageTest.java 2013-12-04 18:00:53.000000000 +0400 @@ -0,0 +1,620 @@ +/* + * 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.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Toolkit; +import java.awt.image.BufferedImage; +import java.io.File; +import java.lang.reflect.Method; +import java.net.URL; +import javax.imageio.ImageIO; +import sun.awt.OSInfo; +import sun.awt.SunHints; +import java.awt.MediaTracker; +import java.awt.geom.AffineTransform; +import java.awt.image.ImageObserver; +import java.util.Arrays; +import java.util.List; +import javax.swing.JPanel; +import sun.awt.SunToolkit; +import sun.awt.image.MultiResolutionImage; + +/** + * @test + * @bug 8011059 + * @author Alexander Scherbatiy + * @summary [macosx] Make JDK demos look perfect on retina displays + * @run main MultiResolutionImageTest CUSTOM + * @run main MultiResolutionImageTest TOOLKIT_PREPARE + * @run main MultiResolutionImageTest TOOLKIT_LOAD + * @run main MultiResolutionImageTest TOOLKIT + */ +public class MultiResolutionImageTest { + + private static final int IMAGE_WIDTH = 300; + private static final int IMAGE_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"; + + public static void main(String[] args) throws Exception { + + System.out.println("args: " + args.length); + + if (args.length == 0) { + throw new RuntimeException("Not found a test"); + } + + String test = args[0]; + + System.out.println("TEST: " + test); + System.out.println("CHECK OS: " + checkOS()); + + if ("CUSTOM".equals(test)) { + testCustomMultiResolutionImage(); + } else if (checkOS()) { + switch (test) { + case "CUSTOM": + break; + case "TOOLKIT_PREPARE": + testToolkitMultiResolutionImagePrepare(); + break; + case "TOOLKIT_LOAD": + testToolkitMultiResolutionImageLoad(); + break; + case "TOOLKIT": + testToolkitMultiResolutionImage(); + testImageNameTo2xParsing(); + break; + default: + throw new RuntimeException("Unknown test: " + test); + } + } + } + + static boolean checkOS() { + return OSInfo.getOSType() == OSInfo.OSType.MACOSX; + } + + public static void testCustomMultiResolutionImage() { + testCustomMultiResolutionImage(false); + testCustomMultiResolutionImage(true); + } + + public static void testCustomMultiResolutionImage(boolean enableImageScaling) { + + Image image = new MultiResolutionBufferedImage(); + + // Same image size + BufferedImage bufferedImage = new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, + BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = (Graphics2D) bufferedImage.getGraphics(); + setImageScalingHint(g2d, enableImageScaling); + g2d.drawImage(image, 0, 0, null); + checkColor(bufferedImage.getRGB(3 * IMAGE_WIDTH / 4, 3 * IMAGE_HEIGHT / 4), false); + + // Twice image size + bufferedImage = new BufferedImage(2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT, + BufferedImage.TYPE_INT_RGB); + g2d = (Graphics2D) bufferedImage.getGraphics(); + setImageScalingHint(g2d, enableImageScaling); + g2d.drawImage(image, 0, 0, 2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT, 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, null); + checkColor(bufferedImage.getRGB(3 * IMAGE_WIDTH / 2, 3 * IMAGE_HEIGHT / 2), enableImageScaling); + + // Scale 2x + bufferedImage = new BufferedImage(2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB); + g2d = (Graphics2D) bufferedImage.getGraphics(); + setImageScalingHint(g2d, enableImageScaling); + g2d.scale(2, 2); + g2d.drawImage(image, 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, null); + checkColor(bufferedImage.getRGB(3 * IMAGE_WIDTH / 2, 3 * IMAGE_HEIGHT / 2), enableImageScaling); + + // Rotate + bufferedImage = new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, + BufferedImage.TYPE_INT_RGB); + g2d = (Graphics2D) bufferedImage.getGraphics(); + setImageScalingHint(g2d, enableImageScaling); + g2d.drawImage(image, 0, 0, null); + g2d.rotate(Math.PI / 4); + checkColor(bufferedImage.getRGB(3 * IMAGE_WIDTH / 4, 3 * IMAGE_HEIGHT / 4), false); + + // Scale 2x and Rotate + bufferedImage = new BufferedImage(2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB); + g2d = (Graphics2D) bufferedImage.getGraphics(); + setImageScalingHint(g2d, enableImageScaling); + g2d.scale(-2, 2); + g2d.rotate(-Math.PI / 10); + g2d.drawImage(image, -IMAGE_WIDTH, 0, IMAGE_WIDTH, IMAGE_HEIGHT, null); + checkColor(bufferedImage.getRGB(3 * IMAGE_WIDTH / 2, 3 * IMAGE_HEIGHT / 2), enableImageScaling); + + // General Transform + bufferedImage = new BufferedImage(2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB); + g2d = (Graphics2D) bufferedImage.getGraphics(); + setImageScalingHint(g2d, enableImageScaling); + float delta = 0.05f; + float cos = 1 - delta * delta / 2; + float sin = 1 + delta; + AffineTransform transform = new AffineTransform(2 * cos, 0.1, 0.3, -2 * sin, 10, -5); + g2d.setTransform(transform); + g2d.drawImage(image, 0, -IMAGE_HEIGHT, IMAGE_WIDTH, IMAGE_HEIGHT, null); + checkColor(bufferedImage.getRGB(3 * IMAGE_WIDTH / 2, 3 * IMAGE_HEIGHT / 2), enableImageScaling); + + int D = 10; + // From Source to small Destination region + bufferedImage = new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB); + g2d = (Graphics2D) bufferedImage.getGraphics(); + setImageScalingHint(g2d, enableImageScaling); + g2d.drawImage(image, IMAGE_WIDTH / 2, IMAGE_HEIGHT / 2, IMAGE_WIDTH - D, IMAGE_HEIGHT - D, + D, D, IMAGE_WIDTH - D, IMAGE_HEIGHT - D, null); + checkColor(bufferedImage.getRGB(3 * IMAGE_WIDTH / 4, 3 * IMAGE_HEIGHT / 4), false); + + // From Source to large Destination region + bufferedImage = new BufferedImage(2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB); + g2d = (Graphics2D) bufferedImage.getGraphics(); + setImageScalingHint(g2d, enableImageScaling); + g2d.drawImage(image, D, D, 2 * IMAGE_WIDTH - D, 2 * IMAGE_HEIGHT - D, + IMAGE_WIDTH / 2, IMAGE_HEIGHT / 2, IMAGE_WIDTH - D, IMAGE_HEIGHT - D, null); + checkColor(bufferedImage.getRGB(3 * IMAGE_WIDTH / 2, 3 * IMAGE_HEIGHT / 2), enableImageScaling); + } + + static class MultiResolutionBufferedImage extends BufferedImage + implements MultiResolutionImage { + + Image highResolutionImage; + + public MultiResolutionBufferedImage() { + super(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB); + highResolutionImage = new BufferedImage( + 2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB); + draw(getGraphics(), 1); + draw(highResolutionImage.getGraphics(), 2); + } + + void draw(Graphics graphics, float resolution) { + Graphics2D g2 = (Graphics2D) graphics; + g2.scale(resolution, resolution); + g2.setColor((resolution == 1) ? COLOR_1X : COLOR_2X); + g2.fillRect(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT); + } + + @Override + public Image getResolutionVariant(int width, int height) { + return ((width <= getWidth() && height <= getHeight())) + ? this : highResolutionImage; + } + + @Override + public List getResolutionVariants() { + return Arrays.asList(this, highResolutionImage); + } + } + + static void testToolkitMultiResolutionImagePrepare() throws Exception { + + generateImages(); + + File imageFile = new File(IMAGE_NAME_1X); + String fileName = imageFile.getAbsolutePath(); + + Image image = Toolkit.getDefaultToolkit().getImage(fileName); + + SunToolkit toolkit = (SunToolkit) Toolkit.getDefaultToolkit(); + toolkit.prepareImage(image, IMAGE_WIDTH, IMAGE_HEIGHT, new LoadImageObserver(image)); + + testToolkitMultiResolutionImageLoad(image); + } + + static void testToolkitMultiResolutionImageLoad() throws Exception { + + generateImages(); + + File imageFile = new File(IMAGE_NAME_1X); + String fileName = imageFile.getAbsolutePath(); + Image image = Toolkit.getDefaultToolkit().getImage(fileName); + testToolkitMultiResolutionImageLoad(image); + } + + static void testToolkitMultiResolutionImageLoad(Image image) throws Exception { + + MediaTracker tracker = new MediaTracker(new JPanel()); + tracker.addImage(image, 0); + tracker.waitForID(0); + if (tracker.isErrorAny()) { + throw new RuntimeException("Error during image loading"); + } + tracker.removeImage(image, 0); + + testImageLoaded(image); + + int w = image.getWidth(null); + int h = image.getHeight(null); + + Image resolutionVariant = ((MultiResolutionImage) image) + .getResolutionVariant(2 * w, 2 * h); + + if (image == resolutionVariant) { + throw new RuntimeException("Resolution variant is not loaded"); + } + + testImageLoaded(resolutionVariant); + } + + static void testImageLoaded(Image image) { + + SunToolkit toolkit = (SunToolkit) Toolkit.getDefaultToolkit(); + + int flags = toolkit.checkImage(image, IMAGE_WIDTH, IMAGE_WIDTH, new SilentImageObserver()); + if ((flags & (ImageObserver.FRAMEBITS | ImageObserver.ALLBITS)) == 0) { + throw new RuntimeException("Image is not loaded!"); + } + } + + static class SilentImageObserver implements ImageObserver { + + @Override + public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { + throw new RuntimeException("Observer should not be called!"); + } + } + + static class LoadImageObserver implements ImageObserver { + + Image image; + + public LoadImageObserver(Image image) { + this.image = image; + } + + @Override + public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { + + if (image != img) { + throw new RuntimeException("Original image is not passed to the observer"); + } + + if ((infoflags & ImageObserver.WIDTH) != 0) { + if (width != IMAGE_WIDTH) { + throw new RuntimeException("Original width is not passed to the observer"); + } + } + + if ((infoflags & ImageObserver.HEIGHT) != 0) { + if (height != IMAGE_HEIGHT) { + throw new RuntimeException("Original height is not passed to the observer"); + } + } + + return (infoflags & ALLBITS) == 0; + } + + } + + static void testToolkitMultiResolutionImage() throws Exception { + + generateImages(); + + File imageFile = new File(IMAGE_NAME_1X); + String fileName = imageFile.getAbsolutePath(); + URL url = imageFile.toURI().toURL(); + testToolkitMultiResolutionImageChache(fileName, url); + + Image image = Toolkit.getDefaultToolkit().getImage(fileName); + testToolkitImageObserver(image); + testToolkitMultiResolutionImage(image, false); + testToolkitMultiResolutionImage(image, true); + + image = Toolkit.getDefaultToolkit().getImage(url); + testToolkitImageObserver(image); + testToolkitMultiResolutionImage(image, false); + testToolkitMultiResolutionImage(image, true); + } + + static void testToolkitMultiResolutionImageChache(String fileName, URL url) { + + Image img1 = Toolkit.getDefaultToolkit().getImage(fileName); + if (!(img1 instanceof MultiResolutionImage)) { + throw new RuntimeException("Not a MultiResolutionImage"); + } + + Image img2 = Toolkit.getDefaultToolkit().getImage(fileName); + if (img1 != img2) { + throw new RuntimeException("Image is not cached"); + } + + img1 = Toolkit.getDefaultToolkit().getImage(url); + if (!(img1 instanceof MultiResolutionImage)) { + throw new RuntimeException("Not a MultiResolutionImage"); + } + + img2 = Toolkit.getDefaultToolkit().getImage(url); + if (img1 != img2) { + throw new RuntimeException("Image is not cached"); + } + } + + static void testToolkitMultiResolutionImage(Image image, boolean enableImageScaling) + throws Exception { + + MediaTracker tracker = new MediaTracker(new JPanel()); + tracker.addImage(image, 0); + tracker.waitForID(0); + if (tracker.isErrorAny()) { + throw new RuntimeException("Error during image loading"); + } + + final BufferedImage bufferedImage1x = new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, + BufferedImage.TYPE_INT_RGB); + Graphics2D g1x = (Graphics2D) bufferedImage1x.getGraphics(); + setImageScalingHint(g1x, false); + g1x.drawImage(image, 0, 0, null); + checkColor(bufferedImage1x.getRGB(3 * IMAGE_WIDTH / 4, 3 * IMAGE_HEIGHT / 4), false); + + Image resolutionVariant = ((MultiResolutionImage) image). + getResolutionVariant(2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT); + + if (resolutionVariant == null) { + throw new RuntimeException("Resolution variant is null"); + } + + MediaTracker tracker2x = new MediaTracker(new JPanel()); + tracker2x.addImage(resolutionVariant, 0); + tracker2x.waitForID(0); + if (tracker2x.isErrorAny()) { + throw new RuntimeException("Error during scalable image loading"); + } + + final BufferedImage bufferedImage2x = new BufferedImage(2 * IMAGE_WIDTH, + 2 * IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB); + Graphics2D g2x = (Graphics2D) bufferedImage2x.getGraphics(); + setImageScalingHint(g2x, enableImageScaling); + g2x.drawImage(image, 0, 0, 2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT, 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, null); + checkColor(bufferedImage2x.getRGB(3 * IMAGE_WIDTH / 2, 3 * IMAGE_HEIGHT / 2), enableImageScaling); + + if (!(image instanceof MultiResolutionImage)) { + throw new RuntimeException("Not a MultiResolutionImage"); + } + + MultiResolutionImage multiResolutionImage = (MultiResolutionImage) image; + + Image image1x = multiResolutionImage.getResolutionVariant(IMAGE_WIDTH, IMAGE_HEIGHT); + Image image2x = multiResolutionImage.getResolutionVariant(2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT); + + if (image1x.getWidth(null) * 2 != image2x.getWidth(null) + || image1x.getHeight(null) * 2 != image2x.getHeight(null)) { + throw new RuntimeException("Wrong resolution variant size"); + } + } + + static void testToolkitImageObserver(final Image image) { + + ImageObserver observer = new ImageObserver() { + + @Override + public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { + + if (img != image) { + throw new RuntimeException("Wrong image in observer"); + } + + if ((infoflags & (ImageObserver.ERROR | ImageObserver.ABORT)) != 0) { + throw new RuntimeException("Error during image loading"); + } + + return (infoflags & ImageObserver.ALLBITS) == 0; + + } + }; + + final BufferedImage bufferedImage2x = new BufferedImage(2 * IMAGE_WIDTH, + 2 * IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB); + Graphics2D g2x = (Graphics2D) bufferedImage2x.getGraphics(); + setImageScalingHint(g2x, true); + + g2x.drawImage(image, 0, 0, 2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT, 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, observer); + + } + + static void setImageScalingHint(Graphics2D g2d, boolean enableImageScaling) { + g2d.setRenderingHint(SunHints.KEY_RESOLUTION_VARIANT, enableImageScaling + ? SunHints.VALUE_RESOLUTION_VARIANT_ON + : SunHints.VALUE_RESOLUTION_VARIANT_OFF); + } + + static void checkColor(int rgb, boolean isImageScaled) { + + if (!isImageScaled && COLOR_1X.getRGB() != rgb) { + throw new RuntimeException("Wrong 1x color: " + new Color(rgb)); + } + + if (isImageScaled && COLOR_2X.getRGB() != rgb) { + throw new RuntimeException("Wrong 2x color" + new Color(rgb)); + } + } + + static void generateImages() throws Exception { + if (!new File(IMAGE_NAME_1X).exists()) { + generateImage(1); + } + + if (!new File(IMAGE_NAME_2X).exists()) { + generateImage(2); + } + } + + static void generateImage(int scale) throws Exception { + BufferedImage image = new BufferedImage(scale * IMAGE_WIDTH, scale * IMAGE_HEIGHT, + BufferedImage.TYPE_INT_RGB); + Graphics g = image.getGraphics(); + g.setColor(scale == 1 ? COLOR_1X : COLOR_2X); + g.fillRect(0, 0, scale * IMAGE_WIDTH, scale * IMAGE_HEIGHT); + File file = new File(scale == 1 ? IMAGE_NAME_1X : IMAGE_NAME_2X); + ImageIO.write(image, "png", file); + } + + static void testImageNameTo2xParsing() throws Exception { + + for (String[] testNames : TEST_FILE_NAMES) { + String testName = testNames[0]; + String goldenName = testNames[1]; + String resultName = getTestScaledImageName(testName); + + if (!isValidPath(testName) && resultName == null) { + continue; + } + + if (goldenName.equals(resultName)) { + continue; + } + + throw new RuntimeException("Test name " + testName + + ", result name: " + resultName); + } + + for (URL[] testURLs : TEST_URLS) { + URL testURL = testURLs[0]; + URL goldenURL = testURLs[1]; + URL resultURL = getTestScaledImageURL(testURL); + + if (!isValidPath(testURL.getPath()) && resultURL == null) { + continue; + } + + if (goldenURL.equals(resultURL)) { + continue; + } + + throw new RuntimeException("Test url: " + testURL + + ", result url: " + resultURL); + } + + } + + static URL getTestScaledImageURL(URL url) throws Exception { + Method method = getScalableImageMethod("getScaledImageURL", URL.class); + return (URL) method.invoke(null, url); + } + + static String getTestScaledImageName(String name) throws Exception { + Method method = getScalableImageMethod("getScaledImageName", String.class); + return (String) method.invoke(null, name); + } + + private static boolean isValidPath(String path) { + return !path.isEmpty() && !path.endsWith("/") && !path.endsWith(".") + && !path.contains("@2x"); + } + + private static Method getScalableImageMethod(String name, + Class... parameterTypes) throws Exception { + Toolkit toolkit = Toolkit.getDefaultToolkit(); + Method method = toolkit.getClass().getDeclaredMethod(name, parameterTypes); + method.setAccessible(true); + return method; + } + private static final String[][] TEST_FILE_NAMES; + private static final URL[][] TEST_URLS; + + static { + TEST_FILE_NAMES = new String[][]{ + {"", null}, + {".", null}, + {"..", null}, + {"/", null}, + {"/.", null}, + {"dir/", null}, + {"dir/.", null}, + {"aaa@2x.png", null}, + {"/dir/aaa@2x.png", null}, + {"image", "image@2x"}, + {"image.ext", "image@2x.ext"}, + {"image.aaa.ext", "image.aaa@2x.ext"}, + {"dir/image", "dir/image@2x"}, + {"dir/image.ext", "dir/image@2x.ext"}, + {"dir/image.aaa.ext", "dir/image.aaa@2x.ext"}, + {"dir/aaa.bbb/image", "dir/aaa.bbb/image@2x"}, + {"dir/aaa.bbb/image.ext", "dir/aaa.bbb/image@2x.ext"}, + {"dir/aaa.bbb/image.ccc.ext", "dir/aaa.bbb/image.ccc@2x.ext"}, + {"/dir/image", "/dir/image@2x"}, + {"/dir/image.ext", "/dir/image@2x.ext"}, + {"/dir/image.aaa.ext", "/dir/image.aaa@2x.ext"}, + {"/dir/aaa.bbb/image", "/dir/aaa.bbb/image@2x"}, + {"/dir/aaa.bbb/image.ext", "/dir/aaa.bbb/image@2x.ext"}, + {"/dir/aaa.bbb/image.ccc.ext", "/dir/aaa.bbb/image.ccc@2x.ext"} + }; + try { + TEST_URLS = new URL[][]{ + // file + {new URL("file:/aaa"), new URL("file:/aaa@2x")}, + {new URL("file:/aaa.ext"), new URL("file:/aaa@2x.ext")}, + {new URL("file:/aaa.bbb.ext"), new URL("file:/aaa.bbb@2x.ext")}, + {new URL("file:/ccc/aaa.bbb.ext"), + new URL("file:/ccc/aaa.bbb@2x.ext")}, + {new URL("file:/ccc.ddd/aaa.bbb.ext"), + new URL("file:/ccc.ddd/aaa.bbb@2x.ext")}, + {new URL("file:///~/image"), new URL("file:///~/image@2x")}, + {new URL("file:///~/image.ext"), + new URL("file:///~/image@2x.ext")}, + // http + {new URL("http://www.test.com"), null}, + {new URL("http://www.test.com/"), null}, + {new URL("http://www.test.com///"), null}, + {new URL("http://www.test.com/image"), + new URL("http://www.test.com/image@2x")}, + {new URL("http://www.test.com/image.ext"), + new URL("http://www.test.com/image@2x.ext")}, + {new URL("http://www.test.com/dir/image"), + new URL("http://www.test.com/dir/image@2x")}, + {new URL("http://www.test.com:80/dir/image.aaa.ext"), + new URL("http://www.test.com:80/dir/image.aaa@2x.ext")}, + {new URL("http://www.test.com:8080/dir/image.aaa.ext"), + new URL("http://www.test.com:8080/dir/image.aaa@2x.ext")}, + // jar + {new URL("jar:file:/dir/Java2D.jar!/image"), + new URL("jar:file:/dir/Java2D.jar!/image@2x")}, + {new URL("jar:file:/dir/Java2D.jar!/image.aaa.ext"), + new URL("jar:file:/dir/Java2D.jar!/image.aaa@2x.ext")}, + {new URL("jar:file:/dir/Java2D.jar!/images/image"), + new URL("jar:file:/dir/Java2D.jar!/images/image@2x")}, + {new URL("jar:file:/dir/Java2D.jar!/images/image.ext"), + new URL("jar:file:/dir/Java2D.jar!/images/image@2x.ext")}, + {new URL("jar:file:/aaa.bbb/Java2D.jar!/images/image.ext"), + new URL("jar:file:/aaa.bbb/Java2D.jar!/images/image@2x.ext")}, + {new URL("jar:file:/dir/Java2D.jar!/aaa.bbb/image.ext"), + new URL("jar:file:/dir/Java2D.jar!/aaa.bbb/image@2x.ext")},}; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + static class PreloadedImageObserver implements ImageObserver { + + @Override + public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { + throw new RuntimeException("Image should be already preloaded"); + } + } +}