Initial Contribution
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ninepatch;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.Raster;
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Transparency;
|
||||
import java.net.URL;
|
||||
import java.io.IOException;
|
||||
|
||||
public class GraphicsUtilities {
|
||||
public static BufferedImage loadCompatibleImage(URL resource) throws IOException {
|
||||
BufferedImage image = ImageIO.read(resource);
|
||||
return toCompatibleImage(image);
|
||||
}
|
||||
|
||||
public static BufferedImage createCompatibleImage(int width, int height) {
|
||||
return getGraphicsConfiguration().createCompatibleImage(width, height);
|
||||
}
|
||||
|
||||
public static BufferedImage toCompatibleImage(BufferedImage image) {
|
||||
if (isHeadless()) {
|
||||
return image;
|
||||
}
|
||||
|
||||
if (image.getColorModel().equals(getGraphicsConfiguration().getColorModel())) {
|
||||
return image;
|
||||
}
|
||||
|
||||
BufferedImage compatibleImage = getGraphicsConfiguration().createCompatibleImage(
|
||||
image.getWidth(), image.getHeight(), image.getTransparency());
|
||||
Graphics g = compatibleImage.getGraphics();
|
||||
g.drawImage(image, 0, 0, null);
|
||||
g.dispose();
|
||||
|
||||
return compatibleImage;
|
||||
}
|
||||
|
||||
public static BufferedImage createCompatibleImage(BufferedImage image, int width, int height) {
|
||||
return getGraphicsConfiguration().createCompatibleImage(width, height,
|
||||
image.getTransparency());
|
||||
}
|
||||
|
||||
private static GraphicsConfiguration getGraphicsConfiguration() {
|
||||
GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
return environment.getDefaultScreenDevice().getDefaultConfiguration();
|
||||
}
|
||||
|
||||
private static boolean isHeadless() {
|
||||
return GraphicsEnvironment.isHeadless();
|
||||
}
|
||||
|
||||
public static BufferedImage createTranslucentCompatibleImage(int width, int height) {
|
||||
return getGraphicsConfiguration().createCompatibleImage(width, height,
|
||||
Transparency.TRANSLUCENT);
|
||||
}
|
||||
|
||||
public static int[] getPixels(BufferedImage img, int x, int y, int w, int h, int[] pixels) {
|
||||
if (w == 0 || h == 0) {
|
||||
return new int[0];
|
||||
}
|
||||
|
||||
if (pixels == null) {
|
||||
pixels = new int[w * h];
|
||||
} else if (pixels.length < w * h) {
|
||||
throw new IllegalArgumentException("Pixels array must have a length >= w * h");
|
||||
}
|
||||
|
||||
int imageType = img.getType();
|
||||
if (imageType == BufferedImage.TYPE_INT_ARGB || imageType == BufferedImage.TYPE_INT_RGB) {
|
||||
Raster raster = img.getRaster();
|
||||
return (int[]) raster.getDataElements(x, y, w, h, pixels);
|
||||
}
|
||||
|
||||
// Unmanages the image
|
||||
return img.getRGB(x, y, w, h, pixels, 0, w);
|
||||
}
|
||||
}
|
||||
474
tools/ninepatch/src/com/android/ninepatch/NinePatch.java
Normal file
474
tools/ninepatch/src/com/android/ninepatch/NinePatch.java
Normal file
@@ -0,0 +1,474 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ninepatch;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a 9-Patch bitmap.
|
||||
*/
|
||||
public class NinePatch {
|
||||
public static final String EXTENSION_9PATCH = ".9.png";
|
||||
|
||||
private BufferedImage mImage;
|
||||
|
||||
private int mMinWidth;
|
||||
private int mMinHeight;
|
||||
|
||||
private int[] row;
|
||||
private int[] column;
|
||||
|
||||
private boolean mVerticalStartWithPatch;
|
||||
private boolean mHorizontalStartWithPatch;
|
||||
|
||||
private List<Rectangle> mFixed;
|
||||
private List<Rectangle> mPatches;
|
||||
private List<Rectangle> mHorizontalPatches;
|
||||
private List<Rectangle> mVerticalPatches;
|
||||
|
||||
private Pair<Integer> mHorizontalPadding;
|
||||
private Pair<Integer> mVerticalPadding;
|
||||
|
||||
private float mHorizontalPatchesSum;
|
||||
private float mVerticalPatchesSum;
|
||||
|
||||
private int mRemainderHorizontal;
|
||||
|
||||
private int mRemainderVertical;
|
||||
|
||||
private final URL mFileUrl;
|
||||
|
||||
/**
|
||||
* Loads a 9 patch or regular bitmap.
|
||||
* @param fileUrl the URL of the file to load.
|
||||
* @param convert if <code>true</code>, non 9-patch bitmpa will be converted into a 9 patch.
|
||||
* If <code>false</code> and the bitmap is not a 9 patch, the method will return
|
||||
* <code>null</code>.
|
||||
* @return a {@link NinePatch} or <code>null</code>.
|
||||
* @throws IOException
|
||||
*/
|
||||
public static NinePatch load(URL fileUrl, boolean convert) throws IOException {
|
||||
BufferedImage image = null;
|
||||
try {
|
||||
image = GraphicsUtilities.loadCompatibleImage(fileUrl);
|
||||
} catch (MalformedURLException e) {
|
||||
// really this shouldn't be happening since we're not creating the URL manually.
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean is9Patch = fileUrl.getPath().toLowerCase().endsWith(EXTENSION_9PATCH);
|
||||
|
||||
if (is9Patch == false) {
|
||||
if (convert) {
|
||||
image = convertTo9Patch(image);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
ensure9Patch(image);
|
||||
}
|
||||
|
||||
|
||||
return new NinePatch(fileUrl, image);
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return mImage.getWidth() - 2;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return mImage.getHeight() - 2;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param padding array of left, top, right, bottom padding
|
||||
* @return
|
||||
*/
|
||||
public boolean getPadding(int[] padding) {
|
||||
padding[0] = mHorizontalPadding.mFirst; // left
|
||||
padding[2] = mHorizontalPadding.mSecond; // right
|
||||
padding[1] = mVerticalPadding.mFirst; // top
|
||||
padding[3] = mVerticalPadding.mSecond; // bottom
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void draw(Graphics2D graphics2D, int x, int y, int scaledWidth, int scaledHeight) {
|
||||
if (scaledWidth <= 1 || scaledHeight <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Graphics2D g = (Graphics2D)graphics2D.create();
|
||||
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
|
||||
|
||||
try {
|
||||
if (mPatches.size() == 0 || mHorizontalPatches.size() == 0 ||
|
||||
mVerticalPatches.size() == 0) {
|
||||
g.drawImage(mImage, x, y, scaledWidth, scaledHeight, null);
|
||||
return;
|
||||
}
|
||||
|
||||
g.translate(x, y);
|
||||
x = y = 0;
|
||||
|
||||
computePatches(scaledWidth, scaledHeight);
|
||||
|
||||
int fixedIndex = 0;
|
||||
int horizontalIndex = 0;
|
||||
int verticalIndex = 0;
|
||||
int patchIndex = 0;
|
||||
|
||||
boolean hStretch;
|
||||
boolean vStretch;
|
||||
|
||||
float vWeightSum = 1.0f;
|
||||
float vRemainder = mRemainderVertical;
|
||||
|
||||
vStretch = mVerticalStartWithPatch;
|
||||
while (y < scaledHeight - 1) {
|
||||
hStretch = mHorizontalStartWithPatch;
|
||||
|
||||
int height = 0;
|
||||
float vExtra = 0.0f;
|
||||
|
||||
float hWeightSum = 1.0f;
|
||||
float hRemainder = mRemainderHorizontal;
|
||||
|
||||
while (x < scaledWidth - 1) {
|
||||
Rectangle r;
|
||||
if (!vStretch) {
|
||||
if (hStretch) {
|
||||
r = mHorizontalPatches.get(horizontalIndex++);
|
||||
float extra = r.width / mHorizontalPatchesSum;
|
||||
int width = (int) (extra * hRemainder / hWeightSum);
|
||||
hWeightSum -= extra;
|
||||
hRemainder -= width;
|
||||
g.drawImage(mImage, x, y, x + width, y + r.height, r.x, r.y,
|
||||
r.x + r.width, r.y + r.height, null);
|
||||
x += width;
|
||||
} else {
|
||||
r = mFixed.get(fixedIndex++);
|
||||
g.drawImage(mImage, x, y, x + r.width, y + r.height, r.x, r.y,
|
||||
r.x + r.width, r.y + r.height, null);
|
||||
x += r.width;
|
||||
}
|
||||
height = r.height;
|
||||
} else {
|
||||
if (hStretch) {
|
||||
r = mPatches.get(patchIndex++);
|
||||
vExtra = r.height / mVerticalPatchesSum;
|
||||
height = (int) (vExtra * vRemainder / vWeightSum);
|
||||
float extra = r.width / mHorizontalPatchesSum;
|
||||
int width = (int) (extra * hRemainder / hWeightSum);
|
||||
hWeightSum -= extra;
|
||||
hRemainder -= width;
|
||||
g.drawImage(mImage, x, y, x + width, y + height, r.x, r.y,
|
||||
r.x + r.width, r.y + r.height, null);
|
||||
x += width;
|
||||
} else {
|
||||
r = mVerticalPatches.get(verticalIndex++);
|
||||
vExtra = r.height / mVerticalPatchesSum;
|
||||
height = (int) (vExtra * vRemainder / vWeightSum);
|
||||
g.drawImage(mImage, x, y, x + r.width, y + height, r.x, r.y,
|
||||
r.x + r.width, r.y + r.height, null);
|
||||
x += r.width;
|
||||
}
|
||||
|
||||
}
|
||||
hStretch = !hStretch;
|
||||
}
|
||||
x = 0;
|
||||
y += height;
|
||||
if (vStretch) {
|
||||
vWeightSum -= vExtra;
|
||||
vRemainder -= height;
|
||||
}
|
||||
vStretch = !vStretch;
|
||||
}
|
||||
|
||||
} finally {
|
||||
g.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
void computePatches(int scaledWidth, int scaledHeight) {
|
||||
boolean measuredWidth = false;
|
||||
boolean endRow = true;
|
||||
|
||||
int remainderHorizontal = 0;
|
||||
int remainderVertical = 0;
|
||||
|
||||
if (mFixed.size() > 0) {
|
||||
int start = mFixed.get(0).y;
|
||||
for (Rectangle rect : mFixed) {
|
||||
if (rect.y > start) {
|
||||
endRow = true;
|
||||
measuredWidth = true;
|
||||
}
|
||||
if (!measuredWidth) {
|
||||
remainderHorizontal += rect.width;
|
||||
}
|
||||
if (endRow) {
|
||||
remainderVertical += rect.height;
|
||||
endRow = false;
|
||||
start = rect.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mRemainderHorizontal = scaledWidth - remainderHorizontal;
|
||||
|
||||
mRemainderVertical = scaledHeight - remainderVertical;
|
||||
|
||||
mHorizontalPatchesSum = 0;
|
||||
if (mHorizontalPatches.size() > 0) {
|
||||
int start = -1;
|
||||
for (Rectangle rect : mHorizontalPatches) {
|
||||
if (rect.x > start) {
|
||||
mHorizontalPatchesSum += rect.width;
|
||||
start = rect.x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mVerticalPatchesSum = 0;
|
||||
if (mVerticalPatches.size() > 0) {
|
||||
int start = -1;
|
||||
for (Rectangle rect : mVerticalPatches) {
|
||||
if (rect.y > start) {
|
||||
mVerticalPatchesSum += rect.height;
|
||||
start = rect.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private NinePatch(URL fileUrl, BufferedImage image) {
|
||||
mFileUrl = fileUrl;
|
||||
mImage = image;
|
||||
|
||||
findPatches();
|
||||
}
|
||||
|
||||
private void findPatches() {
|
||||
int width = mImage.getWidth();
|
||||
int height = mImage.getHeight();
|
||||
|
||||
row = GraphicsUtilities.getPixels(mImage, 0, 0, width, 1, row);
|
||||
column = GraphicsUtilities.getPixels(mImage, 0, 0, 1, height, column);
|
||||
|
||||
boolean[] result = new boolean[1];
|
||||
Pair<List<Pair<Integer>>> left = getPatches(column, result);
|
||||
mVerticalStartWithPatch = result[0];
|
||||
|
||||
// compute the min size, based on the list of fixed sections, which is stored in
|
||||
// Pair.mFirst
|
||||
mMinHeight = 0;
|
||||
List<Pair<Integer>> fixedSections = left.mFirst;
|
||||
for (Pair<Integer> section : fixedSections) {
|
||||
mMinHeight += section.mSecond - section.mFirst;
|
||||
}
|
||||
|
||||
result = new boolean[1];
|
||||
Pair<List<Pair<Integer>>> top = getPatches(row, result);
|
||||
mHorizontalStartWithPatch = result[0];
|
||||
|
||||
// compute the min size, based on the list of fixed sections, which is stored in
|
||||
// Pair.mFirst
|
||||
|
||||
mMinWidth = 0;
|
||||
fixedSections = top.mFirst;
|
||||
for (Pair<Integer> section : fixedSections) {
|
||||
mMinWidth += section.mSecond - section.mFirst;
|
||||
}
|
||||
|
||||
mFixed = getRectangles(left.mFirst, top.mFirst);
|
||||
mPatches = getRectangles(left.mSecond, top.mSecond);
|
||||
|
||||
if (mFixed.size() > 0) {
|
||||
mHorizontalPatches = getRectangles(left.mFirst, top.mSecond);
|
||||
mVerticalPatches = getRectangles(left.mSecond, top.mFirst);
|
||||
} else {
|
||||
mHorizontalPatches = mVerticalPatches = new ArrayList<Rectangle>(0);
|
||||
}
|
||||
|
||||
row = GraphicsUtilities.getPixels(mImage, 0, height - 1, width, 1, row);
|
||||
column = GraphicsUtilities.getPixels(mImage, width - 1, 0, 1, height, column);
|
||||
|
||||
top = getPatches(row, result);
|
||||
mHorizontalPadding = getPadding(top.mFirst);
|
||||
|
||||
left = getPatches(column, result);
|
||||
mVerticalPadding = getPadding(left.mFirst);
|
||||
|
||||
mHorizontalPatchesSum = 0;
|
||||
if (mHorizontalPatches.size() > 0) {
|
||||
int start = -1;
|
||||
for (Rectangle rect : mHorizontalPatches) {
|
||||
if (rect.x > start) {
|
||||
mHorizontalPatchesSum += rect.width;
|
||||
start = rect.x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mVerticalPatchesSum = 0;
|
||||
if (mVerticalPatches.size() > 0) {
|
||||
int start = -1;
|
||||
for (Rectangle rect : mVerticalPatches) {
|
||||
if (rect.y > start) {
|
||||
mVerticalPatchesSum += rect.height;
|
||||
start = rect.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Pair<Integer> getPadding(List<Pair<Integer>> pairs) {
|
||||
if (pairs.size() == 0) {
|
||||
return new Pair<Integer>(0, 0);
|
||||
} else if (pairs.size() == 1) {
|
||||
if (pairs.get(0).mFirst == 1) {
|
||||
return new Pair<Integer>(pairs.get(0).mSecond - pairs.get(0).mFirst, 0);
|
||||
} else {
|
||||
return new Pair<Integer>(0, pairs.get(0).mSecond - pairs.get(0).mFirst);
|
||||
}
|
||||
} else {
|
||||
int index = pairs.size() - 1;
|
||||
return new Pair<Integer>(pairs.get(0).mSecond - pairs.get(0).mFirst,
|
||||
pairs.get(index).mSecond - pairs.get(index).mFirst);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Rectangle> getRectangles(List<Pair<Integer>> leftPairs,
|
||||
List<Pair<Integer>> topPairs) {
|
||||
List<Rectangle> rectangles = new ArrayList<Rectangle>();
|
||||
for (Pair<Integer> left : leftPairs) {
|
||||
int y = left.mFirst;
|
||||
int height = left.mSecond - left.mFirst;
|
||||
for (Pair<Integer> top: topPairs) {
|
||||
int x = top.mFirst;
|
||||
int width = top.mSecond - top.mFirst;
|
||||
|
||||
rectangles.add(new Rectangle(x, y, width, height));
|
||||
}
|
||||
}
|
||||
return rectangles;
|
||||
}
|
||||
|
||||
private Pair<List<Pair<Integer>>> getPatches(int[] pixels, boolean[] startWithPatch) {
|
||||
int lastIndex = 1;
|
||||
int lastPixel = pixels[1];
|
||||
boolean first = true;
|
||||
|
||||
List<Pair<Integer>> fixed = new ArrayList<Pair<Integer>>();
|
||||
List<Pair<Integer>> patches = new ArrayList<Pair<Integer>>();
|
||||
|
||||
for (int i = 1; i < pixels.length - 1; i++) {
|
||||
int pixel = pixels[i];
|
||||
if (pixel != lastPixel) {
|
||||
if (lastPixel == 0xFF000000) {
|
||||
if (first) startWithPatch[0] = true;
|
||||
patches.add(new Pair<Integer>(lastIndex, i));
|
||||
} else {
|
||||
fixed.add(new Pair<Integer>(lastIndex, i));
|
||||
}
|
||||
first = false;
|
||||
|
||||
lastIndex = i;
|
||||
lastPixel = pixel;
|
||||
}
|
||||
}
|
||||
if (lastPixel == 0xFF000000) {
|
||||
if (first) startWithPatch[0] = true;
|
||||
patches.add(new Pair<Integer>(lastIndex, pixels.length - 1));
|
||||
} else {
|
||||
fixed.add(new Pair<Integer>(lastIndex, pixels.length - 1));
|
||||
}
|
||||
|
||||
if (patches.size() == 0) {
|
||||
patches.add(new Pair<Integer>(1, pixels.length - 1));
|
||||
startWithPatch[0] = true;
|
||||
fixed.clear();
|
||||
}
|
||||
return new Pair<List<Pair<Integer>>>(fixed, patches);
|
||||
}
|
||||
|
||||
private static void ensure9Patch(BufferedImage image) {
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
for (int i = 0; i < width; i++) {
|
||||
int pixel = image.getRGB(i, 0);
|
||||
if (pixel != 0 && pixel != 0xFF000000) {
|
||||
image.setRGB(i, 0, 0);
|
||||
}
|
||||
pixel = image.getRGB(i, height - 1);
|
||||
if (pixel != 0 && pixel != 0xFF000000) {
|
||||
image.setRGB(i, height - 1, 0);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < height; i++) {
|
||||
int pixel = image.getRGB(0, i);
|
||||
if (pixel != 0 && pixel != 0xFF000000) {
|
||||
image.setRGB(0, i, 0);
|
||||
}
|
||||
pixel = image.getRGB(width - 1, i);
|
||||
if (pixel != 0 && pixel != 0xFF000000) {
|
||||
image.setRGB(width - 1, i, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static BufferedImage convertTo9Patch(BufferedImage image) {
|
||||
BufferedImage buffer = GraphicsUtilities.createTranslucentCompatibleImage(
|
||||
image.getWidth() + 2, image.getHeight() + 2);
|
||||
|
||||
Graphics2D g2 = buffer.createGraphics();
|
||||
g2.drawImage(image, 1, 1, null);
|
||||
g2.dispose();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static class Pair<E> {
|
||||
E mFirst;
|
||||
E mSecond;
|
||||
|
||||
Pair(E first, E second) {
|
||||
mFirst = first;
|
||||
mSecond = second;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Pair[" + mFirst + ", " + mSecond + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user