8035069: [macosx] Loading resolution variants by demand
authoralexsch
Tue, 18 Mar 2014 14:48:47 +0400
changeset 23666 a54bf43b5ba9
parent 23665 3597c2a96fd3
child 23667 37f0fbe26e84
8035069: [macosx] Loading resolution variants by demand Reviewed-by: serb, pchelko
jdk/src/macosx/classes/com/apple/laf/AquaIcon.java
jdk/src/macosx/classes/com/apple/laf/AquaImageFactory.java
jdk/src/macosx/classes/com/apple/laf/AquaPainter.java
jdk/src/macosx/classes/com/apple/laf/AquaUtils.java
jdk/src/macosx/classes/com/apple/laf/ImageCache.java
jdk/src/macosx/classes/sun/lwawt/macosx/CImage.java
jdk/src/share/classes/sun/awt/AppContext.java
jdk/src/share/classes/sun/awt/image/ImageCache.java
jdk/src/share/classes/sun/awt/image/MultiResolutionBufferedImage.java
jdk/test/java/awt/image/MultiResolutionImage/NSImageToMultiResolutionImageTest.java
--- a/jdk/src/macosx/classes/com/apple/laf/AquaIcon.java	Tue Mar 18 12:30:17 2014 +0400
+++ b/jdk/src/macosx/classes/com/apple/laf/AquaIcon.java	Tue Mar 18 14:48:47 2014 +0400
@@ -295,14 +295,8 @@
         }
 
         Image createImage() {
-            int w = getIconWidth();
-            int h = getIconHeight();
-            return new AquaImageFactory.MultiResolutionIconImage(
-                    AquaUtils.getCImageCreator().createSystemImageFromSelector(
-                            selector, w, h),
-                    AquaUtils.getCImageCreator().createSystemImageFromSelector(
-                            selector, 2 * w, 2 * h)
-            );
+            return AquaUtils.getCImageCreator().createSystemImageFromSelector(
+                    selector, getIconWidth(), getIconHeight());
         }
     }
 }
--- a/jdk/src/macosx/classes/com/apple/laf/AquaImageFactory.java	Tue Mar 18 12:30:17 2014 +0400
+++ b/jdk/src/macosx/classes/com/apple/laf/AquaImageFactory.java	Tue Mar 18 14:48:47 2014 +0400
@@ -125,16 +125,14 @@
     private static final int kAlertIconSize = 64;
     static IconUIResource getAppIconCompositedOn(final Image background) {
 
-        final BufferedImage iconImage = getAppIconImageCompositedOn(background, 1);
+        if (background instanceof MultiResolutionBufferedImage) {
+            int width = background.getWidth(null);
+            Image mrIconImage = ((MultiResolutionBufferedImage) background).map(
+                    rv -> getAppIconImageCompositedOn(rv, rv.getWidth(null) / width));
+            return new IconUIResource(new ImageIcon(mrIconImage));
+        }
 
-        if (background instanceof MultiResolutionIconImage) {
-            BufferedImage background2x
-                    = ((MultiResolutionIconImage) background).resolutionVariant;
-            BufferedImage icon2xImage = getAppIconImageCompositedOn(background2x, 2);
-
-            return new IconUIResource(new ImageIcon(
-                    new MultiResolutionIconImage(iconImage, icon2xImage)));
-        }
+        BufferedImage iconImage = getAppIconImageCompositedOn(background, 1);
         return new IconUIResource(new ImageIcon(iconImage));
     }
 
@@ -313,10 +311,16 @@
             return icon;
         }
 
