8011059: [macosx] Support automatic @2x images loading on Mac OS X
Reviewed-by: serb, flar
--- a/jdk/src/macosx/classes/sun/lwawt/macosx/LWCToolkit.java Thu Jan 09 12:47:01 2014 +0400
+++ b/jdk/src/macosx/classes/sun/lwawt/macosx/LWCToolkit.java Thu Jan 09 18:04:31 2014 +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(".");
+ }
}
--- a/jdk/src/share/classes/java/awt/MediaTracker.java Thu Jan 09 12:47:01 2014 +0400
+++ b/jdk/src/share/classes/java/awt/MediaTracker.java Thu Jan 09 18:04:31 2014 +0400
@@ -28,6 +28,7 @@
import java.awt.Component;
import java.awt.Image;
import java.awt.image.ImageObserver;
+import sun.awt.image.MultiResolutionToolkitImage;
/**
* The <code>MediaTracker</code> 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 {
--- a/jdk/src/share/classes/sun/awt/SunHints.java Thu Jan 09 12:47:01 2014 +0400
+++ b/jdk/src/share/classes/sun/awt/SunHints.java Thu Jan 09 18:04:31 2014 +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 {
--- a/jdk/src/share/classes/sun/awt/SunToolkit.java Thu Jan 09 12:47:01 2014 +0400
+++ b/jdk/src/share/classes/sun/awt/SunToolkit.java Thu Jan 09 18:04:31 2014 +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,6 +718,194 @@
static final SoftCache imgCache = new SoftCache();
static Image getImageFromHash(Toolkit tk, URL url) {
+ checkPermissions(url);
+ synchronized (imgCache) {
+ Image img = (Image)imgCache.get(url);
+ if (img == null) {
+ try {
+ img = tk.createImage(new URLImageSource(url));
+ imgCache.put(url, img);
+ } catch (Exception e) {
+ }
+ }
+ return img;
+ }
+ }
+
+ static Image getImageFromHash(Toolkit tk,
+ String filename) {
+ checkPermissions(filename);
+ synchronized (imgCache) {
+ Image img = (Image)imgCache.get(filename);
+ if (img == null) {
+ try {
+ img = tk.createImage(new FileImageSource(filename));
+ imgCache.put(filename, img);
+ } catch (Exception e) {
+ }
+ }
+ return img;
+ }
+ }
+
+ public Image getImage(String filename) {
+ return getImageFromHash(this, filename);
+ }
+
+ public Image getImage(URL url) {
+ return getImageFromHash(this, url);
+ }
+
+ 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) {
+ checkPermissions(url);
+ return createImage(new URLImageSource(url));
+ }
+
+ public Image createImage(byte[] data, int offset, int length) {
+ return createImage(new ByteArrayImageSource(data, offset, length));
+ }
+
+ public Image createImage(ImageProducer producer) {
+ 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;
+ }
+
+ ToolkitImage tkimg = (ToolkitImage)img;
+ int repbits;
+ if (w == 0 || h == 0) {
+ repbits = ImageObserver.ALLBITS;
+ } else {
+ repbits = tkimg.getImageRep().check(o);
+ }
+ return (tkimg.check(o) | repbits) & checkResolutionVariant(img, w, h, o);
+ }
+
+ public boolean prepareImage(Image img, int w, int h, ImageObserver o) {
+ if (w == 0 || h == 0) {
+ return true;
+ }
+
+ // Must be a ToolkitImage
+ if (!(img instanceof ToolkitImage)) {
+ return true;
+ }
+
+ ToolkitImage tkimg = (ToolkitImage)img;
+ if (tkimg.hasError()) {
+ if (o != null) {
+ o.imageUpdate(img, ImageObserver.ERROR|ImageObserver.ABORT,
+ -1, -1, -1, -1);
+ }
+ return false;
+ }
+ ImageRepresentation ir = tkimg.getImageRep();
+ 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 {
@@ -742,128 +933,6 @@
sm.checkConnect(url.getHost(), url.getPort());
}
}
- synchronized (imgCache) {
- Image img = (Image)imgCache.get(url);
- if (img == null) {
- try {
- img = tk.createImage(new URLImageSource(url));
- imgCache.put(url, img);
- } catch (Exception e) {
- }
- }
- return img;
- }
- }
-
- static Image getImageFromHash(Toolkit tk,
- String filename) {
- SecurityManager security = System.getSecurityManager();
- if (security != null) {
- security.checkRead(filename);
- }
- synchronized (imgCache) {
- Image img = (Image)imgCache.get(filename);
- if (img == null) {
- try {
- img = tk.createImage(new FileImageSource(filename));
- imgCache.put(filename, img);
- } catch (Exception e) {
- }
- }
- return img;
- }
- }
-
- public Image getImage(String filename) {
- return getImageFromHash(this, filename);
- }
-
- public Image getImage(URL url) {
- return getImageFromHash(this, url);
- }
-
- public Image createImage(String filename) {
- SecurityManager security = System.getSecurityManager();
- if (security != null) {
- security.checkRead(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());
- }
- }
- return createImage(new URLImageSource(url));
- }
-
- public Image createImage(byte[] data, int offset, int length) {
- return createImage(new ByteArrayImageSource(data, offset, length));
- }
-
- public Image createImage(ImageProducer producer) {
- return new ToolkitImage(producer);
- }
-
- public int checkImage(Image img, int w, int h, ImageObserver o) {
- if (!(img instanceof ToolkitImage)) {
- return ImageObserver.ALLBITS;
- }
-
- ToolkitImage tkimg = (ToolkitImage)img;
- int repbits;
- if (w == 0 || h == 0) {
- repbits = ImageObserver.ALLBITS;
- } else {
- repbits = tkimg.getImageRep().check(o);
- }
- return tkimg.check(o) | repbits;
- }
-
- public boolean prepareImage(Image img, int w, int h, ImageObserver o) {
- if (w == 0 || h == 0) {
- return true;
- }
-
- // Must be a ToolkitImage
- if (!(img instanceof ToolkitImage)) {
- return true;
- }
-
- ToolkitImage tkimg = (ToolkitImage)img;
- if (tkimg.hasError()) {
- if (o != null) {
- o.imageUpdate(img, ImageObserver.ERROR|ImageObserver.ABORT,
- -1, -1, -1, -1);
- }
- return false;
- }
- ImageRepresentation ir = tkimg.getImageRep();
- return ir.prepare(o);
}
/**
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/awt/image/MultiResolutionImage.java Thu Jan 09 18:04:31 2014 +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 <code>MultiResolutionImage</code> 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,
+ * <pre>
+ * {@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<Image> getResolutionVariants() {
+ * return Arrays.asList(this, highResolutionImage);
+ * }
+ * }
+ * }</pre>
+ *
+ * It is recommended to cache image variants for performance reasons.
+ *
+ * <b>WARNING</b>: 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<Image> getResolutionVariants();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/awt/image/MultiResolutionToolkitImage.java Thu Jan 09 18:04:31 2014 +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<Image> getResolutionVariants() {
+ return Arrays.<Image>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;
+ }
+ }
+}
--- a/jdk/src/share/classes/sun/java2d/SunGraphics2D.java Thu Jan 09 12:47:01 2014 +0400
+++ b/jdk/src/share/classes/sun/java2d/SunGraphics2D.java Thu Jan 09 18:04:31 2014 +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.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/awt/image/MultiResolutionImageTest.java Thu Jan 09 18:04:31 2014 +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<Image> 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");
+ }
+ }
+}