8035069: [macosx] Loading resolution variants by demand
Reviewed-by: serb, pchelko
--- 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