8035069: [macosx] Loading resolution variants by demand
Reviewed-by: serb, pchelko
This commit is contained in:
parent
eaa221b268
commit
a246384219
@ -295,14 +295,8 @@ public class AquaIcon {
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -125,16 +125,14 @@ public class AquaImageFactory {
|
||||
private static final int kAlertIconSize = 64;
|
||||
static IconUIResource getAppIconCompositedOn(final Image background) {
|
||||
|
||||
final BufferedImage iconImage = getAppIconImageCompositedOn(background, 1);
|
||||
|
||||
if (background instanceof MultiResolutionIconImage) {
|
||||
BufferedImage background2x
|
||||
= ((MultiResolutionIconImage) background).resolutionVariant;
|
||||
BufferedImage icon2xImage = getAppIconImageCompositedOn(background2x, 2);
|
||||
|
||||
return new IconUIResource(new ImageIcon(
|
||||
new MultiResolutionIconImage(iconImage, icon2xImage)));
|
||||
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));
|
||||
}
|
||||
|
||||
BufferedImage iconImage = getAppIconImageCompositedOn(background, 1);
|
||||
return new IconUIResource(new ImageIcon(iconImage));
|
||||
}
|
||||
|
||||
@ -313,10 +311,16 @@ public class AquaImageFactory {
|
||||
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 class AquaImageFactory {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -38,6 +38,7 @@ import sun.java2d.*;
|
||||
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 @@ abstract class AquaPainter <T extends JRSUIState> {
|
||||
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 @@ abstract class AquaPainter <T extends JRSUIState> {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
|
@ -177,16 +177,7 @@ final class AquaUtils {
|
||||
|
||||
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() {
|
||||
|
@ -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;
|
||||
// }
|
||||
}
|
@ -243,24 +243,11 @@ public class CImage extends CFRetainedResource {
|
||||
= nativeGetNSImageRepresentationSizes(ptr,
|
||||
size.getWidth(), size.getHeight());
|
||||
|
||||
if (sizes == null || sizes.length < 2) {
|
||||
return toImage(w, h, w, h);
|
||||
}
|
||||
BufferedImage baseImage = toImage(w, h, w, h);
|
||||
|
||||
BufferedImage[] images = new BufferedImage[sizes.length];
|
||||
int currentImageIndex = 0;
|
||||
|
||||
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) {
|
||||
|
@ -42,11 +42,13 @@ import java.util.Set;
|
||||
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 final class AppContext {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
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 {
|
||||
|
163
jdk/src/share/classes/sun/awt/image/ImageCache.java
Normal file
163
jdk/src/share/classes/sun/awt/image/ImageCache.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -26,46 +26,152 @@ package sun.awt.image;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ import sun.awt.OSInfo;
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user