| /* | |
| * Copyright (c) 2004, 2023, 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.swing; | |
| import sun.awt.image.SurfaceManager; | |
| import sun.java2d.SurfaceData; | |
| import java.awt.Component; | |
| import java.awt.Graphics; | |
| import java.awt.Graphics2D; | |
| import java.awt.GraphicsConfiguration; | |
| import java.awt.Image; | |
| import java.awt.geom.AffineTransform; | |
| import java.awt.image.AbstractMultiResolutionImage; | |
| import java.awt.image.BufferedImage; | |
| import java.awt.image.ImageObserver; | |
| import java.awt.image.VolatileImage; | |
| import java.util.Arrays; | |
| import java.util.HashMap; | |
| import java.util.Map; | |
| /** | |
| * A base class used for icons or images that are expensive to paint. | |
| * A subclass will do the following: | |
| * <ol> | |
| * <li>Invoke <code>paint</code> when you want to paint the image, | |
| * if you are implementing <code>Icon</code> you'll invoke this from | |
| * <code>paintIcon</code>. | |
| * The args argument is useful when additional state is needed. | |
| * <li>Override <code>paintToImage</code> to render the image. The code that | |
| * lives here is equivalent to what previously would go in | |
| * <code>paintIcon</code>, for an <code>Icon</code>. | |
| * </ol> | |
| * The two ways to use this class are: | |
| * <ol> | |
| * <li>Invoke <code>paint</code> to draw the cached representation at | |
| * the specified location. | |
| * <li>Invoke <code>getImage</code> to get the cached representation and | |
| * draw the image yourself. This is primarily useful when you are not | |
| * using <code>VolatileImage</code>. | |
| * </ol> | |
| * | |
| * | |
| */ | |
| public abstract class CachedPainter { | |
| // CacheMap maps from class to ImageCache. | |
| private static final Map<Object,ImageCache> cacheMap = new HashMap<>(); | |
| private static ImageCache getCache(Object key) { | |
| synchronized(CachedPainter.class) { | |
| ImageCache cache = cacheMap.get(key); | |
| if (cache == null) { | |
| if (key == PainterMultiResolutionCachedImage.class) { | |
| cache = new ImageCache(32); | |
| } else { | |
| cache = new ImageCache(1); | |
| } | |
| cacheMap.put(key, cache); | |
| } | |
| return cache; | |
| } | |
| } | |
| /** | |
| * Creates an instance of <code>CachedPainter</code> that will cache up | |
| * to <code>cacheCount</code> images of this class. | |
| * | |
| * @param cacheCount Max number of images to cache | |
| */ | |
| public CachedPainter(int cacheCount) { | |
| getCache(getClass()).setMaxCount(cacheCount); | |
| } | |
| /** | |
| * Renders the cached image to the passed in <code>Graphic</code>. | |
| * If there is no cached image <code>paintToImage</code> will be invoked. | |
| * <code>paintImage</code> is invoked to paint the cached image. | |
| * | |
| * @param c Component rendering to, this may be null. | |
| * @param g Graphics to paint to | |
| * @param x X-coordinate to render to | |
| * @param y Y-coordinate to render to | |
| * @param w Width to render in | |
| * @param h Height to render in | |
| * @param args Variable arguments that will be passed to paintToImage | |
| */ | |
| public void paint(Component c, Graphics g, int x, | |
| int y, int w, int h, Object... args) { | |
| if (w <= 0 || h <= 0) { | |
| return; | |
| } | |
| synchronized (CachedPainter.class) { | |
| paint0(c, g, x, y, w, h, args); | |
| } | |
| } | |
| private Image getImage(Object key, Component c, | |
| int baseWidth, int baseHeight, | |
| int w, int h, Object... args) { | |
| GraphicsConfiguration config = getGraphicsConfiguration(c); | |
| ImageCache cache = getCache(key); | |
| Image image = cache.getImage(key, config, w, h, args); | |
| int attempts = 0; | |
| VolatileImage volatileImage = (image instanceof VolatileImage) | |
| ? (VolatileImage) image | |
| : null; | |
| do { | |
| boolean draw = false; | |
| if (volatileImage != null) { | |
| // See if we need to recreate the image | |
| switch (volatileImage.validate(config)) { | |
| case VolatileImage.IMAGE_INCOMPATIBLE: | |
| volatileImage.flush(); | |
| image = null; | |
| break; | |
| case VolatileImage.IMAGE_RESTORED: | |
| draw = true; | |
| break; | |
| } | |
| } | |
| if (image == null) { | |
| // Recreate the image | |
| if( config != null && (w != baseHeight || h != baseWidth)) { | |
| AffineTransform tx = config.getDefaultTransform(); | |
| double sx = tx.getScaleX(); | |
| double sy = tx.getScaleY(); | |
| if ( Double.compare(sx, 1) != 0 || | |
| Double.compare(sy, 1) != 0) { | |
| if (Math.abs(sx * baseWidth - w) < 1 && | |
| Math.abs(sy * baseHeight - h) < 1) { | |
| w = baseWidth; | |
| h = baseHeight; | |
| } else { | |
| w = (int)Math.ceil(w / sx); | |
| h = (int)Math.ceil(w / sy); | |
| } | |
| } | |
| } | |
| image = createImage(c, w, h, config, args); | |
| cache.setImage(key, config, w, h, args, image); | |
| draw = true; | |
| volatileImage = (image instanceof VolatileImage) | |
| ? (VolatileImage) image | |
| : null; | |
| } | |
| if (draw) { | |
| // Render to the Image | |
| Graphics2D g2 = (Graphics2D) image.getGraphics(); | |
| if (volatileImage == null) { | |
| if ((w != baseWidth || h != baseHeight)) { | |
| g2.scale((double) w / baseWidth, | |
| (double) h / baseHeight); | |
| } | |
| paintToImage(c, image, g2, baseWidth, baseHeight, args); | |
| } else { | |
| SurfaceData sd = SurfaceManager.getManager(volatileImage) | |
| .getPrimarySurfaceData(); | |
| double sx = sd.getDefaultScaleX(); | |
| double sy = sd.getDefaultScaleY(); | |
| if ( Double.compare(sx, 1) != 0 || | |
| Double.compare(sy, 1) != 0) { | |
| g2.scale(1 / sx, 1 / sy); | |
| } | |
| paintToImage(c, image, g2, (int)Math.ceil(w * sx), | |
| (int)Math.ceil(h * sy), args); | |
| } | |
| g2.dispose(); | |
| } | |
| // If we did this 3 times and the contents are still lost | |
| // assume we're painting to a VolatileImage that is bogus and | |
| // give up. Presumably we'll be called again to paint. | |
| } while ((volatileImage != null) && | |
| volatileImage.contentsLost() && ++attempts < 3); | |
| return image; | |
| } | |
| private void paint0(Component c, Graphics g, int x, | |
| int y, int w, int h, Object... args) { | |
| Object key = getClass(); | |
| GraphicsConfiguration config = getGraphicsConfiguration(c); | |
| ImageCache cache = getCache(key); | |
| Image image = cache.getImage(key, config, w, h, args); | |
| if (image == null) { | |
| image = new PainterMultiResolutionCachedImage(w, h); | |
| cache.setImage(key, config, w, h, args, image); | |
| } | |
| if (image instanceof PainterMultiResolutionCachedImage) { | |
| ((PainterMultiResolutionCachedImage) image).setParams(c, args); | |
| } | |
| // Render to the passed in Graphics | |
| paintImage(c, g, x, y, w, h, image, args); | |
| } | |
| /** | |
| * Paints the representation to cache to the supplied Graphics. | |
| * | |
| * @param c Component painting to, may be null. | |
| * @param image Image to paint to | |
| * @param g Graphics to paint to, obtained from the passed in Image. | |
| * @param w Width to paint to | |
| * @param h Height to paint to | |
| * @param args Arguments supplied to <code>paint</code> | |
| */ | |
| protected abstract void paintToImage(Component c, Image image, Graphics g, | |
| int w, int h, Object[] args); | |
| /** | |
| * Paints the image to the specified location. | |
| * | |
| * @param c Component painting to | |
| * @param g Graphics to paint to | |
| * @param x X coordinate to paint to | |
| * @param y Y coordinate to paint to | |
| * @param w Width to paint to | |
| * @param h Height to paint to | |
| * @param image Image to paint | |
| * @param args Arguments supplied to <code>paint</code> | |
| */ | |
| protected void paintImage(Component c, Graphics g, | |
| int x, int y, int w, int h, Image image, | |
| Object[] args) { | |
| g.drawImage(image, x, y, null); | |
| } | |
| /** | |
| * Creates the image to cache. This returns an opaque image, subclasses | |
| * that require translucency or transparency will need to override this | |
| * method. | |
| * | |
| * @param c Component painting to | |
| * @param w Width of image to create | |
| * @param h Height to image to create | |
| * @param config GraphicsConfiguration that will be | |
| * rendered to, this may be null. | |
| * @param args Arguments passed to paint | |
| */ | |
| protected Image createImage(Component c, int w, int h, | |
| GraphicsConfiguration config, Object[] args) { | |
| if (config == null) { | |
| return new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); | |
| } | |
| return config.createCompatibleVolatileImage(w, h); | |
| } | |
| /** | |
| * Clear the image cache | |
| */ | |
| protected void flush() { | |
| synchronized(CachedPainter.class) { | |
| getCache(getClass()).flush(); | |
| } | |
| } | |
| private GraphicsConfiguration getGraphicsConfiguration(Component c) { | |
| if (c == null) { | |
| return null; | |
| } | |
| return c.getGraphicsConfiguration(); | |
| } | |
| class PainterMultiResolutionCachedImage extends AbstractMultiResolutionImage { | |
| private final int baseWidth; | |
| private final int baseHeight; | |
| private Component c; | |
| private Object[] args; | |
| public PainterMultiResolutionCachedImage(int baseWidth, int baseHeight) { | |
| this.baseWidth = baseWidth; | |
| this.baseHeight = baseHeight; | |
| } | |
| public void setParams(Component c, Object[] args) { | |
| this.c = c; | |
| this.args = args; | |
| } | |
| @Override | |
| public int getWidth(ImageObserver observer) { | |
| return baseWidth; | |
| } | |
| @Override | |
| public int getHeight(ImageObserver observer) { | |
| return baseHeight; | |
| } | |
| @Override | |
| public Image getResolutionVariant(double destWidth, double destHeight) { | |
| int w = (int) Math.floor(destWidth + 0.5); | |
| int h = (int) Math.floor(destHeight + 0.5); | |
| return getImage(PainterMultiResolutionCachedImage.class, | |
| c, baseWidth, baseHeight, w, h, args); | |
| } | |
| @Override | |
| protected Image getBaseImage() { | |
| return getResolutionVariant(baseWidth, baseHeight); | |
| } | |
| @Override | |
| public java.util.List<Image> getResolutionVariants() { | |
| return Arrays.asList(getResolutionVariant(baseWidth, baseHeight)); | |
| } | |
| } | |
| } |