diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:29:09 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:29:09 -0800 |
commit | 55a2c71f27d3e0b8344597c7f281e687cb7aeb1b (patch) | |
tree | ecd18b995aea8eeeb8b3823266280d41245bf0f7 /ninepatch | |
parent | 82ea7a177797b844b252effea5c7c7c5d63ea4ac (diff) | |
download | sdk-55a2c71f27d3e0b8344597c7f281e687cb7aeb1b.zip sdk-55a2c71f27d3e0b8344597c7f281e687cb7aeb1b.tar.gz sdk-55a2c71f27d3e0b8344597c7f281e687cb7aeb1b.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'ninepatch')
-rw-r--r-- | ninepatch/Android.mk | 23 | ||||
-rw-r--r-- | ninepatch/src/com/android/ninepatch/GraphicsUtilities.java | 96 | ||||
-rw-r--r-- | ninepatch/src/com/android/ninepatch/NinePatch.java | 474 |
3 files changed, 593 insertions, 0 deletions
diff --git a/ninepatch/Android.mk b/ninepatch/Android.mk new file mode 100644 index 0000000..42e0205 --- /dev/null +++ b/ninepatch/Android.mk @@ -0,0 +1,23 @@ +# +# 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. +# +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under,src) + +LOCAL_MODULE := ninepatch + +include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/ninepatch/src/com/android/ninepatch/GraphicsUtilities.java b/ninepatch/src/com/android/ninepatch/GraphicsUtilities.java new file mode 100644 index 0000000..7a823ec --- /dev/null +++ b/ninepatch/src/com/android/ninepatch/GraphicsUtilities.java @@ -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); + } +} diff --git a/ninepatch/src/com/android/ninepatch/NinePatch.java b/ninepatch/src/com/android/ninepatch/NinePatch.java new file mode 100644 index 0000000..39e05c6 --- /dev/null +++ b/ninepatch/src/com/android/ninepatch/NinePatch.java @@ -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 + "]"; + } + } +} |