/*
* 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;
}
}
}
}
}