-        Image icon2x = AquaUtils.getCImageCreator().createImageFromName(
-                imageName, 2 * icon.getWidth(null), 2 * icon.getHeight(null));
-        return new MultiResolutionBufferedImage(
-                BufferedImage.TYPE_INT_ARGB_PRE, 0, icon, icon2x);
+        int w = icon.getWidth(null);
+        int h = icon.getHeight(null);
+
+        Dimension[] sizes = new Dimension[]{
+            new Dimension(w, h), new Dimension(2 * w, 2 * h)
+        };
+
+        return new MultiResolutionBufferedImage(icon, sizes, (width, height) ->
+                AquaUtils.getCImageCreator().createImageFromName(
+                        imageName, width, height));
     }
 
     public static class NineSliceMetrics {
@@ -526,29 +530,4 @@
     public static Color getSelectionInactiveForegroundColorUIResource() {
         return new SystemColorProxy(LWCToolkit.getAppleColor(LWCToolkit.INACTIVE_SELECTION_FOREGROUND_COLOR));
     }
-
-    static class MultiResolutionIconImage extends BufferedImage
-            implements MultiResolutionImage {
-
-        BufferedImage resolutionVariant;
-
-        public MultiResolutionIconImage(BufferedImage image, BufferedImage resolutionVariant) {
-            super(image.getWidth(), image.getHeight(), image.getType());
-            this.resolutionVariant = resolutionVariant;
-            Graphics g = getGraphics();
-            g.drawImage(image, 0, 0, null);
-            g.dispose();
-        }
-
-        @Override
-        public Image getResolutionVariant(int width, int height) {
-            return ((width <= getWidth() && height <= getHeight()))
-                    ? this : resolutionVariant;
-        }
-
-        @Override
-        public List<Image> getResolutionVariants() {
-            return Arrays.asList(this, resolutionVariant);
-        }
-    }
-}
+}
\ No newline at end of file
--- a/jdk/src/macosx/classes/com/apple/laf/AquaPainter.java	Tue Mar 18 12:30:17 2014 +0400
+++ b/jdk/src/macosx/classes/com/apple/laf/AquaPainter.java	Tue Mar 18 14:48:47 2014 +0400
@@ -38,6 +38,7 @@
 import sun.print.*;
 import apple.laf.*;
 import apple.laf.JRSUIUtils.NineSliceMetricsProvider;
