/* * Copyright 2006-2008 Sun Microsystems, Inc. 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. * * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package com.sun.javafx.scene.paint; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Insets; import java.awt.Paint; import java.awt.PaintContext; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.Raster; import java.awt.image.WritableRaster; /** * SplitImagePaint - A awt Paint implementation that uses a Image which can be split, tiled or stretched to fit the area * of the shape being painted. * * @author Jasper Potts */ public class SplitImagePaint implements Paint { /** Enumeration for the types of painting this class can handle. */ public enum PaintType { /** Painting type indicating the image should be centered in the space provided. */ CENTER, /** Painting type indicating the image should be stretched to fill the specified width and height. */ STRETCH, // todo /** Painting type indicating the image should be tiled across the specified width and height. */ TILE, /** * Painting type indicating the image should be split into nine regions with the top, left, bottom and right * areas stretched. */ PAINT9_STRETCH, /** * Painting type indicating the image should be split into nine regions with the top, left, bottom and right * areas tiled. */ PAINT9_TILE } /** * A data class for sepecifying the size of splits for the PAINT9 modes, they can be either a int in pixels or a * double in percentage. */ public static class Size { private int pxSize = 0; private double percentageSize = 0; private final boolean isPercentage; public Size(double percentageSize) { this.percentageSize = percentageSize; isPercentage = true; } public Size(int pxSize) { this.pxSize = pxSize; isPercentage = false; } private int getSize(int widthOrHeight){ if (isPercentage){ return (int) Math.round(widthOrHeight * percentageSize); } else { return pxSize; } } } private static final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0); public BufferedImage img; private PaintType type; private Insets sInsets; private Insets dInsets; private boolean inverse; /** * Create SplitImagePaint * * @param img Image to render from, if null nothing will be painted * @param type Specifies what type of algorithm to use in painting * @param sInsets Insets specifying the portion of the image that will be stretched or tiled, if null * empty Insets will be used. * @param dInsets Destination insets specifying the portion of the image will be stretched or tiled, if * null empty Insets will be used. */ public SplitImagePaint(BufferedImage img, PaintType type, Insets sInsets, Insets dInsets) { this.img = img; this.type = type; this.sInsets = sInsets; this.dInsets = dInsets; } /** * Create SplitImagePaint * * @param img Image to render from, if null nothing will be painted * @param type Specifies what type of algorithm to use in painting * @param inverse This only has effect when using this with paint types of PAINT9_STRETCH or PAINT9_TILE. When * true the center region will be a fixed size in the source and destination. Therefore * the outer regions will resize. When false the split lines will be a fixed distance * from the edges as specified by top, left, bottom and right both in the source image and the * destination area. Therefore the center of the 9 split will be the part resizing. * @param top The distance from the edge of the top horizontal split line. This is ignored unless the * type is PAINT9_STRETCH or PAINT9_TILE * @param left The distance from the edge of the left vertical split line. This is ignored unless the * type is PAINT9_STRETCH or PAINT9_TILE * @param bottom The distance from the edge of the bottom horizontal split line. This is ignored unless the * type is PAINT9_STRETCH or PAINT9_TILE * @param right The distance from the edge of the right vertical split line. This is ignored unless the * type is PAINT9_STRETCH or PAINT9_TILE */ public SplitImagePaint(BufferedImage img, PaintType type, boolean inverse, Size top, Size left, Size bottom, Size right) { this.img = img; this.type = type; this.inverse = inverse; // convert input to sInsets and dInsets int w = img.getWidth(); int h = img.getHeight(); int t = (top != null)? top.getSize(h) : 0; int l = (left != null)? left.getSize(w) : 0; int b = (bottom != null)? bottom.getSize(h) : 0; int r = (right != null)? right.getSize(w) : 0; sInsets = dInsets = new Insets(t,l,b,r); } public PaintContext createContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) { // claculate dInsets if inverse = true if (inverse){ int centerWidth = img.getWidth() - sInsets.left - sInsets.right; int centerHeight = img.getHeight() - sInsets.top - sInsets.bottom; int leftRight = (int)( (userBounds.getWidth() - centerWidth)/2 ); int topBottom = (int)( (userBounds.getHeight() - centerHeight)/2 ); dInsets = new Insets(topBottom, leftRight, topBottom, leftRight); } return new SplitImagePaintContext(cm, deviceBounds, userBounds, xform, hints); } public int getTransparency() { return OPAQUE; } private class SplitImagePaintContext implements PaintContext { private Rectangle deviceBounds; private ColorModel cm; private Raster transformedRaster; public SplitImagePaintContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) { this.deviceBounds = deviceBounds; // Render the transformed image BufferedImage transformed; if (cm != null) { transformed = new BufferedImage(cm, WritableRaster.createWritableRaster( cm.createCompatibleSampleModel(deviceBounds.width, deviceBounds.height), new Point(0, 0)), false, null); } else { transformed = new BufferedImage( deviceBounds.width, deviceBounds.height, BufferedImage.TYPE_INT_RGB); } this.cm = transformed.getColorModel(); Graphics2D g2 = transformed.createGraphics(); g2.setRenderingHints(hints); g2.setColor(Color.PINK); g2.fillRect(0, 0, deviceBounds.width, deviceBounds.height); g2.translate(-deviceBounds.getX(), -deviceBounds.getY()); g2.transform(xform); paint9(g2, (int) userBounds.getX(), (int) userBounds.getY(), (int) userBounds.getWidth(), (int) userBounds.getHeight()); g2.dispose(); transformedRaster = transformed.getRaster(); } public void dispose() { transformedRaster = null; } public ColorModel getColorModel() { return cm; } public Raster getRaster(int x, int y, int w, int h) { return transformedRaster.createChild(x - deviceBounds.x, y - deviceBounds.y, w, h, 0, 0, null); } protected void paint9(Graphics g, int x, int y, int w, int h) { if (sInsets == null) { sInsets = EMPTY_INSETS; } if (dInsets == null) { dInsets = EMPTY_INSETS; } int iw = img.getWidth(null); int ih = img.getHeight(null); if (type == PaintType.CENTER) { // Center the image g.drawImage(img, x + (w - iw) / 2, y + (h - ih) / 2, null); } else if (type == PaintType.TILE) { // Tile the image int lastIY = 0; for (int yCounter = y, maxY = y + h; yCounter < maxY; yCounter += (ih - lastIY), lastIY = 0) { int lastIX = 0; for (int xCounter = x, maxX = x + w; xCounter < maxX; xCounter += (iw - lastIX), lastIX = 0) { int dx2 = Math.min(maxX, xCounter + iw - lastIX); int dy2 = Math.min(maxY, yCounter + ih - lastIY); g.drawImage(img, xCounter, yCounter, dx2, dy2, lastIX, lastIY, lastIX + dx2 - xCounter, lastIY + dy2 - yCounter, null); } } } else { int st = sInsets.top; int sl = sInsets.left; int sb = sInsets.bottom; int sr = sInsets.right; int dt = dInsets.top; int dl = dInsets.left; int db = dInsets.bottom; int dr = dInsets.right; // Constrain the insets to the size of the image if (st + sb > ih) { db = dt = sb = st = Math.max(0, ih / 2); } if (sl + sr > iw) { dl = dr = sl = sr = Math.max(0, iw / 2); } // Constrain the insets to the size of the region we're painting // in. if (dt + db > h) { dt = db = Math.max(0, h / 2 - 1); } if (dl + dr > w) { dl = dr = Math.max(0, w / 2 - 1); } boolean stretch = type == PaintType.PAINT9_STRETCH; if (type != PaintType.STRETCH){ // left drawChunk(img, g, stretch, x, y + dt, x + dl, y + h - db, 0, st, sl, ih - sb, false); // top left drawImage(img, g, x, y, x + dl, y + dt, 0, 0, sl, st); // top drawChunk(img, g, stretch, x + dl, y, x + w - dr, y + dt, sl, 0, iw - sr, st, true); // top right drawImage(img, g, x + w - dr, y, x + w, y + dt, iw - sr, 0, iw, st); // right drawChunk(img, g, stretch, x + w - dr, y + dt, x + w, y + h - db, iw - sr, st, iw, ih - sb, false); // bottom right drawImage(img, g, x + w - dr, y + h - db, x + w, y + h, iw - sr, ih - sb, iw, ih); // bottom drawChunk(img, g, stretch, x + dl, y + h - db, x + w - dr, y + h, sl, ih - sb, iw - sr, ih, true); // bottom left drawImage(img, g, x, y + h - db, x + dl, y + h, 0, ih - sb, sl, ih); } // center drawImage(img, g, x + dl, y + dt, x + w - dr, y + h - db, sl, st, iw - sr, ih - sb); } } private void drawImage(Image image, Graphics g, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2) { // PENDING: is this necessary, will G2D do it for me? if (dx2 - dx1 <= 0 || dy2 - dy1 <= 0 || sx2 - sx1 <= 0 || sy2 - sy1 <= 0) { // Bogus location, nothing to paint return; } g.drawImage(image, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null); } /** * Draws a portion of an image, stretched or tiled. * * @param image Image to render. * @param g Graphics to render to * @param stretch Whether the image should be stretched or timed in the provided space. * @param dx1 X origin to draw to * @param dy1 Y origin to draw to * @param dx2 End x location to draw to * @param dy2 End y location to draw to * @param sx1 X origin to draw from * @param sy1 Y origin to draw from * @param sx2 Max x location to draw from * @param sy2 Max y location to draw from * @param xDirection Used if the image is not stretched. If true it indicates the image should be tiled along the x * axis. */ private void drawChunk(Image image, Graphics g, boolean stretch, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, boolean xDirection) { if (dx2 - dx1 <= 0 || dy2 - dy1 <= 0 || sx2 - sx1 <= 0 || sy2 - sy1 <= 0) { // Bogus location, nothing to paint return; } if (stretch) { g.drawImage(image, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null); } else { int xSize = sx2 - sx1; int ySize = sy2 - sy1; int deltaX; int deltaY; if (xDirection) { deltaX = xSize; deltaY = 0; } else { deltaX = 0; deltaY = ySize; } while (dx1 < dx2 && dy1 < dy2) { int newDX2 = Math.min(dx2, dx1 + xSize); int newDY2 = Math.min(dy2, dy1 + ySize); g.drawImage(image, dx1, dy1, newDX2, newDY2, sx1, sy1, sx1 + newDX2 - dx1, sy1 + newDY2 - dy1, null); dx1 += deltaX; dy1 += deltaY; } } } } }