提交 339f35c4 编写于 作者: A alexsch

8035069: [macosx] Loading resolution variants by demand

Reviewed-by: serb, pchelko
上级 4393ad17
......@@ -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));
}
......@@ -312,10 +310,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 {
......@@ -524,29 +528,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);
}
}
}
}
\ No newline at end of file
......@@ -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() {
......
......@@ -224,24 +224,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[] 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();
BufferedImage baseImage = toImage(w, h, w, h);
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 {
......
/*
* Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
* 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
......@@ -23,25 +23,28 @@
* questions.
*/
package com.apple.laf;
package sun.awt.image;
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;
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.
* 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 class ImageCache {
final public 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);
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;
......@@ -53,15 +56,9 @@ final class ImageCache {
// 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();
public static ImageCache getInstance() {
return AppContext.getSoftReferenceValue(ImageCache.class,
() -> new ImageCache());
}
ImageCache(final int maxPixelCount) {
......@@ -81,186 +78,86 @@ final class ImageCache {
}
}
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;
public Image getImage(final PixelsKey key){
final ImageSoftReference ref;
lock.readLock().lock();
try {
ref = map.get(hash);
ref = map.get(key);
} 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;
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
* @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);
public void setImage(final PixelsKey key, final Image image) {
lock.writeLock().lock();
try {
PixelCountSoftReference ref = map.get(hash);
// check if currently in map
if (ref != null && ref.get() == image) return true;
ImageSoftReference ref = map.get(key);
// clear out old
// check if currently in map
if (ref != null) {
currentPixelCount -= ref.pixelCount;
map.remove(hash);
}
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 = image.getWidth(null) * image.getHeight(null);
final int newPixelCount = key.getPixelCount();
currentPixelCount += newPixelCount;
// clean out lost references if not enough space
if (currentPixelCount > maxPixelCount) {
while ((ref = (PixelCountSoftReference)referenceQueue.poll()) != null) {
while ((ref = (ImageSoftReference)referenceQueue.poll()) != null) {
//reference lost
map.remove(ref.hash);
currentPixelCount -= ref.pixelCount;
map.remove(ref.key);
currentPixelCount -= ref.key.getPixelCount();
}
}
// remove old items till there is enough free space
if (currentPixelCount > maxPixelCount) {
final Iterator<Map.Entry<Integer, PixelCountSoftReference>> mapIter = map.entrySet().iterator();
final Iterator<Map.Entry<PixelsKey, ImageSoftReference>>
mapIter = map.entrySet().iterator();
while ((currentPixelCount > maxPixelCount) && mapIter.hasNext()) {
final Map.Entry<Integer, PixelCountSoftReference> entry = mapIter.next();
final Map.Entry<PixelsKey, ImageSoftReference> entry =
mapIter.next();
mapIter.remove();
final Image img = entry.getValue().get();
if (img != null) img.flush();
currentPixelCount -= entry.getValue().pixelCount;
currentPixelCount -= entry.getValue().key.getPixelCount();
}
}
// finally put new in map
map.put(hash, new PixelCountSoftReference(image, referenceQueue, newPixelCount, hash, config, w, h, scale, state));
return true;
map.put(key, new ImageSoftReference(key, image, referenceQueue));
} 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;
}
public interface PixelsKey {
/**
* Extended SoftReference that stores the pixel count even after the image
* is lost.
*/
private static class PixelCountSoftReference extends SoftReference<Image> {
int getPixelCount();
}
// default access, because access to these fields shouldn't be emulated
// by a synthetic accessor.
final int pixelCount;
final int hash;
private static class ImageSoftReference extends SoftReference<Image> {
// key parts
private final GraphicsConfiguration config;
private final int w;
private final int h;
private final int scale;
private final JRSUIState state;
final PixelsKey key;
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) {
ImageSoftReference(final PixelsKey key, final Image referent,
final ReferenceQueue<? super Image> q) {
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);
this.key = key;
}
}
// /** 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;
// }
}
......@@ -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;
}
}
}
}
\ No newline at end of file
......@@ -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
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册