+import sun.awt.image.ImageCache;
 
 abstract class AquaPainter <T extends JRSUIState> {
     static <T extends JRSUIState> AquaPainter<T> create(final T state) {
@@ -155,10 +156,15 @@
             final ImageCache cache = ImageCache.getInstance();
             final int imgW = bounds.width * scale;
             final int imgH = bounds.height * scale;
-            BufferedImage img = (BufferedImage) cache.getImage(config, imgW, imgH, scale, controlState);
+            AquaPixelsKey key = new AquaPixelsKey(config,
+                    imgW, imgH, scale, controlState);
+            BufferedImage img = (BufferedImage) cache.getImage(key);
             if (img == null) {
                 img = new BufferedImage(imgW, imgH, BufferedImage.TYPE_INT_ARGB_PRE);
-                cache.setImage(img, config, imgW, imgH, scale, controlState);
+                if (!controlState.is(JRSUIConstants.Animating.YES)) {
+                    cache.setImage(key, img);
+                }
+
                 final WritableRaster raster = img.getRaster();
                 final DataBufferInt buffer = (DataBufferInt) raster.getDataBuffer();
 
@@ -172,6 +178,59 @@
         }
     }
 
+    private static class AquaPixelsKey implements ImageCache.PixelsKey {
+
+        private final int pixelCount;
+        private final int hash;
+
+        // key parts
+        private final GraphicsConfiguration config;
+        private final int w;
+        private final int h;
+        private final int scale;
+        private final JRSUIState state;
+
+        AquaPixelsKey(final GraphicsConfiguration config,
+                final int w, final int h, final int scale,
+                final JRSUIState state) {
+            this.pixelCount = w * h;
+            this.config = config;
+            this.w = w;
+            this.h = h;
+            this.scale = scale;
+            this.state = state;
+            this.hash = hash();
+        }
+
+        public int getPixelCount() {
+            return pixelCount;
+        }
+
+        private int hash() {
+            int hash = config != null ? config.hashCode() : 0;
+            hash = 31 * hash + w;
+            hash = 31 * hash + h;
+            hash = 31 * hash + scale;
+            hash = 31 * hash + state.hashCode();
+            return hash;
+        }
+
+        @Override
+        public int hashCode() {
+            return hash;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof AquaPixelsKey) {
+                AquaPixelsKey key = (AquaPixelsKey) obj;
+                return config == key.config && w == key.w && h == key.h
+                        && scale == key.scale && state.equals(key.state);
+            }
+            return false;
+        }
+    }
+
     private static class RecyclableJRSUISlicedImageControl
             extends RecyclableSlicedImageControl {
 
--- a/jdk/src/macosx/classes/com/apple/laf/AquaUtils.java	Tue Mar 18 12:30:17 2014 +0400
+++ b/jdk/src/macosx/classes/com/apple/laf/AquaUtils.java	Tue Mar 18 14:48:47 2014 +0400
@@ -177,16 +177,7 @@
 
     abstract static class RecyclableSingleton<T> {
         final T get() {
-            final AppContext appContext = AppContext.getAppContext();
-            SoftReference<T> ref = (SoftReference<T>) appContext.get(this);
-            if (ref != null) {
-                final T object = ref.get();
-                if (object != null) return object;
-            }
-            final T object = getInstance();
-            ref = new SoftReference<T>(object);
-            appContext.put(this, ref);
-            return object;
+            return AppContext.getSoftReferenceValue(this, () -> getInstance());
         }
 
         void reset() {
--- a/jdk/src/macosx/classes/com/apple/laf/ImageCache.java	Tue Mar 18 12:30:17 2014 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,266 +0,0 @@
-/*
- * Copyright (c) 2011, 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 com.apple.laf;
-
-import java.awt.*;
-import java.lang.ref.*;
-import java.util.*;
-import java.util.concurrent.locks.*;
-
-import apple.laf.JRSUIConstants;
-import apple.laf.JRSUIState;
-import com.apple.laf.AquaUtils.RecyclableSingleton;
-
-/**
- * ImageCache - A fixed pixel count sized cache of Images keyed by arbitrary set of arguments. All images are held with
- * SoftReferences so they will be dropped by the GC if heap memory gets tight. When our size hits max pixel count least
- * recently requested images are removed first.
- */
-final class ImageCache {
-    // Ordered Map keyed by args hash, ordered by most recent accessed entry.
-    private final LinkedHashMap<Integer, PixelCountSoftReference> map = new LinkedHashMap<>(16, 0.75f, true);
-
-    // Maximum number of pixels to cache, this is used if maxCount
-    private final int maxPixelCount;
-    // The current number of pixels stored in the cache
-    private int currentPixelCount = 0;
-
-    // Lock for concurrent access to map
-    private final ReadWriteLock lock = new ReentrantReadWriteLock();
-    // Reference queue for tracking lost softreferences to images in the cache
-    private final ReferenceQueue<Image> referenceQueue = new ReferenceQueue<>();
-
-    // Singleton Instance
-    private static final RecyclableSingleton<ImageCache> instance = new RecyclableSingleton<ImageCache>() {
-        @Override
-        protected ImageCache getInstance() {
-            return new ImageCache();
-        }
-    };
-    static ImageCache getInstance() {
-        return instance.get();
-    }
-
-    ImageCache(final int maxPixelCount) {
-        this.maxPixelCount = maxPixelCount;
-    }
-
-    ImageCache() {
-        this((8 * 1024 * 1024) / 4); // 8Mb of pixels
-    }
-
-    public void flush() {
-        lock.writeLock().lock();
-        try {
-            map.clear();
-        } finally {
-            lock.writeLock().unlock();
-        }
-    }
-
-    public Image getImage(final GraphicsConfiguration config, final int w,
-                          final int h, final int scale,
-                          final JRSUIState state) {
-        final int hash = hash(config, w, h, scale, state);
-        final PixelCountSoftReference ref;
-        lock.readLock().lock();
-        try {
-            ref = map.get(hash);
-        } finally {
-            lock.readLock().unlock();
-        }
-        // check reference has not been lost and the key truly matches,
-        // in case of false positive hash match
-        if (ref != null && ref.equals(config, w, h, scale, state)) {
-            return ref.get();
-        }
-        return null;
-    }
-
-    /**
-     * Sets the cached image for the specified constraints.
-     *
-     * @param image  The image to store in cache
-     * @param config The graphics configuration, needed if cached image is a Volatile Image. Used as part of cache key
-     * @param w      The image width, used as part of cache key
-     * @param h      The image height, used as part of cache key
-     * @param scale  The image scale factor, used as part of cache key
-     * @return true if the image could be cached, false otherwise.
-     */
-    public boolean setImage(final Image image,
-            final GraphicsConfiguration config, final int w, final int h,
-            final int scale, final JRSUIState state) {
-        if (state.is(JRSUIConstants.Animating.YES)) {
-            return false;
-        }
-
-        final int hash = hash(config, w, h, scale, state);
-
-        lock.writeLock().lock();
-        try {
-            PixelCountSoftReference ref = map.get(hash);
-            // check if currently in map
-            if (ref != null && ref.get() == image) return true;
-
-            // clear out old
-            if (ref != null) {
-                currentPixelCount -= ref.pixelCount;
-                map.remove(hash);
-            }
-
-            // add new image to pixel count
-            final int newPixelCount = image.getWidth(null) * image.getHeight(null);
-            currentPixelCount += newPixelCount;
-            // clean out lost references if not enough space
-            if (currentPixelCount > maxPixelCount) {
-                while ((ref = (PixelCountSoftReference)referenceQueue.poll()) != null) {
-                    //reference lost
-                    map.remove(ref.hash);
-                    currentPixelCount -= ref.pixelCount;
-                }
-            }
-
-            // remove old items till there is enough free space
-            if (currentPixelCount > maxPixelCount) {
-                final Iterator<Map.Entry<Integer, PixelCountSoftReference>> mapIter = map.entrySet().iterator();
-                while ((currentPixelCount > maxPixelCount) && mapIter.hasNext()) {
-                    final Map.Entry<Integer, PixelCountSoftReference> entry = mapIter.next();
-                    mapIter.remove();
-                    final Image img = entry.getValue().get();
-                    if (img != null) img.flush();
-                    currentPixelCount -= entry.getValue().pixelCount;
-                }
-            }
-            // finally put new in map
-            map.put(hash, new PixelCountSoftReference(image, referenceQueue, newPixelCount, hash, config, w, h, scale, state));
-            return true;
-        } finally {
-            lock.writeLock().unlock();
-        }
-    }
-
-    private static int hash(final GraphicsConfiguration config, final int w,
-                            final int h, final int scale,
-                            final JRSUIState state) {
-        int hash = config != null ? config.hashCode() : 0;
-        hash = 31 * hash + w;
-        hash = 31 * hash + h;
-        hash = 31 * hash + scale;
-        hash = 31 * hash + state.hashCode();
-        return hash;
-    }
-
-    /**
-     * Extended SoftReference that stores the pixel count even after the image
-     * is lost.
-     */
-    private static class PixelCountSoftReference extends SoftReference<Image> {
-
-        // default access, because access to these fields shouldn't be emulated
-        // by a synthetic accessor.
-        final int pixelCount;
-        final int hash;
-
-        // key parts
-        private final GraphicsConfiguration config;
-        private final int w;
-        private final int h;
-        private final int scale;
-        private final JRSUIState state;
-
-        PixelCountSoftReference(final Image referent,
-                final ReferenceQueue<? super Image> q, final int pixelCount,
-                final int hash, final GraphicsConfiguration config, final int w,
-                final int h, final int scale, final JRSUIState state) {
-            super(referent, q);
-            this.pixelCount = pixelCount;
-            this.hash = hash;
-            this.config = config;
-            this.w = w;
-            this.h = h;
-            this.scale = scale;
-            this.state = state;
-        }
-
-        boolean equals(final GraphicsConfiguration config, final int w,
-                       final int h, final int scale, final JRSUIState state) {
-            return config == this.config && w == this.w && h == this.h
-                    && scale == this.scale && state.equals(this.state);
-        }
-    }
-
-//    /** Gets the rendered image for this painter at the requested size, either from cache or create a new one */
-//    private VolatileImage getImage(GraphicsConfiguration config, JComponent c, int w, int h, Object[] extendedCacheKeys) {
-//        VolatileImage buffer = (VolatileImage)getImage(config, w, h, this, extendedCacheKeys);
-//
-//        int renderCounter = 0; // to avoid any potential, though unlikely, infinite loop
-//        do {
-//            //validate the buffer so we can check for surface loss
-//            int bufferStatus = VolatileImage.IMAGE_INCOMPATIBLE;
-//            if (buffer != null) {
-//                bufferStatus = buffer.validate(config);
-//            }
-//
-//            //If the buffer status is incompatible or restored, then we need to re-render to the volatile image
-//            if (bufferStatus == VolatileImage.IMAGE_INCOMPATIBLE || bufferStatus == VolatileImage.IMAGE_RESTORED) {
-//                // if the buffer isn't the right size, or has lost its contents, then recreate
-//                if (buffer != null) {
-//                    if (buffer.getWidth() != w || buffer.getHeight() != h || bufferStatus == VolatileImage.IMAGE_INCOMPATIBLE) {
-//                        // clear any resources related to the old back buffer
-//                        buffer.flush();
-//                        buffer = null;
-//                    }
-//                }
-//
-//                if (buffer == null) {
-//                    // recreate the buffer
-//                    buffer = config.createCompatibleVolatileImage(w, h, Transparency.TRANSLUCENT);
-//                    // put in cache for future
-//                    setImage(buffer, config, w, h, this, extendedCacheKeys);
-//                }
-//
-//                //create the graphics context with which to paint to the buffer
-//                Graphics2D bg = buffer.createGraphics();
-//
-//                //clear the background before configuring the graphics
-//                bg.setComposite(AlphaComposite.Clear);
-//                bg.fillRect(0, 0, w, h);
-//                bg.setComposite(AlphaComposite.SrcOver);
-//                bg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-//
-//                // paint the painter into buffer
-//                paint0(bg, c, w, h, extendedCacheKeys);
-//                //close buffer graphics
-//                bg.dispose();
-//            }
-//        } while (buffer.contentsLost() && renderCounter++ < 3);
-//
-//        // check if we failed
-//        if (renderCounter >= 3) return null;
-//
-//        return buffer;
-//    }
-}
--- a/jdk/src/macosx/classes/sun/lwawt/macosx/CImage.java	Tue Mar 18 12:30:17 2014 +0400
+++ b/jdk/src/macosx/classes/sun/lwawt/macosx/CImage.java	Tue Mar 18 14:48:47 2014 +0400
@@ -243,24 +243,11 @@
                 = nativeGetNSImageRepresentationSizes(ptr,
                         size.getWidth(), size.getHeight());
 
-        if (sizes == null || sizes.length < 2) {
-            return toImage(w, h, w, h);
-        }
-
-        BufferedImage[] images = new BufferedImage[sizes.length];
-        int currentImageIndex = 0;
+        BufferedImage baseImage = toImage(w, h, w, h);
 
-        for (int i = 0; i < sizes.length; i++) {
-            int imageRepWidth = (int) sizes[i].getWidth();
-            int imageRepHeight = (int) sizes[i].getHeight();
-
-            if(imageRepHeight <= w && imageRepHeight <= h){
-                currentImageIndex = i;
-            }
-            images[i] = toImage(w, h, imageRepWidth, imageRepHeight);
-        }
-        return new MultiResolutionBufferedImage(BufferedImage.TYPE_INT_ARGB_PRE,
-                currentImageIndex, images);
+        return sizes == null || sizes.length < 2 ? baseImage
+                : new MultiResolutionBufferedImage(baseImage, sizes,
+                        (width, height) -> toImage(w, h, width, height));
     }
 
     private BufferedImage toImage(int srcWidth, int srcHeight, int dstWidth, int dstHeight) {
--- a/jdk/src/share/classes/sun/awt/AppContext.java	Tue Mar 18 12:30:17 2014 +0400
+++ b/jdk/src/share/classes/sun/awt/AppContext.java	Tue Mar 18 14:48:47 2014 +0400
@@ -42,11 +42,13 @@
 import java.util.HashSet;
 import java.beans.PropertyChangeSupport;
 import java.beans.PropertyChangeListener;
+import java.lang.ref.SoftReference;
 import sun.util.logging.PlatformLogger;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
 
 /**
  * The AppContext is a table referenced by ThreadGroup which stores
@@ -883,6 +885,23 @@
 
         });
     }
+
+    public static <T> T getSoftReferenceValue(Object key,
+            Supplier<T> supplier) {
+
+        final AppContext appContext = AppContext.getAppContext();
+        SoftReference<T> ref = (SoftReference<T>) appContext.get(key);
+        if (ref != null) {
+            final T object = ref.get();
+            if (object != null) {
+                return object;
+            }
+        }
+        final T object = supplier.get();
+        ref = new SoftReference<>(object);
+        appContext.put(key, ref);
+        return object;
+    }
 }
 
 final class MostRecentKeyValue {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/awt/image/ImageCache.java	Tue Mar 18 14:48:47 2014 +0400
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2011, 2014, 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.*;
+import java.lang.ref.*;
+import java.util.*;
+import java.util.concurrent.locks.*;
+import sun.awt.AppContext;
+
+/**
+ * ImageCache - A fixed pixel count sized cache of Images keyed by arbitrary
+ * set of arguments. All images are held with SoftReferences so they will be
+ * dropped by the GC if heap memory gets tight. When our size hits max pixel
+ * count least recently requested images are removed first.
+ *
+ * The ImageCache must be used from the thread with an AppContext only.
+ *
+ */
+final public class ImageCache {
+
+    // Ordered Map keyed by args hash, ordered by most recent accessed entry.
+    private final LinkedHashMap<PixelsKey, ImageSoftReference> map
+            = new LinkedHashMap<>(16, 0.75f, true);
+
+    // Maximum number of pixels to cache, this is used if maxCount
+    private final int maxPixelCount;
+    // The current number of pixels stored in the cache
+    private int currentPixelCount = 0;
+
+    // Lock for concurrent access to map
+    private final ReadWriteLock lock = new ReentrantReadWriteLock();
+    // Reference queue for tracking lost softreferences to images in the cache
+    private final ReferenceQueue<Image> referenceQueue = new ReferenceQueue<>();
+
+    public static ImageCache getInstance() {
+        return AppContext.getSoftReferenceValue(ImageCache.class,
+                () -> new ImageCache());
+    }
+
+    ImageCache(final int maxPixelCount) {
+        this.maxPixelCount = maxPixelCount;
+    }
+
+    ImageCache() {
+        this((8 * 1024 * 1024) / 4); // 8Mb of pixels
+    }
+
+    public void flush() {
+        lock.writeLock().lock();
+        try {
+            map.clear();
+        } finally {
+            lock.writeLock().unlock();
+        }
+    }
+
+    public Image getImage(final PixelsKey key){
+        final ImageSoftReference ref;
+        lock.readLock().lock();
+        try {
+            ref = map.get(key);
+        } finally {
+            lock.readLock().unlock();
+        }
+        return ref == null ? null : ref.get();
+    }
+
+    /**
+     * Sets the cached image for the specified constraints.
+     *
+     * @param key The key with which the specified image is to be associated
+     * @param image  The image to store in cache
+     */
+    public void setImage(final PixelsKey key, final Image image) {
+
+        lock.writeLock().lock();
+        try {
+            ImageSoftReference ref = map.get(key);
+
+            // check if currently in map
+            if (ref != null) {
+                if (ref.get() != null) {
+                    return;
+                }
+                // soft image has been removed
+                currentPixelCount -= key.getPixelCount();
+                map.remove(key);
+            };
+
+
+            // add new image to pixel count
+            final int newPixelCount = key.getPixelCount();
+            currentPixelCount += newPixelCount;
+            // clean out lost references if not enough space
+            if (currentPixelCount > maxPixelCount) {
+                while ((ref = (ImageSoftReference)referenceQueue.poll()) != null) {
+                    //reference lost
+                    map.remove(ref.key);
+                    currentPixelCount -= ref.key.getPixelCount();
+                }
+            }
+
+            // remove old items till there is enough free space
+            if (currentPixelCount > maxPixelCount) {
+                final Iterator<Map.Entry<PixelsKey, ImageSoftReference>>
+                        mapIter = map.entrySet().iterator();
+                while ((currentPixelCount > maxPixelCount) && mapIter.hasNext()) {
+                    final Map.Entry<PixelsKey, ImageSoftReference> entry =
+                            mapIter.next();
+                    mapIter.remove();
+                    final Image img = entry.getValue().get();
+                    if (img != null) img.flush();
+                    currentPixelCount -= entry.getValue().key.getPixelCount();
+                }
+            }
+
+            // finally put new in map
+            map.put(key, new ImageSoftReference(key, image, referenceQueue));
+        } finally {
+            lock.writeLock().unlock();
+        }
+    }
+
+    public interface PixelsKey {
+
+        int getPixelCount();
+    }
+
+    private static class ImageSoftReference extends SoftReference<Image> {
+
+        final PixelsKey key;
+
+        ImageSoftReference(final PixelsKey key, final Image referent,
+                final ReferenceQueue<? super Image> q) {
+            super(referent, q);
+            this.key = key;
+        }
+    }
+}
--- a/jdk/src/share/classes/sun/awt/image/MultiResolutionBufferedImage.java	Tue Mar 18 12:30:17 2014 +0400
+++ b/jdk/src/share/classes/sun/awt/image/MultiResolutionBufferedImage.java	Tue Mar 18 14:48:47 2014 +0400
@@ -26,46 +26,152 @@
 
 import java.awt.Image;
 import java.awt.Graphics;
+import java.awt.geom.Dimension2D;
 import java.awt.image.BufferedImage;
+import java.awt.image.ImageObserver;
 import java.util.Arrays;
 import java.util.List;
 import java.util.function.Function;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
 
 public class MultiResolutionBufferedImage extends BufferedImage
         implements MultiResolutionImage {
 
-    Image[] resolutionVariants;
-    int baseIndex;
+    private final BiFunction<Integer, Integer, Image> mapper;
+    private final Dimension2D[] sizes;
+    private int availableInfo;
 
-    public MultiResolutionBufferedImage(int imageType, int baseIndex, Image... images) {
-        super(images[baseIndex].getWidth(null), images[baseIndex].getHeight(null),
-                imageType);
-        this.baseIndex = baseIndex;
-        this.resolutionVariants = images;
+    public MultiResolutionBufferedImage(Image baseImage,
+            Dimension2D[] sizes, BiFunction<Integer, Integer, Image> mapper) {
+        super(baseImage.getWidth(null), baseImage.getHeight(null),
+                BufferedImage.TYPE_INT_ARGB_PRE);
+        this.sizes = sizes;
+        this.mapper = mapper;
+        this.availableInfo = getInfo(baseImage);
         Graphics g = getGraphics();
-        g.drawImage(images[baseIndex], 0, 0, null);
+        g.drawImage(baseImage, 0, 0, null);
         g.dispose();
-        images[baseIndex] = this;
     }
 
     @Override
     public Image getResolutionVariant(int width, int height) {
-        for (Image image : resolutionVariants) {
-            if (width <= image.getWidth(null) && height <= image.getHeight(null)) {
-                return image;
-            }
+        int baseWidth = getWidth();
+        int baseHeight = getHeight();
+
+        if (baseWidth == width && baseHeight == height) {
+            return this;
         }
-        return this;
+
+        ImageCache cache = ImageCache.getInstance();
+        ImageCacheKey key = new ImageCacheKey(this, width, height);
+        Image resolutionVariant = cache.getImage(key);
+        if (resolutionVariant == null) {
+            resolutionVariant = mapper.apply(width, height);
+            cache.setImage(key, resolutionVariant);
+            preload(resolutionVariant, availableInfo);
+        }
+
+        return resolutionVariant;
     }
 
     @Override
     public List<Image> getResolutionVariants() {
-        return Arrays.asList(resolutionVariants);
+        return Arrays.stream(sizes).map((Function<Dimension2D, Image>) size
+                -> getResolutionVariant((int) size.getWidth(),
+                        (int) size.getHeight())).collect(Collectors.toList());
     }
 
     public MultiResolutionBufferedImage map(Function<Image, Image> mapper) {
-        return new MultiResolutionBufferedImage(getType(), baseIndex,
-                Arrays.stream(resolutionVariants).map(mapper)
-                        .toArray(length -> new Image[length]));
+        return new MultiResolutionBufferedImage(mapper.apply(this), sizes,
+                (width, height) ->
+                        mapper.apply(getResolutionVariant(width, height)));
+    }
+
+    @Override
+    public int getWidth(ImageObserver observer) {
+        availableInfo |= ImageObserver.WIDTH;
+        return super.getWidth(observer);
+    }
+
+    @Override
+    public int getHeight(ImageObserver observer) {
+        availableInfo |= ImageObserver.HEIGHT;
+        return super.getHeight(observer);
+    }
+
+    @Override
+    public Object getProperty(String name, ImageObserver observer) {
+        availableInfo |= ImageObserver.PROPERTIES;
+        return super.getProperty(name, observer);
+    }
+
+    private static int getInfo(Image image) {
+        if (image instanceof ToolkitImage) {
+            return ((ToolkitImage) image).getImageRep().check(
+                    (img, infoflags, x, y, w, h) -> false);
+        }
+        return 0;
+    }
+
+    private static void preload(Image image, int availableInfo) {
+        if (image instanceof ToolkitImage) {
+            ((ToolkitImage) image).preload(new ImageObserver() {
+                int flags = availableInfo;
+
+                @Override
+                public boolean imageUpdate(Image img, int infoflags,
+                        int x, int y, int width, int height) {
+                    flags &= ~infoflags;
+                    return (flags != 0) && ((infoflags
+                            & (ImageObserver.ERROR | ImageObserver.ABORT)) == 0);
+                }
+            });
+        }
     }
-}
+
+    private static class ImageCacheKey implements ImageCache.PixelsKey {
+
+        private final int pixelCount;
+        private final int hash;
+
+        private final int w;
+        private final int h;
+        private final Image baseImage;
+
+        ImageCacheKey(final Image baseImage,
+                final int w, final int h) {
+            this.baseImage = baseImage;
+            this.w = w;
+            this.h = h;
+            this.pixelCount = w * h;
+            hash = hash();
+        }
+
+        @Override
+        public int getPixelCount() {
+            return pixelCount;
+        }
+
+        private int hash() {
+            int hash = baseImage.hashCode();
+            hash = 31 * hash + w;
+            hash = 31 * hash + h;
+            return hash;
+        }
+
+        @Override
+        public int hashCode() {
+            return hash;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof ImageCacheKey) {
+                ImageCacheKey key = (ImageCacheKey) obj;
+                return baseImage == key.baseImage && w == key.w && h == key.h;
+            }
+            return false;
+        }
+    }
+}
\ No newline at end of file
--- a/jdk/test/java/awt/image/MultiResolutionImage/NSImageToMultiResolutionImageTest.java	Tue Mar 18 12:30:17 2014 +0400
+++ b/jdk/test/java/awt/image/MultiResolutionImage/NSImageToMultiResolutionImageTest.java	Tue Mar 18 14:48:47 2014 +0400
@@ -27,7 +27,7 @@
 import sun.awt.image.MultiResolutionImage;
 /*
  * @test
- * @bug 8033534
+ * @bug 8033534 8035069
  * @summary [macosx] Get MultiResolution image from native system
  * @author Alexander Scherbatiy
  * @run main NSImageToMultiResolutionImageTest