NinePatches to draw auto-stretchy borders
Google's Android has handy-dandy NinePatches for making a graphic that stretches appropriately to border things, so here's my AWT re-implementation of it. git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@882 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library 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
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.image;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* The NinePatch class permits drawing a bitmap in nine sections. The four corners are unscaled;
|
||||
* the four edges are scaled in one axis, and the middle is scaled in both axes. Normally, the
|
||||
* middle is transparent so that the patch can provide a selection about a rectangle. Essentially,
|
||||
* it allows the creation of custom graphics that will scale the way that you define, when content
|
||||
* added within the image exceeds the normal bounds of the graphic.
|
||||
*
|
||||
* This is an AWT clone of the NinePatch functionality that exists in Google's Android graphic
|
||||
* system. See http://developer.android.com/guide/topics/graphics/2d-graphics.html#nine-patch
|
||||
*/
|
||||
public class NinePatch
|
||||
{
|
||||
/**
|
||||
* Builds a NinePatch based on the given image.
|
||||
*
|
||||
* Figures out the NinePatch bounds by parsing the image: Opaque pixels in the top row
|
||||
* and left column show the stretchable center. Opaque pixels in the bottom row and right
|
||||
* column show the content area.
|
||||
*/
|
||||
public NinePatch (BufferedImage img)
|
||||
{
|
||||
this(img.getSubimage(1, 1, img.getWidth() - 2, img.getHeight() - 2),
|
||||
getRectangle(img, true), getRectangle(img, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a NinePatch based on the given image.
|
||||
*
|
||||
* @param center specifies the stretchable center of the image.
|
||||
* @param content if non-null, specifies the usable content area of the image
|
||||
*/
|
||||
public NinePatch (BufferedImage img, Rectangle center, Rectangle content)
|
||||
{
|
||||
_img = img;
|
||||
|
||||
_left = center.x;
|
||||
_right = _img.getWidth() - (center.x + center.width);
|
||||
_top = center.y;
|
||||
_bottom = _img.getHeight() - (center.y + center.height);
|
||||
|
||||
if (content != null) {
|
||||
_leftPad = content.x;
|
||||
_rightPad = _img.getWidth() - (content.x + content.width);
|
||||
_topPad = content.y;
|
||||
_bottomPad = _img.getHeight() - (content.y + content.height);
|
||||
} else {
|
||||
_leftPad = _rightPad = _topPad = _bottomPad = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void paint (Graphics2D gfx, Rectangle location)
|
||||
{
|
||||
int sx, sy, sw, sh;
|
||||
int dx, dy, dw, dh;
|
||||
|
||||
int width = _img.getWidth();
|
||||
int height = _img.getHeight();
|
||||
|
||||
// Top left
|
||||
sw = _left;
|
||||
sh = _top;
|
||||
sx = 0;
|
||||
sy = 0;
|
||||
|
||||
dw = sw;
|
||||
dh = sh;
|
||||
dx = location.x;
|
||||
dy = location.y;
|
||||
|
||||
gfx.drawImage(_img, dx, dy, dx + dw, dy + dh, sx, sy, sx + sw, sy + sh, null);
|
||||
|
||||
// Top right
|
||||
sw = _right;
|
||||
sh = _top;
|
||||
sx = width - sw;
|
||||
sy = 0;
|
||||
|
||||
dw = sw;
|
||||
dh = sh;
|
||||
dx = location.x + location.width - dw;
|
||||
dy = location.y;
|
||||
|
||||
gfx.drawImage(_img, dx, dy, dx + dw, dy + dh, sx, sy, sx + sw, sy + sh, null);
|
||||
|
||||
// Bottom left
|
||||
sw = _left;
|
||||
sh = _bottom;
|
||||
sx = 0;
|
||||
sy = height - sh;
|
||||
|
||||
dw = sw;
|
||||
dh = sh;
|
||||
dx = location.x;
|
||||
dy = location.y + location.height - dh;
|
||||
|
||||
gfx.drawImage(_img, dx, dy, dx + dw, dy + dh, sx, sy, sx + sw, sy + sh, null);
|
||||
|
||||
// Bottom right
|
||||
sw = _right;
|
||||
sh = _bottom;
|
||||
sx = width - sw;
|
||||
sy = height - sh;
|
||||
|
||||
dw = sw;
|
||||
dh = sh;
|
||||
dx = location.x + location.width - dw;
|
||||
dy = location.y + location.height - dh;
|
||||
|
||||
gfx.drawImage(_img, dx, dy, dx + dw, dy + dh, sx, sy, sx + sw, sy + sh, null);
|
||||
|
||||
// Top center
|
||||
sw = width - _left - _right;
|
||||
sh = _top;
|
||||
sx = _left;
|
||||
sy = 0;
|
||||
|
||||
dw = location.width - _left - _right;
|
||||
dh = sh;
|
||||
dx = location.x + _left;
|
||||
dy = location.y;
|
||||
|
||||
gfx.drawImage(_img, dx, dy, dx + dw, dy + dh, sx, sy, sx + sw, sy + sh, null);
|
||||
|
||||
// Bottom center
|
||||
sw = width - _left - _right;
|
||||
sh = _bottom;
|
||||
sx = _left;
|
||||
sy = height - _bottom;
|
||||
|
||||
dw = location.width - _left - _right;
|
||||
dh = sh;
|
||||
dx = location.x + _left;
|
||||
dy = location.y + location.height - dh;
|
||||
|
||||
gfx.drawImage(_img, dx, dy, dx + dw, dy + dh, sx, sy, sx + sw, sy + sh, null);
|
||||
|
||||
// Left center
|
||||
sw = _left;
|
||||
sh = height - _top - _bottom;
|
||||
sx = 0;
|
||||
sy = _top;
|
||||
|
||||
dw = sw;
|
||||
dh = location.height - _top - _bottom;
|
||||
dx = location.x;
|
||||
dy = location.y + _top;
|
||||
|
||||
gfx.drawImage(_img, dx, dy, dx + dw, dy + dh, sx, sy, sx + sw, sy + sh, null);
|
||||
|
||||
// Right center
|
||||
sw = _right;
|
||||
sh = height - _top - _bottom;
|
||||
sx = width - _right;
|
||||
sy = _top;
|
||||
|
||||
dw = sw;
|
||||
dh = location.height - _top - _bottom;
|
||||
dx = location.x + location.width - _right;
|
||||
dy = location.y + _top;
|
||||
|
||||
gfx.drawImage(_img, dx, dy, dx + dw, dy + dh, sx, sy, sx + sw, sy + sh, null);
|
||||
|
||||
// Dead center
|
||||
sw = width - _left - _right;
|
||||
sh = height - _top - _bottom;
|
||||
sx = _left;
|
||||
sy = _top;
|
||||
|
||||
dw = location.width - _left - _right;
|
||||
dh = location.height - _top - _bottom;
|
||||
dx = location.x + _left;
|
||||
dy = location.y + _top;
|
||||
|
||||
gfx.drawImage(_img, dx, dy, dx + dw, dy + dh, sx, sy, sx + sw, sy + sh, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a rectangle describing the bounds of this NinePatch when drawn such that it frames
|
||||
* the given content rectangle.
|
||||
*/
|
||||
public Rectangle getBoundsSurrounding (Rectangle content)
|
||||
{
|
||||
Rectangle bounds = new Rectangle(content);
|
||||
|
||||
bounds.width += _leftPad + _rightPad;
|
||||
bounds.height += _topPad + _bottomPad;
|
||||
bounds.x -= _leftPad;
|
||||
bounds.y -= _topPad;
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the image to find the bounds of the rectangle defined by pixels on the outside.
|
||||
*/
|
||||
protected static Rectangle getRectangle (BufferedImage img, boolean stretch)
|
||||
{
|
||||
Rectangle rect = new Rectangle(0, 0, img.getWidth() - 2, img.getHeight() - 2);
|
||||
|
||||
for (int xx = 1; xx < rect.width + 1; xx++) {
|
||||
if (ImageUtil.hitTest(img, xx, stretch ? 0 : img.getHeight() - 1)) {
|
||||
rect.x = xx - 1;
|
||||
rect.width -= rect.x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int xx = img.getWidth() - 1; xx >= rect.x + 1; xx--) {
|
||||
if (ImageUtil.hitTest(img, xx, stretch ? 0 : img.getHeight() - 1)) {
|
||||
rect.width = xx - rect.x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int yy = 1; yy < rect.height + 1; yy++) {
|
||||
if (ImageUtil.hitTest(img, stretch ? 0 : img.getWidth() - 1, yy)) {
|
||||
rect.y = yy - 1;
|
||||
rect.height -= rect.y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int yy = img.getHeight() - 1; yy >= rect.y + 1; yy--) {
|
||||
if (ImageUtil.hitTest(img, stretch ? 0 : img.getWidth() - 1, yy)) {
|
||||
rect.height = yy - rect.y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
final protected BufferedImage _img;
|
||||
|
||||
/** The size of the non-stretchable regions on each side. */
|
||||
final protected int _top;
|
||||
final protected int _bottom;
|
||||
final protected int _left;
|
||||
final protected int _right;
|
||||
|
||||
/** The amount of padding on each side until we're in the content area. */
|
||||
final protected int _topPad;
|
||||
final protected int _bottomPad;
|
||||
final protected int _leftPad;
|
||||
final protected int _rightPad;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// $Id$
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/nenya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library 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
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.media.image;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* Wraps a {@link NinePatch} as a {@link Mirage} of specific dimensions.
|
||||
*/
|
||||
public class NinePatchMirage
|
||||
implements Mirage
|
||||
{
|
||||
public NinePatchMirage (BufferedImage img, int width, int height)
|
||||
{
|
||||
this(new NinePatch(img), width, height);
|
||||
}
|
||||
|
||||
public NinePatchMirage (NinePatch ninePatch, int width, int height)
|
||||
{
|
||||
if (width < 0 || height < 0) {
|
||||
throw new IllegalArgumentException("Illegal dimensions");
|
||||
}
|
||||
|
||||
_ninePatch = ninePatch;
|
||||
_width = width;
|
||||
_height = height;
|
||||
}
|
||||
|
||||
public long getEstimatedMemoryUsage ()
|
||||
{
|
||||
return ImageUtil.getEstimatedMemoryUsage(_ninePatch._img.getRaster());
|
||||
}
|
||||
|
||||
public BufferedImage getSnapshot ()
|
||||
{
|
||||
BufferedImage img = new BufferedImage(_width, _height, BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
_ninePatch.paint(img.createGraphics(), new Rectangle(0, 0, _width, _height));
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
public int getWidth ()
|
||||
{
|
||||
return _width;
|
||||
}
|
||||
|
||||
public int getHeight ()
|
||||
{
|
||||
return _height;
|
||||
}
|
||||
|
||||
public boolean hitTest (int x, int y)
|
||||
{
|
||||
// This is stupidly expensive.
|
||||
return ImageUtil.hitTest(getSnapshot(), x, y);
|
||||
}
|
||||
|
||||
public void paint (Graphics2D gfx, int x, int y)
|
||||
{
|
||||
_ninePatch.paint(gfx, new Rectangle(x, y, _width, _height));
|
||||
}
|
||||
|
||||
protected final NinePatch _ninePatch;
|
||||
protected final int _width;
|
||||
protected final int _height;
|
||||
}
|
||||
Reference in New Issue
Block a user