aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSiva Velusamy <vsiva@google.com>2013-02-17 22:25:38 -0800
committerSiva Velusamy <vsiva@google.com>2013-02-27 09:45:24 -0800
commit374ab2667fec32acc9588b8fd8c9fe5b0e6bce5d (patch)
treec0efece9c3d12d77f6589aa64b92ee220011c1ce
parent3bfe4491ec0e4f7ae8435d43d1b7556af7166b04 (diff)
downloadsdk-374ab2667fec32acc9588b8fd8c9fe5b0e6bce5d.zip
sdk-374ab2667fec32acc9588b8fd8c9fe5b0e6bce5d.tar.gz
sdk-374ab2667fec32acc9588b8fd8c9fe5b0e6bce5d.tar.bz2
Move components to separate classes.
Also collects all 9 patch related information into PatchInfo, add adds some basic tests for PatchInfo. Change-Id: If5d8acbb169416290a2a0298526889b8f7960e32
-rw-r--r--draw9patch/src/main/java/com/android/draw9patch/ui/CorruptPatch.java100
-rw-r--r--draw9patch/src/main/java/com/android/draw9patch/ui/ImageEditorPanel.java1046
-rw-r--r--draw9patch/src/main/java/com/android/draw9patch/ui/ImageViewer.java572
-rw-r--r--draw9patch/src/main/java/com/android/draw9patch/ui/Pair.java32
-rw-r--r--draw9patch/src/main/java/com/android/draw9patch/ui/PatchInfo.java204
-rw-r--r--draw9patch/src/main/java/com/android/draw9patch/ui/StretchesViewer.java326
-rw-r--r--draw9patch/src/test/java/com/android/draw9patch/ui/PatchInfoTest.java104
7 files changed, 1375 insertions, 1009 deletions
diff --git a/draw9patch/src/main/java/com/android/draw9patch/ui/CorruptPatch.java b/draw9patch/src/main/java/com/android/draw9patch/ui/CorruptPatch.java
new file mode 100644
index 0000000..4f0763a
--- /dev/null
+++ b/draw9patch/src/main/java/com/android/draw9patch/ui/CorruptPatch.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2013 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.draw9patch.ui;
+
+import com.android.draw9patch.graphics.GraphicsUtilities;
+
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class CorruptPatch {
+ public static List<Rectangle> findBadPatches(BufferedImage image, PatchInfo patchInfo) {
+ List<Rectangle> corruptedPatches = new ArrayList<Rectangle>();
+
+ for (Rectangle patch : patchInfo.patches) {
+ if (corruptPatch(image, patch)) {
+ corruptedPatches.add(patch);
+ }
+ }
+
+ for (Rectangle patch : patchInfo.horizontalPatches) {
+ if (corruptHorizontalPatch(image, patch)) {
+ corruptedPatches.add(patch);
+ }
+ }
+
+ for (Rectangle patch : patchInfo.verticalPatches) {
+ if (corruptVerticalPatch(image, patch)) {
+ corruptedPatches.add(patch);
+ }
+ }
+
+ return corruptedPatches;
+ }
+
+ private static boolean corruptPatch(BufferedImage image, Rectangle patch) {
+ int[] pixels = GraphicsUtilities.getPixels(image, patch.x, patch.y,
+ patch.width, patch.height, null);
+
+ if (pixels.length > 0) {
+ int reference = pixels[0];
+ for (int pixel : pixels) {
+ if (pixel != reference) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean corruptHorizontalPatch(BufferedImage image, Rectangle patch) {
+ int[] reference = new int[patch.height];
+ int[] column = new int[patch.height];
+ reference = GraphicsUtilities.getPixels(image, patch.x, patch.y,
+ 1, patch.height, reference);
+
+ for (int i = 1; i < patch.width; i++) {
+ column = GraphicsUtilities.getPixels(image, patch.x + i, patch.y,
+ 1, patch.height, column);
+ if (!Arrays.equals(reference, column)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean corruptVerticalPatch(BufferedImage image, Rectangle patch) {
+ int[] reference = new int[patch.width];
+ int[] row = new int[patch.width];
+ reference = GraphicsUtilities.getPixels(image, patch.x, patch.y,
+ patch.width, 1, reference);
+
+ for (int i = 1; i < patch.height; i++) {
+ row = GraphicsUtilities.getPixels(image, patch.x, patch.y + i, patch.width, 1, row);
+ if (!Arrays.equals(reference, row)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/draw9patch/src/main/java/com/android/draw9patch/ui/ImageEditorPanel.java b/draw9patch/src/main/java/com/android/draw9patch/ui/ImageEditorPanel.java
index 845ee54..c707c38 100644
--- a/draw9patch/src/main/java/com/android/draw9patch/ui/ImageEditorPanel.java
+++ b/draw9patch/src/main/java/com/android/draw9patch/ui/ImageEditorPanel.java
@@ -18,66 +18,36 @@ package com.android.draw9patch.ui;
import com.android.draw9patch.graphics.GraphicsUtilities;
-import javax.swing.JPanel;
-import javax.swing.JLabel;
-import javax.swing.BorderFactory;
-import javax.swing.JSlider;
-import javax.swing.JComponent;
-import javax.swing.JScrollPane;
-import javax.swing.JCheckBox;
-import javax.swing.Box;
-import javax.swing.JFileChooser;
-import javax.swing.JSplitPane;
-import javax.swing.JButton;
-import javax.swing.border.EmptyBorder;
-import javax.swing.event.AncestorEvent;
-import javax.swing.event.AncestorListener;
-import javax.swing.event.ChangeListener;
-import javax.swing.event.ChangeEvent;
-import java.awt.image.BufferedImage;
-import java.awt.image.RenderedImage;
-import java.awt.Graphics2D;
import java.awt.BorderLayout;
import java.awt.Color;
-import java.awt.Graphics;
-import java.awt.Dimension;
-import java.awt.TexturePaint;
-import java.awt.Shape;
-import java.awt.BasicStroke;
-import java.awt.RenderingHints;
-import java.awt.Rectangle;
-import java.awt.GridBagLayout;
+import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
import java.awt.Insets;
-import java.awt.Toolkit;
-import java.awt.AWTEvent;
-import java.awt.event.MouseMotionAdapter;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseAdapter;
-import java.awt.event.ActionListener;
+import java.awt.TexturePaint;
import java.awt.event.ActionEvent;
-import java.awt.event.KeyEvent;
-import java.awt.event.AWTEventListener;
+import java.awt.event.ActionListener;
import java.awt.geom.Rectangle2D;
-import java.awt.geom.Line2D;
-import java.awt.geom.Area;
-import java.awt.geom.RoundRectangle2D;
-import java.io.IOException;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
import java.io.File;
+import java.io.IOException;
import java.net.URL;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Arrays;
+
+import javax.swing.Box;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSlider;
+import javax.swing.JSplitPane;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
class ImageEditorPanel extends JPanel {
private static final String EXTENSION_9PATCH = ".9.png";
- private static final int DEFAULT_ZOOM = 8;
- private static final float DEFAULT_SCALE = 2.0f;
-
- // For stretch regions and padding
- private static final int BLACK_TICK = 0xFF000000;
- // For Layout Bounds
- private static final int RED_TICK = 0xFFFF0000;
private String name;
private BufferedImage image;
@@ -90,30 +60,20 @@ class ImageEditorPanel extends JPanel {
private TexturePaint texture;
- private List<Rectangle> patches;
- private List<Rectangle> horizontalPatches;
- private List<Rectangle> verticalPatches;
- private List<Rectangle> fixed;
- private boolean verticalStartWithPatch;
- private boolean horizontalStartWithPatch;
-
- private Pair<Integer> horizontalPadding;
- private Pair<Integer> verticalPadding;
-
ImageEditorPanel(MainFrame mainFrame, BufferedImage image, String name) {
this.image = image;
this.name = name;
setTransferHandler(new ImageTransferHandler(mainFrame));
- checkImage();
-
setOpaque(false);
setLayout(new BorderLayout());
loadSupport();
buildImageViewer();
buildStatusPanel();
+
+ checkImage();
}
private void loadSupport() {
@@ -128,7 +88,13 @@ class ImageEditorPanel extends JPanel {
}
private void buildImageViewer() {
- viewer = new ImageViewer();
+ viewer = new ImageViewer(this, texture, image, new ImageViewer.StatusBar() {
+ @Override
+ public void setPointerLocation(int x, int y) {
+ xLabel.setText(x + " px");
+ yLabel.setText(y + " px");
+ }
+ });
JSplitPane splitter = new JSplitPane();
splitter.setContinuousLayout(true);
@@ -148,7 +114,7 @@ class ImageEditorPanel extends JPanel {
}
private JComponent buildStretchesViewer() {
- stretchesViewer = new StretchesViewer();
+ stretchesViewer = new StretchesViewer(this, viewer, texture);
JScrollPane scroller = new JScrollPane(stretchesViewer);
scroller.setBorder(null);
scroller.getViewport().setBorder(null);
@@ -177,7 +143,8 @@ class ImageEditorPanel extends JPanel {
GridBagConstraints.LINE_END, GridBagConstraints.NONE,
new Insets(0, 0, 0, 0), 0, 0));
- JSlider zoomSlider = new JSlider(1, 16, DEFAULT_ZOOM);
+ JSlider zoomSlider = new JSlider(ImageViewer.MIN_ZOOM, ImageViewer.MAX_ZOOM,
+ ImageViewer.DEFAULT_ZOOM);
zoomSlider.setSnapToTicks(true);
zoomSlider.putClientProperty("JComponent.sizeVariant", "small");
zoomSlider.addChangeListener(new ChangeListener() {
@@ -213,7 +180,7 @@ class ImageEditorPanel extends JPanel {
GridBagConstraints.LINE_END, GridBagConstraints.NONE,
new Insets(0, 0, 0, 0), 0, 0));
- zoomSlider = new JSlider(200, 600, (int) (DEFAULT_SCALE * 100.0f));
+ zoomSlider = new JSlider(200, 600, (int) (StretchesViewer.DEFAULT_SCALE * 100.0f));
zoomSlider.setSnapToTicks(true);
zoomSlider.putClientProperty("JComponent.sizeVariant", "small");
zoomSlider.addChangeListener(new ChangeListener() {
@@ -322,21 +289,21 @@ class ImageEditorPanel extends JPanel {
int height = image.getHeight();
for (int i = 0; i < width; i++) {
int pixel = image.getRGB(i, 0);
- if (pixel != 0 && pixel != BLACK_TICK && pixel != RED_TICK) {
+ if (pixel != 0 && pixel != PatchInfo.BLACK_TICK && pixel != PatchInfo.RED_TICK) {
image.setRGB(i, 0, 0);
}
pixel = image.getRGB(i, height - 1);
- if (pixel != 0 && pixel != BLACK_TICK && pixel != RED_TICK) {
+ if (pixel != 0 && pixel != PatchInfo.BLACK_TICK && pixel != PatchInfo.RED_TICK) {
image.setRGB(i, height - 1, 0);
}
}
for (int i = 0; i < height; i++) {
int pixel = image.getRGB(0, i);
- if (pixel != 0 && pixel != BLACK_TICK && pixel != RED_TICK) {
+ if (pixel != 0 && pixel != PatchInfo.BLACK_TICK && pixel != PatchInfo.RED_TICK) {
image.setRGB(0, i, 0);
}
pixel = image.getRGB(width - 1, i);
- if (pixel != 0 && pixel != BLACK_TICK && pixel != RED_TICK) {
+ if (pixel != 0 && pixel != PatchInfo.BLACK_TICK && pixel != PatchInfo.RED_TICK) {
image.setRGB(width - 1, i, 0);
}
}
@@ -351,6 +318,7 @@ class ImageEditorPanel extends JPanel {
g2.dispose();
image = buffer;
+ viewer.setImage(image);
name = name.substring(0, name.lastIndexOf('.')) + ".9.png";
}
@@ -385,944 +353,4 @@ class ImageEditorPanel extends JPanel {
RenderedImage getImage() {
return image;
}
-
- private class StretchesViewer extends JPanel {
- private static final int MARGIN = 24;
-
- private StretchView horizontal;
- private StretchView vertical;
- private StretchView both;
-
- private Dimension size;
-
- private float horizontalPatchesSum;
- private float verticalPatchesSum;
-
- private boolean showPadding;
-
- StretchesViewer() {
- setOpaque(false);
- setLayout(new GridBagLayout());
- setBorder(BorderFactory.createEmptyBorder(MARGIN, MARGIN, MARGIN, MARGIN));
-
- horizontal = new StretchView();
- vertical = new StretchView();
- both = new StretchView();
-
- setScale(DEFAULT_SCALE);
-
- add(vertical, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
- GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
- add(horizontal, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
- GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
- add(both, new GridBagConstraints(0, 2, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
- GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
- }
-
- @Override
- protected void paintComponent(Graphics g) {
- Graphics2D g2 = (Graphics2D) g.create();
- g2.setPaint(texture);
- g2.fillRect(0, 0, getWidth(), getHeight());
- g2.dispose();
- }
-
- void setScale(float scale) {
- int patchWidth = image.getWidth() - 2;
- int patchHeight = image.getHeight() - 2;
-
- int scaledWidth = (int) (patchWidth * scale);
- int scaledHeight = (int) (patchHeight * scale);
-
- horizontal.scaledWidth = scaledWidth;
- vertical.scaledHeight = scaledHeight;
- both.scaledWidth = scaledWidth;
- both.scaledHeight = scaledHeight;
-
- size = new Dimension(scaledWidth, scaledHeight);
-
- computePatches();
- }
-
- void computePatches() {
- boolean measuredWidth = false;
- boolean endRow = true;
-
- int remainderHorizontal = 0;
- int remainderVertical = 0;
-
- if (fixed.size() > 0) {
- int start = fixed.get(0).y;
- for (Rectangle rect : fixed) {
- if (rect.y > start) {
- endRow = true;
- measuredWidth = true;
- }
- if (!measuredWidth) {
- remainderHorizontal += rect.width;
- }
- if (endRow) {
- remainderVertical += rect.height;
- endRow = false;
- start = rect.y;
- }
- }
- }
-
- horizontal.remainderHorizontal = horizontal.scaledWidth - remainderHorizontal;
- vertical.remainderHorizontal = vertical.scaledWidth - remainderHorizontal;
- both.remainderHorizontal = both.scaledWidth - remainderHorizontal;
-
- horizontal.remainderVertical = horizontal.scaledHeight - remainderVertical;
- vertical.remainderVertical = vertical.scaledHeight - remainderVertical;
- both.remainderVertical = both.scaledHeight - remainderVertical;
-
- horizontalPatchesSum = 0;
- if (horizontalPatches.size() > 0) {
- int start = -1;
- for (Rectangle rect : horizontalPatches) {
- if (rect.x > start) {
- horizontalPatchesSum += rect.width;
- start = rect.x;
- }
- }
- } else {
- int start = -1;
- for (Rectangle rect : patches) {
- if (rect.x > start) {
- horizontalPatchesSum += rect.width;
- start = rect.x;
- }
- }
- }
-
- verticalPatchesSum = 0;
- if (verticalPatches.size() > 0) {
- int start = -1;
- for (Rectangle rect : verticalPatches) {
- if (rect.y > start) {
- verticalPatchesSum += rect.height;
- start = rect.y;
- }
- }
- } else {
- int start = -1;
- for (Rectangle rect : patches) {
- if (rect.y > start) {
- verticalPatchesSum += rect.height;
- start = rect.y;
- }
- }
- }
-
- setSize(size);
- ImageEditorPanel.this.validate();
- repaint();
- }
-
- void setPaddingVisible(boolean visible) {
- showPadding = visible;
- repaint();
- }
-
- private class StretchView extends JComponent {
- private final Color PADDING_COLOR = new Color(0.37f, 0.37f, 1.0f, 0.5f);
-
- int scaledWidth;
- int scaledHeight;
-
- int remainderHorizontal;
- int remainderVertical;
-
- StretchView() {
- scaledWidth = image.getWidth();
- scaledHeight = image.getHeight();
- }
-
- @Override
- protected void paintComponent(Graphics g) {
- int x = (getWidth() - scaledWidth) / 2;
- int y = (getHeight() - scaledHeight) / 2;
-
- Graphics2D g2 = (Graphics2D) g.create();
- g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
- RenderingHints.VALUE_INTERPOLATION_BILINEAR);
- g.translate(x, y);
-
- x = 0;
- y = 0;
-
- if (patches.size() == 0) {
- g.drawImage(image, 0, 0, scaledWidth, scaledHeight, null);
- g2.dispose();
- return;
- }
-
- int fixedIndex = 0;
- int horizontalIndex = 0;
- int verticalIndex = 0;
- int patchIndex = 0;
-
- boolean hStretch;
- boolean vStretch;
-
- float vWeightSum = 1.0f;
- float vRemainder = remainderVertical;
-
- vStretch = verticalStartWithPatch;
- while (y < scaledHeight - 1) {
- hStretch = horizontalStartWithPatch;
-
- int height = 0;
- float vExtra = 0.0f;
-
- float hWeightSum = 1.0f;
- float hRemainder = remainderHorizontal;
-
- while (x < scaledWidth - 1) {
- Rectangle r;
- if (!vStretch) {
- if (hStretch) {
- r = horizontalPatches.get(horizontalIndex++);
- float extra = r.width / horizontalPatchesSum;
- int width = (int) (extra * hRemainder / hWeightSum);
- hWeightSum -= extra;
- hRemainder -= width;
- g.drawImage(image, x, y, x + width, y + r.height, r.x, r.y,
- r.x + r.width, r.y + r.height, null);
- x += width;
- } else {
- r = fixed.get(fixedIndex++);
- g.drawImage(image, 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 = patches.get(patchIndex++);
- vExtra = r.height / verticalPatchesSum;
- height = (int) (vExtra * vRemainder / vWeightSum);
- float extra = r.width / horizontalPatchesSum;
- int width = (int) (extra * hRemainder / hWeightSum);
- hWeightSum -= extra;
- hRemainder -= width;
- g.drawImage(image, x, y, x + width, y + height, r.x, r.y,
- r.x + r.width, r.y + r.height, null);
- x += width;
- } else {
- r = verticalPatches.get(verticalIndex++);
- vExtra = r.height / verticalPatchesSum;
- height = (int) (vExtra * vRemainder / vWeightSum);
- g.drawImage(image, 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;
- }
-
- if (showPadding) {
- g.setColor(PADDING_COLOR);
- g.fillRect(horizontalPadding.first, verticalPadding.first,
- scaledWidth - horizontalPadding.first - horizontalPadding.second,
- scaledHeight - verticalPadding.first - verticalPadding.second);
- }
-
- g2.dispose();
- }
-
- @Override
- public Dimension getPreferredSize() {
- return size;
- }
- }
- }
-
- private class ImageViewer extends JComponent {
- private final Color CORRUPTED_COLOR = new Color(1.0f, 0.0f, 0.0f, 0.7f);
- private final Color LOCK_COLOR = new Color(0.0f, 0.0f, 0.0f, 0.7f);
- private final Color STRIPES_COLOR = new Color(1.0f, 0.0f, 0.0f, 0.5f);
- private final Color BACK_COLOR = new Color(0xc0c0c0);
- private final Color HELP_COLOR = new Color(0xffffe1);
- private final Color PATCH_COLOR = new Color(1.0f, 0.37f, 0.99f, 0.5f);
- private final Color PATCH_ONEWAY_COLOR = new Color(0.37f, 1.0f, 0.37f, 0.5f);
-
- private static final float STRIPES_WIDTH = 4.0f;
- private static final double STRIPES_SPACING = 6.0;
- private static final int STRIPES_ANGLE = 45;
-
- private int zoom = DEFAULT_ZOOM;
- private boolean showPatches;
- private boolean showLock = true;
-
- private final Dimension size;
-
- private boolean locked;
-
- private int[] row;
- private int[] column;
-
- private int lastPositionX;
- private int lastPositionY;
- private int currentButton;
- private boolean showCursor;
-
- private JLabel helpLabel;
- private boolean eraseMode;
-
- private JButton checkButton;
- private List<Rectangle> corruptedPatches;
- private boolean showBadPatches;
-
- private JPanel helpPanel;
- private boolean drawingLine;
- private int lineFromX;
- private int lineFromY;
- private int lineToX;
- private int lineToY;
- private boolean showDrawingLine;
-
- ImageViewer() {
- setLayout(new GridBagLayout());
- helpPanel = new JPanel(new BorderLayout());
- helpPanel.setBorder(new EmptyBorder(0, 6, 0, 6));
- helpPanel.setBackground(HELP_COLOR);
- helpLabel = new JLabel("Press Shift to erase pixels."
- + " Press Control to draw layout bounds");
- helpLabel.putClientProperty("JComponent.sizeVariant", "small");
- helpPanel.add(helpLabel, BorderLayout.WEST);
- checkButton = new JButton("Show bad patches");
- checkButton.putClientProperty("JComponent.sizeVariant", "small");
- checkButton.putClientProperty("JButton.buttonType", "roundRect");
- helpPanel.add(checkButton, BorderLayout.EAST);
-
- add(helpPanel, new GridBagConstraints(0, 0, 1, 1,
- 1.0f, 1.0f, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL,
- new Insets(0, 0, 0, 0), 0, 0));
-
- setOpaque(true);
-
- // Exact size will be set by setZoom() in AncestorListener#ancestorMoved.
- size = new Dimension(0, 0);
-
- addAncestorListener(new AncestorListener() {
- @Override
- public void ancestorRemoved(AncestorEvent event) {
- }
- @Override
- public void ancestorMoved(AncestorEvent event) {
- // Set exactly size.
- viewer.setZoom(DEFAULT_ZOOM);
- viewer.removeAncestorListener(this);
- }
- @Override
- public void ancestorAdded(AncestorEvent event) {
- }
- });
-
- findPatches();
-
- addMouseListener(new MouseAdapter() {
- @Override
- public void mousePressed(MouseEvent event) {
- // Store the button here instead of retrieving it again in MouseDragged
- // below, because on linux, calling MouseEvent.getButton() for the drag
- // event returns 0, which appears to be technically correct (no button
- // changed state).
- currentButton = event.isShiftDown() ? MouseEvent.BUTTON3 : event.getButton();
- currentButton = event.isControlDown() ? MouseEvent.BUTTON2 : currentButton;
- startDrawingLine(event.getX(), event.getY(), currentButton);
- }
-
- @Override
- public void mouseReleased(MouseEvent event) {
- endDrawingLine();
- }
- });
- addMouseMotionListener(new MouseMotionAdapter() {
- @Override
- public void mouseDragged(MouseEvent event) {
- if (!checkLockedRegion(event.getX(), event.getY())) {
- // use the stored button, see note above
-
- moveLine(event.getX(), event.getY());
- }
- }
-
- @Override
- public void mouseMoved(MouseEvent event) {
- checkLockedRegion(event.getX(), event.getY());
- }
- });
- Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
- public void eventDispatched(AWTEvent event) {
- enableEraseMode((KeyEvent) event);
- }
- }, AWTEvent.KEY_EVENT_MASK);
-
- checkButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent event) {
- if (!showBadPatches) {
- findBadPatches();
- checkButton.setText("Hide bad patches");
- } else {
- checkButton.setText("Show bad patches");
- corruptedPatches = null;
- }
- repaint();
- showBadPatches = !showBadPatches;
- }
- });
- }
-
- private void findBadPatches() {
- corruptedPatches = new ArrayList<Rectangle>();
-
- for (Rectangle patch : patches) {
- if (corruptPatch(patch)) {
- corruptedPatches.add(patch);
- }
- }
-
- for (Rectangle patch : horizontalPatches) {
- if (corruptHorizontalPatch(patch)) {
- corruptedPatches.add(patch);
- }
- }
-
- for (Rectangle patch : verticalPatches) {
- if (corruptVerticalPatch(patch)) {
- corruptedPatches.add(patch);
- }
- }
- }
-
- private boolean corruptPatch(Rectangle patch) {
- int[] pixels = GraphicsUtilities.getPixels(image, patch.x, patch.y,
- patch.width, patch.height, null);
-
- if (pixels.length > 0) {
- int reference = pixels[0];
- for (int pixel : pixels) {
- if (pixel != reference) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- private boolean corruptHorizontalPatch(Rectangle patch) {
- int[] reference = new int[patch.height];
- int[] column = new int[patch.height];
- reference = GraphicsUtilities.getPixels(image, patch.x, patch.y,
- 1, patch.height, reference);
-
- for (int i = 1; i < patch.width; i++) {
- column = GraphicsUtilities.getPixels(image, patch.x + i, patch.y,
- 1, patch.height, column);
- if (!Arrays.equals(reference, column)) {
- return true;
- }
- }
-
- return false;
- }
-
- private boolean corruptVerticalPatch(Rectangle patch) {
- int[] reference = new int[patch.width];
- int[] row = new int[patch.width];
- reference = GraphicsUtilities.getPixels(image, patch.x, patch.y,
- patch.width, 1, reference);
-
- for (int i = 1; i < patch.height; i++) {
- row = GraphicsUtilities.getPixels(image, patch.x, patch.y + i, patch.width, 1, row);
- if (!Arrays.equals(reference, row)) {
- return true;
- }
- }
-
- return false;
- }
-
- private void enableEraseMode(KeyEvent event) {
- boolean oldEraseMode = eraseMode;
- eraseMode = event.isShiftDown();
- if (eraseMode != oldEraseMode) {
- if (eraseMode) {
- helpLabel.setText("Release Shift to draw pixels");
- } else {
- helpLabel.setText("Press Shift to erase pixels."
- + " Press Control to draw layout bounds");
- }
- }
- }
-
- private void startDrawingLine(int x, int y, int button) {
- int left = (getWidth() - size.width) / 2;
- int top = helpPanel.getHeight() + (getHeight() - size.height) / 2;
-
- x = (x - left) / zoom;
- y = (y - top) / zoom;
-
- int width = image.getWidth();
- int height = image.getHeight();
- if (((x == 0 || x == width - 1) && (y > 0 && y < height - 1))
- || ((x > 0 && x < width - 1) && (y == 0 || y == height - 1))) {
- drawingLine = true;
- lineFromX = x;
- lineFromY = y;
- lineToX = x;
- lineToY = y;
-
- showDrawingLine = true;
-
- showCursor = false;
-
- repaint();
- }
- }
-
- private void moveLine(int x, int y) {
- if (drawingLine == false)
- return;
-
- int left = (getWidth() - size.width) / 2;
- int top = helpPanel.getHeight() + (getHeight() - size.height) / 2;
-
- x = (x - left) / zoom;
- y = (y - top) / zoom;
-
- int width = image.getWidth();
- int height = image.getHeight();
-
- showDrawingLine = false;
-
- if (((x == lineFromX) && (y > 0 && y < height - 1))
- || ((x > 0 && x < width - 1) && (y == lineFromY))) {
- if (x == lineFromX || y == lineFromY) {
- lineToX = x;
- lineToY = y;
-
- showDrawingLine = true;
- }
- }
-
- repaint();
- }
-
- private void endDrawingLine() {
- if (drawingLine == false)
- return;
-
- drawingLine = false;
-
- if (showDrawingLine == false)
- return;
-
- int color;
- switch (currentButton) {
- case MouseEvent.BUTTON1:
- color = BLACK_TICK;
- break;
- case MouseEvent.BUTTON2:
- color = RED_TICK;
- break;
- case MouseEvent.BUTTON3:
- color = 0;
- break;
- default:
- return;
- }
-
- int x = lineFromX;
- int y = lineFromY;
-
- int dx = 0;
- int dy = 0;
-
- if (lineToX != lineFromX)
- dx = lineToX > lineFromX ? 1 : -1;
- else if (lineToY != lineFromY)
- dy = lineToY > lineFromY ? 1 : -1;
-
- do {
- image.setRGB(x, y, color);
-
- if (x == lineToX && y == lineToY)
- break;
-
- x += dx;
- y += dy;
- } while (true);
-
- findPatches();
- stretchesViewer.computePatches();
- if (showBadPatches) {
- findBadPatches();
- }
-
- repaint();
- }
-
- private boolean checkLockedRegion(int x, int y) {
- int oldX = lastPositionX;
- int oldY = lastPositionY;
- lastPositionX = x;
- lastPositionY = y;
-
- int left = (getWidth() - size.width) / 2;
- int top = helpPanel.getHeight() + (getHeight() - size.height) / 2;
-
- x = (x - left) / zoom;
- y = (y - top) / zoom;
-
- int width = image.getWidth();
- int height = image.getHeight();
-
- xLabel.setText(Math.max(0, Math.min(x, width - 1)) + " px");
- yLabel.setText(Math.max(0, Math.min(y, height - 1)) + " px");
-
- boolean previousLock = locked;
- locked = x > 0 && x < width - 1 && y > 0 && y < height - 1;
-
- boolean previousCursor = showCursor;
- showCursor =
- !drawingLine &&
- ( ((x == 0 || x == width - 1) && (y > 0 && y < height - 1)) ||
- ((x > 0 && x < width - 1) && (y == 0 || y == height - 1)) );
-
- if (locked != previousLock) {
- repaint();
- } else if (showCursor || (showCursor != previousCursor)) {
- Rectangle clip = new Rectangle(lastPositionX - 1 - zoom / 2,
- lastPositionY - 1 - zoom / 2, zoom + 2, zoom + 2);
- clip = clip.union(new Rectangle(oldX - 1 - zoom / 2,
- oldY - 1 - zoom / 2, zoom + 2, zoom + 2));
- repaint(clip);
- }
-
- return locked;
- }
-
- @Override
- protected void paintComponent(Graphics g) {
- int x = (getWidth() - size.width) / 2;
- int y = helpPanel.getHeight() + (getHeight() - size.height) / 2;
-
- Graphics2D g2 = (Graphics2D) g.create();
- g2.setColor(BACK_COLOR);
- g2.fillRect(0, 0, getWidth(), getHeight());
-
- g2.translate(x, y);
- g2.setPaint(texture);
- g2.fillRect(0, 0, size.width, size.height);
- g2.scale(zoom, zoom);
- g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
- RenderingHints.VALUE_ANTIALIAS_ON);
- g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
- RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
- g2.drawImage(image, 0, 0, null);
-
- if (showPatches) {
- g2.setColor(PATCH_COLOR);
- for (Rectangle patch : patches) {
- g2.fillRect(patch.x, patch.y, patch.width, patch.height);
- }
- g2.setColor(PATCH_ONEWAY_COLOR);
- for (Rectangle patch : horizontalPatches) {
- g2.fillRect(patch.x, patch.y, patch.width, patch.height);
- }
- for (Rectangle patch : verticalPatches) {
- g2.fillRect(patch.x, patch.y, patch.width, patch.height);
- }
- }
-
- if (corruptedPatches != null) {
- g2.setColor(CORRUPTED_COLOR);
- g2.setStroke(new BasicStroke(3.0f / zoom));
- for (Rectangle patch : corruptedPatches) {
- g2.draw(new RoundRectangle2D.Float(patch.x - 2.0f / zoom, patch.y - 2.0f / zoom,
- patch.width + 2.0f / zoom, patch.height + 2.0f / zoom,
- 6.0f / zoom, 6.0f / zoom));
- }
- }
-
- if (showLock && locked) {
- int width = image.getWidth();
- int height = image.getHeight();
-
- g2.setColor(LOCK_COLOR);
- g2.fillRect(1, 1, width - 2, height - 2);
-
- g2.setColor(STRIPES_COLOR);
- g2.translate(1, 1);
- paintStripes(g2, width - 2, height - 2);
- g2.translate(-1, -1);
- }
-
- g2.dispose();
-
- if (drawingLine && showDrawingLine) {
- Graphics cursor = g.create();
- cursor.setXORMode(Color.WHITE);
- cursor.setColor(Color.BLACK);
-
- x = Math.min(lineFromX, lineToX);
- y = Math.min(lineFromY, lineToY);
- int w = Math.abs(lineFromX - lineToX) + 1;
- int h = Math.abs(lineFromY - lineToY) + 1;
-
- x = x * zoom;
- y = y * zoom;
- w = w * zoom;
- h = h * zoom;
-
- int left = (getWidth() - size.width) / 2;
- int top = helpPanel.getHeight() + (getHeight() - size.height)
- / 2;
-
- x += left;
- y += top;
-
- cursor.drawRect(x, y, w, h);
- cursor.dispose();
- }
-
- if (showCursor) {
- Graphics cursor = g.create();
- cursor.setXORMode(Color.WHITE);
- cursor.setColor(Color.BLACK);
- cursor.drawRect(lastPositionX - zoom / 2, lastPositionY - zoom / 2, zoom, zoom);
- cursor.dispose();
- }
- }
-
- private void paintStripes(Graphics2D g, int width, int height) {
- //draws pinstripes at the angle specified in this class
- //and at the given distance apart
- Shape oldClip = g.getClip();
- Area area = new Area(new Rectangle(0, 0, width, height));
- if(oldClip != null) {
- area = new Area(oldClip);
- }
- area.intersect(new Area(new Rectangle(0,0,width,height)));
- g.setClip(area);
-
- g.setStroke(new BasicStroke(STRIPES_WIDTH));
-
- double hypLength = Math.sqrt((width * width) +
- (height * height));
-
- double radians = Math.toRadians(STRIPES_ANGLE);
- g.rotate(radians);
-
- double spacing = STRIPES_SPACING;
- spacing += STRIPES_WIDTH;
- int numLines = (int)(hypLength / spacing);
-
- for (int i=0; i<numLines; i++) {
- double x = i * spacing;
- Line2D line = new Line2D.Double(x, -hypLength, x, hypLength);
- g.draw(line);
- }
- g.setClip(oldClip);
- }
-
- @Override
- public Dimension getPreferredSize() {
- return size;
- }
-
- void setZoom(int value) {
- int width = image.getWidth();
- int height = image.getHeight();
-
- zoom = value;
- if (size.height == 0 || (getHeight() - size.height) == 0) {
- size.setSize(width * zoom, height * zoom + helpPanel.getHeight());
- } else {
- size.setSize(width * zoom, height * zoom);
- }
-
- if (!size.equals(getSize())) {
- setSize(size);
- ImageEditorPanel.this.validate();
- repaint();
- }
- }
-
- void setPatchesVisible(boolean visible) {
- showPatches = visible;
- findPatches();
- repaint();
- }
-
- private void findPatches() {
- int width = image.getWidth();
- int height = image.getHeight();
-
- row = GraphicsUtilities.getPixels(image, 0, 0, width, 1, row);
- column = GraphicsUtilities.getPixels(image, 0, 0, 1, height, column);
-
- boolean[] result = new boolean[1];
- Pair<List<Pair<Integer>>> left = getPatches(column, result);
- verticalStartWithPatch = result[0];
-
- result = new boolean[1];
- Pair<List<Pair<Integer>>> top = getPatches(row, result);
- horizontalStartWithPatch = result[0];
-
- fixed = getRectangles(left.first, top.first);
- patches = getRectangles(left.second, top.second);
-
- if (fixed.size() > 0) {
- horizontalPatches = getRectangles(left.first, top.second);
- verticalPatches = getRectangles(left.second, top.first);
- } else {
- if (top.first.size() > 0) {
- horizontalPatches = new ArrayList<Rectangle>(0);
- verticalPatches = getVerticalRectangles(top.first);
- } else if (left.first.size() > 0) {
- horizontalPatches = getHorizontalRectangles(left.first);
- verticalPatches = new ArrayList<Rectangle>(0);
- } else {
- horizontalPatches = verticalPatches = new ArrayList<Rectangle>(0);
- }
- }
-
- row = GraphicsUtilities.getPixels(image, 0, height - 1, width, 1, row);
- column = GraphicsUtilities.getPixels(image, width - 1, 0, 1, height, column);
-
- top = getPatches(row, result);
- horizontalPadding = getPadding(top.first);
-
- left = getPatches(column, result);
- verticalPadding = getPadding(left.first);
- }
-
- private List<Rectangle> getVerticalRectangles(List<Pair<Integer>> topPairs) {
- List<Rectangle> rectangles = new ArrayList<Rectangle>();
- for (Pair<Integer> top : topPairs) {
- int x = top.first;
- int width = top.second - top.first;
-
- rectangles.add(new Rectangle(x, 1, width, image.getHeight() - 2));
- }
- return rectangles;
- }
-
- private List<Rectangle> getHorizontalRectangles(List<Pair<Integer>> leftPairs) {
- List<Rectangle> rectangles = new ArrayList<Rectangle>();
- for (Pair<Integer> left : leftPairs) {
- int y = left.first;
- int height = left.second - left.first;
-
- rectangles.add(new Rectangle(1, y, image.getWidth() - 2, height));
- }
- return rectangles;
- }
-
- 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).first == 1) {
- return new Pair<Integer>(pairs.get(0).second - pairs.get(0).first, 0);
- } else {
- return new Pair<Integer>(0, pairs.get(0).second - pairs.get(0).first);
- }
- } else {
- int index = pairs.size() - 1;
- return new Pair<Integer>(pairs.get(0).second - pairs.get(0).first,
- pairs.get(index).second - pairs.get(index).first);
- }
- }
-
- 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.first;
- int height = left.second - left.first;
- for (Pair<Integer> top : topPairs) {
- int x = top.first;
- int width = top.second - top.first;
-
- 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 == BLACK_TICK) {
- 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 == BLACK_TICK) {
- 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);
- }
-
- void setLockVisible(boolean visible) {
- showLock = visible;
- repaint();
- }
- }
-
- static class Pair<E> {
- E first;
- E second;
-
- Pair(E first, E second) {
- this.first = first;
- this.second = second;
- }
-
- @Override
- public String toString() {
- return "Pair[" + first + ", " + second + "]";
- }
- }
}
diff --git a/draw9patch/src/main/java/com/android/draw9patch/ui/ImageViewer.java b/draw9patch/src/main/java/com/android/draw9patch/ui/ImageViewer.java
new file mode 100644
index 0000000..e36afc1
--- /dev/null
+++ b/draw9patch/src/main/java/com/android/draw9patch/ui/ImageViewer.java
@@ -0,0 +1,572 @@
+/*
+ *
+ * Copyright (C) 2013 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.draw9patch.ui;
+
+import java.awt.AWTEvent;
+import java.awt.BasicStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.TexturePaint;
+import java.awt.Toolkit;
+import java.awt.event.AWTEventListener;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+import java.awt.geom.Area;
+import java.awt.geom.Line2D;
+import java.awt.geom.RoundRectangle2D;
+import java.awt.image.BufferedImage;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.AncestorEvent;
+import javax.swing.event.AncestorListener;
+
+public class ImageViewer extends JComponent {
+ private final Color CORRUPTED_COLOR = new Color(1.0f, 0.0f, 0.0f, 0.7f);
+ private final Color LOCK_COLOR = new Color(0.0f, 0.0f, 0.0f, 0.7f);
+ private final Color STRIPES_COLOR = new Color(1.0f, 0.0f, 0.0f, 0.5f);
+ private final Color BACK_COLOR = new Color(0xc0c0c0);
+ private final Color HELP_COLOR = new Color(0xffffe1);
+ private final Color PATCH_COLOR = new Color(1.0f, 0.37f, 0.99f, 0.5f);
+ private final Color PATCH_ONEWAY_COLOR = new Color(0.37f, 1.0f, 0.37f, 0.5f);
+
+ private static final float STRIPES_WIDTH = 4.0f;
+ private static final double STRIPES_SPACING = 6.0;
+ private static final int STRIPES_ANGLE = 45;
+
+ /** Default zoom level for the 9patch image. */
+ public static final int DEFAULT_ZOOM = 8;
+
+ /** Minimum zoom level for the 9patch image. */
+ public static final int MIN_ZOOM = 1;
+
+ /** Maximum zoom level for the 9patch image. */
+ public static final int MAX_ZOOM = 16;
+
+ /** Current 9patch zoom level, {@link #MIN_ZOOM} <= zoom <= {@link #MAX_ZOOM} */
+ private int zoom = DEFAULT_ZOOM;
+ private boolean showPatches;
+ private boolean showLock = true;
+
+ private final TexturePaint texture;
+ private final Container container;
+ private final StatusBar statusBar;
+
+ private final Dimension size;
+
+ private boolean locked;
+
+ private int lastPositionX;
+ private int lastPositionY;
+ private int currentButton;
+ private boolean showCursor;
+
+ private JLabel helpLabel;
+ private boolean eraseMode;
+
+ private JButton checkButton;
+ private List<Rectangle> corruptedPatches;
+ private boolean showBadPatches;
+
+ private JPanel helpPanel;
+ private boolean drawingLine;
+ private int lineFromX;
+ private int lineFromY;
+ private int lineToX;
+ private int lineToY;
+ private boolean showDrawingLine;
+
+ private BufferedImage image;
+ private PatchInfo patchInfo;
+
+ ImageViewer(Container container, TexturePaint texture, BufferedImage image,
+ StatusBar statusBar) {
+ this.container = container;
+ this.texture = texture;
+ this.image = image;
+ this.statusBar = statusBar;
+
+ setLayout(new GridBagLayout());
+ helpPanel = new JPanel(new BorderLayout());
+ helpPanel.setBorder(new EmptyBorder(0, 6, 0, 6));
+ helpPanel.setBackground(HELP_COLOR);
+ helpLabel = new JLabel("Press Shift to erase pixels."
+ + " Press Control to draw layout bounds");
+ helpLabel.putClientProperty("JComponent.sizeVariant", "small");
+ helpPanel.add(helpLabel, BorderLayout.WEST);
+ checkButton = new JButton("Show bad patches");
+ checkButton.putClientProperty("JComponent.sizeVariant", "small");
+ checkButton.putClientProperty("JButton.buttonType", "roundRect");
+ helpPanel.add(checkButton, BorderLayout.EAST);
+
+ add(helpPanel, new GridBagConstraints(0, 0, 1, 1,
+ 1.0f, 1.0f, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL,
+ new Insets(0, 0, 0, 0), 0, 0));
+
+ setOpaque(true);
+
+ // Exact size will be set by setZoom() in AncestorListener#ancestorMoved.
+ size = new Dimension(0, 0);
+
+ addAncestorListener(new AncestorListener() {
+ @Override
+ public void ancestorRemoved(AncestorEvent event) {
+ }
+ @Override
+ public void ancestorMoved(AncestorEvent event) {
+ // Set exactly size.
+ setZoom(DEFAULT_ZOOM);
+ removeAncestorListener(this);
+ }
+ @Override
+ public void ancestorAdded(AncestorEvent event) {
+ }
+ });
+
+ updatePatchInfo();
+
+ addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent event) {
+ // Store the button here instead of retrieving it again in MouseDragged
+ // below, because on linux, calling MouseEvent.getButton() for the drag
+ // event returns 0, which appears to be technically correct (no button
+ // changed state).
+ currentButton = event.isShiftDown() ? MouseEvent.BUTTON3 : event.getButton();
+ currentButton = event.isControlDown() ? MouseEvent.BUTTON2 : currentButton;
+ startDrawingLine(event.getX(), event.getY());
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent event) {
+ endDrawingLine();
+ }
+ });
+ addMouseMotionListener(new MouseMotionAdapter() {
+ @Override
+ public void mouseDragged(MouseEvent event) {
+ if (!checkLockedRegion(event.getX(), event.getY())) {
+ // use the stored button, see note above
+
+ moveLine(event.getX(), event.getY());
+ }
+ }
+
+ @Override
+ public void mouseMoved(MouseEvent event) {
+ checkLockedRegion(event.getX(), event.getY());
+ }
+ });
+ Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
+ public void eventDispatched(AWTEvent event) {
+ enableEraseMode((KeyEvent) event);
+ }
+ }, AWTEvent.KEY_EVENT_MASK);
+
+ checkButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ if (!showBadPatches) {
+ corruptedPatches = CorruptPatch.findBadPatches(ImageViewer.this.image,
+ patchInfo);
+ checkButton.setText("Hide bad patches");
+ } else {
+ checkButton.setText("Show bad patches");
+ corruptedPatches = null;
+ }
+ repaint();
+ showBadPatches = !showBadPatches;
+ }
+ });
+ }
+
+ private void updatePatchInfo() {
+ patchInfo = new PatchInfo(image);
+ }
+
+ private void enableEraseMode(KeyEvent event) {
+ boolean oldEraseMode = eraseMode;
+ eraseMode = event.isShiftDown();
+ if (eraseMode != oldEraseMode) {
+ if (eraseMode) {
+ helpLabel.setText("Release Shift to draw pixels");
+ } else {
+ helpLabel.setText("Press Shift to erase pixels."
+ + " Press Control to draw layout bounds");
+ }
+ }
+ }
+
+ private void startDrawingLine(int x, int y) {
+ int left = (getWidth() - size.width) / 2;
+ int top = helpPanel.getHeight() + (getHeight() - size.height) / 2;
+
+ x = (x - left) / zoom;
+ y = (y - top) / zoom;
+
+ int width = image.getWidth();
+ int height = image.getHeight();
+ if (((x == 0 || x == width - 1) && (y > 0 && y < height - 1))
+ || ((x > 0 && x < width - 1) && (y == 0 || y == height - 1))) {
+ drawingLine = true;
+ lineFromX = x;
+ lineFromY = y;
+ lineToX = x;
+ lineToY = y;
+
+ showDrawingLine = true;
+
+ showCursor = false;
+
+ repaint();
+ }
+ }
+
+ private void moveLine(int x, int y) {
+ if (!drawingLine) {
+ return;
+ }
+
+ int left = (getWidth() - size.width) / 2;
+ int top = helpPanel.getHeight() + (getHeight() - size.height) / 2;
+
+ x = (x - left) / zoom;
+ y = (y - top) / zoom;
+
+ int width = image.getWidth();
+ int height = image.getHeight();
+
+ showDrawingLine = false;
+
+ if (((x == lineFromX) && (y > 0 && y < height - 1))
+ || ((x > 0 && x < width - 1) && (y == lineFromY))) {
+ lineToX = x;
+ lineToY = y;
+
+ showDrawingLine = true;
+ }
+
+ repaint();
+ }
+
+ private void endDrawingLine() {
+ if (!drawingLine) {
+ return;
+ }
+
+ drawingLine = false;
+
+ if (!showDrawingLine) {
+ return;
+ }
+
+ int color;
+ switch (currentButton) {
+ case MouseEvent.BUTTON1:
+ color = PatchInfo.BLACK_TICK;
+ break;
+ case MouseEvent.BUTTON2:
+ color = PatchInfo.RED_TICK;
+ break;
+ case MouseEvent.BUTTON3:
+ color = 0;
+ break;
+ default:
+ return;
+ }
+
+ int x = lineFromX;
+ int y = lineFromY;
+
+ int dx = 0;
+ int dy = 0;
+
+ if (lineToX != lineFromX)
+ dx = lineToX > lineFromX ? 1 : -1;
+ else if (lineToY != lineFromY)
+ dy = lineToY > lineFromY ? 1 : -1;
+
+ do {
+ image.setRGB(x, y, color);
+
+ if (x == lineToX && y == lineToY)
+ break;
+
+ x += dx;
+ y += dy;
+ } while (true);
+
+ updatePatchInfo();
+ notifyPatchesUpdated();
+ if (showBadPatches) {
+ corruptedPatches = CorruptPatch.findBadPatches(image, patchInfo);
+ }
+
+ repaint();
+ }
+
+ private boolean checkLockedRegion(int x, int y) {
+ int oldX = lastPositionX;
+ int oldY = lastPositionY;
+ lastPositionX = x;
+ lastPositionY = y;
+
+ int left = (getWidth() - size.width) / 2;
+ int top = helpPanel.getHeight() + (getHeight() - size.height) / 2;
+
+ x = (x - left) / zoom;
+ y = (y - top) / zoom;
+
+ int width = image.getWidth();
+ int height = image.getHeight();
+
+ statusBar.setPointerLocation(Math.max(0, Math.min(x, width - 1)),
+ Math.max(0, Math.min(y, height - 1)));
+
+ boolean previousLock = locked;
+ locked = x > 0 && x < width - 1 && y > 0 && y < height - 1;
+
+ boolean previousCursor = showCursor;
+ showCursor =
+ !drawingLine &&
+ ( ((x == 0 || x == width - 1) && (y > 0 && y < height - 1)) ||
+ ((x > 0 && x < width - 1) && (y == 0 || y == height - 1)) );
+
+ if (locked != previousLock) {
+ repaint();
+ } else if (showCursor || (showCursor != previousCursor)) {
+ Rectangle clip = new Rectangle(lastPositionX - 1 - zoom / 2,
+ lastPositionY - 1 - zoom / 2, zoom + 2, zoom + 2);
+ clip = clip.union(new Rectangle(oldX - 1 - zoom / 2,
+ oldY - 1 - zoom / 2, zoom + 2, zoom + 2));
+ repaint(clip);
+ }
+
+ return locked;
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ int x = (getWidth() - size.width) / 2;
+ int y = helpPanel.getHeight() + (getHeight() - size.height) / 2;
+
+ Graphics2D g2 = (Graphics2D) g.create();
+ g2.setColor(BACK_COLOR);
+ g2.fillRect(0, 0, getWidth(), getHeight());
+
+ g2.translate(x, y);
+ g2.setPaint(texture);
+ g2.fillRect(0, 0, size.width, size.height);
+ g2.scale(zoom, zoom);
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
+ g2.drawImage(image, 0, 0, null);
+
+ if (showPatches) {
+ g2.setColor(PATCH_COLOR);
+ for (Rectangle patch : patchInfo.patches) {
+ g2.fillRect(patch.x, patch.y, patch.width, patch.height);
+ }
+ g2.setColor(PATCH_ONEWAY_COLOR);
+ for (Rectangle patch : patchInfo.horizontalPatches) {
+ g2.fillRect(patch.x, patch.y, patch.width, patch.height);
+ }
+ for (Rectangle patch : patchInfo.verticalPatches) {
+ g2.fillRect(patch.x, patch.y, patch.width, patch.height);
+ }
+ }
+
+ if (corruptedPatches != null) {
+ g2.setColor(CORRUPTED_COLOR);
+ g2.setStroke(new BasicStroke(3.0f / zoom));
+ for (Rectangle patch : corruptedPatches) {
+ g2.draw(new RoundRectangle2D.Float(patch.x - 2.0f / zoom, patch.y - 2.0f / zoom,
+ patch.width + 2.0f / zoom, patch.height + 2.0f / zoom,
+ 6.0f / zoom, 6.0f / zoom));
+ }
+ }
+
+ if (showLock && locked) {
+ int width = image.getWidth();
+ int height = image.getHeight();
+
+ g2.setColor(LOCK_COLOR);
+ g2.fillRect(1, 1, width - 2, height - 2);
+
+ g2.setColor(STRIPES_COLOR);
+ g2.translate(1, 1);
+ paintStripes(g2, width - 2, height - 2);
+ g2.translate(-1, -1);
+ }
+
+ g2.dispose();
+
+ if (drawingLine && showDrawingLine) {
+ Graphics cursor = g.create();
+ cursor.setXORMode(Color.WHITE);
+ cursor.setColor(Color.BLACK);
+
+ x = Math.min(lineFromX, lineToX);
+ y = Math.min(lineFromY, lineToY);
+ int w = Math.abs(lineFromX - lineToX) + 1;
+ int h = Math.abs(lineFromY - lineToY) + 1;
+
+ x = x * zoom;
+ y = y * zoom;
+ w = w * zoom;
+ h = h * zoom;
+
+ int left = (getWidth() - size.width) / 2;
+ int top = helpPanel.getHeight() + (getHeight() - size.height)
+ / 2;
+
+ x += left;
+ y += top;
+
+ cursor.drawRect(x, y, w, h);
+ cursor.dispose();
+ }
+
+ if (showCursor) {
+ Graphics cursor = g.create();
+ cursor.setXORMode(Color.WHITE);
+ cursor.setColor(Color.BLACK);
+ cursor.drawRect(lastPositionX - zoom / 2, lastPositionY - zoom / 2, zoom, zoom);
+ cursor.dispose();
+ }
+ }
+
+ private void paintStripes(Graphics2D g, int width, int height) {
+ //draws pinstripes at the angle specified in this class
+ //and at the given distance apart
+ Shape oldClip = g.getClip();
+ Area area = new Area(new Rectangle(0, 0, width, height));
+ if(oldClip != null) {
+ area = new Area(oldClip);
+ }
+ area.intersect(new Area(new Rectangle(0,0,width,height)));
+ g.setClip(area);
+
+ g.setStroke(new BasicStroke(STRIPES_WIDTH));
+
+ double hypLength = Math.sqrt((width * width) +
+ (height * height));
+
+ double radians = Math.toRadians(STRIPES_ANGLE);
+ g.rotate(radians);
+
+ double spacing = STRIPES_SPACING;
+ spacing += STRIPES_WIDTH;
+ int numLines = (int)(hypLength / spacing);
+
+ for (int i=0; i<numLines; i++) {
+ double x = i * spacing;
+ Line2D line = new Line2D.Double(x, -hypLength, x, hypLength);
+ g.draw(line);
+ }
+ g.setClip(oldClip);
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ return size;
+ }
+
+ void setZoom(int value) {
+ int width = image.getWidth();
+ int height = image.getHeight();
+
+ zoom = value;
+ if (size.height == 0 || (getHeight() - size.height) == 0) {
+ size.setSize(width * zoom, height * zoom + helpPanel.getHeight());
+ } else {
+ size.setSize(width * zoom, height * zoom);
+ }
+
+ if (!size.equals(getSize())) {
+ setSize(size);
+ container.validate();
+ repaint();
+ }
+ }
+
+ void setPatchesVisible(boolean visible) {
+ showPatches = visible;
+ updatePatchInfo();
+ repaint();
+ }
+
+ void setLockVisible(boolean visible) {
+ showLock = visible;
+ repaint();
+ }
+
+ public void setImage(BufferedImage image) {
+ this.image = image;
+ }
+
+ public BufferedImage getImage() {
+ return image;
+ }
+
+ public PatchInfo getPatchInfo() {
+ return patchInfo;
+ }
+
+ public interface StatusBar {
+ void setPointerLocation(int x, int y);
+ }
+
+ public interface PatchUpdateListener {
+ void patchesUpdated();
+ }
+
+ private final Set<PatchUpdateListener> listeners = new HashSet<PatchUpdateListener>();
+
+ public void addPatchUpdateListener(PatchUpdateListener p) {
+ listeners.add(p);
+ }
+
+ private void notifyPatchesUpdated() {
+ for (PatchUpdateListener p: listeners) {
+ p.patchesUpdated();
+ }
+ }
+} \ No newline at end of file
diff --git a/draw9patch/src/main/java/com/android/draw9patch/ui/Pair.java b/draw9patch/src/main/java/com/android/draw9patch/ui/Pair.java
new file mode 100644
index 0000000..bc38671
--- /dev/null
+++ b/draw9patch/src/main/java/com/android/draw9patch/ui/Pair.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013 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.draw9patch.ui;
+
+public class Pair<E> {
+ E first;
+ E second;
+
+ Pair(E first, E second) {
+ this.first = first;
+ this.second = second;
+ }
+
+ @Override
+ public String toString() {
+ return "Pair[" + first + ", " + second + "]";
+ }
+}
diff --git a/draw9patch/src/main/java/com/android/draw9patch/ui/PatchInfo.java b/draw9patch/src/main/java/com/android/draw9patch/ui/PatchInfo.java
new file mode 100644
index 0000000..f68740b
--- /dev/null
+++ b/draw9patch/src/main/java/com/android/draw9patch/ui/PatchInfo.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2013 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.draw9patch.ui;
+
+import com.android.draw9patch.graphics.GraphicsUtilities;
+
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.List;
+
+public class PatchInfo {
+ /** Color used to indicate stretch regions and padding. */
+ public static final int BLACK_TICK = 0xFF000000;
+
+ /** Color used to indicate layout bounds. */
+ public static final int RED_TICK = 0xFFFF0000;
+
+ /** Areas of the image that are stretchable in both directions. */
+ public final List<Rectangle> patches;
+
+ /** Areas of the image that are not stretchable in either direction. */
+ public final List<Rectangle> fixed;
+
+ /** Areas of image stretchable horizontally. */
+ public final List<Rectangle> horizontalPatches;
+
+ /** Areas of image stretchable vertically. */
+ public final List<Rectangle> verticalPatches;
+
+ public final boolean verticalStartWithPatch;
+ public final boolean horizontalStartWithPatch;
+
+ /** Beginning and end padding in the horizontal direction */
+ public final Pair<Integer> horizontalPadding;
+
+ /** Beginning and end padding in the vertical direction */
+ public final Pair<Integer> verticalPadding;
+
+ private BufferedImage image;
+
+ public PatchInfo(BufferedImage image) {
+ this.image = image;
+
+ int width = image.getWidth();
+ int height = image.getHeight();
+
+ int[] row = GraphicsUtilities.getPixels(image, 0, 0, width, 1, null);
+ int[] column = GraphicsUtilities.getPixels(image, 0, 0, 1, height, null);
+
+ P left = getPatches(column);
+ verticalStartWithPatch = left.startsWithPatch;
+
+ P top = getPatches(row);
+ horizontalStartWithPatch = top.startsWithPatch;
+
+ fixed = getRectangles(left.fixed, top.fixed);
+ patches = getRectangles(left.patches, top.patches);
+
+ if (fixed.size() > 0) {
+ horizontalPatches = getRectangles(left.fixed, top.patches);
+ verticalPatches = getRectangles(left.patches, top.fixed);
+ } else {
+ if (top.fixed.size() > 0) {
+ horizontalPatches = new ArrayList<Rectangle>(0);
+ verticalPatches = getVerticalRectangles(top.fixed);
+ } else if (left.fixed.size() > 0) {
+ horizontalPatches = getHorizontalRectangles(left.fixed);
+ verticalPatches = new ArrayList<Rectangle>(0);
+ } else {
+ horizontalPatches = verticalPatches = new ArrayList<Rectangle>(0);
+ }
+ }
+
+ row = GraphicsUtilities.getPixels(image, 0, height - 1, width, 1, row);
+ column = GraphicsUtilities.getPixels(image, width - 1, 0, 1, height, column);
+
+ top = PatchInfo.getPatches(row);
+ horizontalPadding = getPadding(top.fixed);
+
+ left = PatchInfo.getPatches(column);
+ verticalPadding = getPadding(left.fixed);
+ }
+
+ private List<Rectangle> getVerticalRectangles(List<Pair<Integer>> topPairs) {
+ List<Rectangle> rectangles = new ArrayList<Rectangle>();
+ for (Pair<Integer> top : topPairs) {
+ int x = top.first;
+ int width = top.second - top.first;
+
+ rectangles.add(new Rectangle(x, 1, width, image.getHeight() - 2));
+ }
+ return rectangles;
+ }
+
+ private List<Rectangle> getHorizontalRectangles(List<Pair<Integer>> leftPairs) {
+ List<Rectangle> rectangles = new ArrayList<Rectangle>();
+ for (Pair<Integer> left : leftPairs) {
+ int y = left.first;
+ int height = left.second - left.first;
+
+ rectangles.add(new Rectangle(1, y, image.getWidth() - 2, height));
+ }
+ return rectangles;
+ }
+
+ 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).first == 1) {
+ return new Pair<Integer>(pairs.get(0).second - pairs.get(0).first, 0);
+ } else {
+ return new Pair<Integer>(0, pairs.get(0).second - pairs.get(0).first);
+ }
+ } else {
+ int index = pairs.size() - 1;
+ return new Pair<Integer>(pairs.get(0).second - pairs.get(0).first,
+ pairs.get(index).second - pairs.get(index).first);
+ }
+ }
+
+ 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.first;
+ int height = left.second - left.first;
+ for (Pair<Integer> top : topPairs) {
+ int x = top.first;
+ int width = top.second - top.first;
+
+ rectangles.add(new Rectangle(x, y, width, height));
+ }
+ }
+ return rectangles;
+ }
+
+ private static class P {
+ public final List<Pair<Integer>> fixed;
+ public final List<Pair<Integer>> patches;
+ public final boolean startsWithPatch;
+
+ private P(List<Pair<Integer>> f, List<Pair<Integer>> p, boolean s) {
+ fixed = f;
+ patches = p;
+ startsWithPatch = s;
+ }
+ }
+
+ private static P getPatches(int[] pixels) {
+ int lastIndex = 1;
+ int lastPixel = pixels[1];
+ boolean first = true;
+ boolean startWithPatch = false;
+
+ 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 == BLACK_TICK) {
+ if (first) startWithPatch = true;
+ patches.add(new Pair<Integer>(lastIndex, i));
+ } else {
+ fixed.add(new Pair<Integer>(lastIndex, i));
+ }
+ first = false;
+
+ lastIndex = i;
+ lastPixel = pixel;
+ }
+ }
+ if (lastPixel == BLACK_TICK) {
+ if (first) startWithPatch = 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 = true;
+ fixed.clear();
+ }
+
+ return new P(fixed, patches, startWithPatch);
+ }
+}
diff --git a/draw9patch/src/main/java/com/android/draw9patch/ui/StretchesViewer.java b/draw9patch/src/main/java/com/android/draw9patch/ui/StretchesViewer.java
new file mode 100644
index 0000000..efe0055
--- /dev/null
+++ b/draw9patch/src/main/java/com/android/draw9patch/ui/StretchesViewer.java
@@ -0,0 +1,326 @@
+/*
+ *
+ * Copyright (C) 2013 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.draw9patch.ui;
+
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.TexturePaint;
+import java.awt.image.BufferedImage;
+
+import javax.swing.BorderFactory;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+public class StretchesViewer extends JPanel {
+ public static final float DEFAULT_SCALE = 2.0f;
+ private static final int MARGIN = 24;
+
+ private final Container container;
+ private final ImageViewer viewer;
+ private final TexturePaint texture;
+
+ private BufferedImage image;
+ private PatchInfo patchInfo;
+
+ private StretchView horizontal;
+ private StretchView vertical;
+ private StretchView both;
+
+ private Dimension size;
+
+ private float horizontalPatchesSum;
+ private float verticalPatchesSum;
+
+ private boolean showPadding;
+
+ StretchesViewer(Container container, ImageViewer viewer, TexturePaint texture) {
+ this.container = container;
+ this.viewer = viewer;
+ this.texture = texture;
+
+ image = viewer.getImage();
+ patchInfo = viewer.getPatchInfo();
+
+ viewer.addPatchUpdateListener(new ImageViewer.PatchUpdateListener() {
+ @Override
+ public void patchesUpdated() {
+ computePatches();
+ }
+ });
+
+ setOpaque(false);
+ setLayout(new GridBagLayout());
+ setBorder(BorderFactory.createEmptyBorder(MARGIN, MARGIN, MARGIN, MARGIN));
+
+ horizontal = new StretchView();
+ vertical = new StretchView();
+ both = new StretchView();
+
+ setScale(DEFAULT_SCALE);
+
+ add(vertical, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
+ GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
+ add(horizontal, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
+ GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
+ add(both, new GridBagConstraints(0, 2, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
+ GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ Graphics2D g2 = (Graphics2D) g.create();
+ g2.setPaint(texture);
+ g2.fillRect(0, 0, getWidth(), getHeight());
+ g2.dispose();
+ }
+
+ void setScale(float scale) {
+ int patchWidth = image.getWidth() - 2;
+ int patchHeight = image.getHeight() - 2;
+
+ int scaledWidth = (int) (patchWidth * scale);
+ int scaledHeight = (int) (patchHeight * scale);
+
+ horizontal.scaledWidth = scaledWidth;
+ vertical.scaledHeight = scaledHeight;
+ both.scaledWidth = scaledWidth;
+ both.scaledHeight = scaledHeight;
+
+ size = new Dimension(scaledWidth, scaledHeight);
+
+ computePatches();
+ }
+
+ void computePatches() {
+ image = viewer.getImage();
+ patchInfo = viewer.getPatchInfo();
+
+ boolean measuredWidth = false;
+ boolean endRow = true;
+
+ int remainderHorizontal = 0;
+ int remainderVertical = 0;
+
+ if (patchInfo.fixed.size() > 0) {
+ int start = patchInfo.fixed.get(0).y;
+ for (Rectangle rect : patchInfo.fixed) {
+ if (rect.y > start) {
+ endRow = true;
+ measuredWidth = true;
+ }
+ if (!measuredWidth) {
+ remainderHorizontal += rect.width;
+ }
+ if (endRow) {
+ remainderVertical += rect.height;
+ endRow = false;
+ start = rect.y;
+ }
+ }
+ }
+
+ horizontal.remainderHorizontal = horizontal.scaledWidth - remainderHorizontal;
+ vertical.remainderHorizontal = vertical.scaledWidth - remainderHorizontal;
+ both.remainderHorizontal = both.scaledWidth - remainderHorizontal;
+
+ horizontal.remainderVertical = horizontal.scaledHeight - remainderVertical;
+ vertical.remainderVertical = vertical.scaledHeight - remainderVertical;
+ both.remainderVertical = both.scaledHeight - remainderVertical;
+
+ horizontalPatchesSum = 0;
+ if (patchInfo.horizontalPatches.size() > 0) {
+ int start = -1;
+ for (Rectangle rect : patchInfo.horizontalPatches) {
+ if (rect.x > start) {
+ horizontalPatchesSum += rect.width;
+ start = rect.x;
+ }
+ }
+ } else {
+ int start = -1;
+ for (Rectangle rect : patchInfo.patches) {
+ if (rect.x > start) {
+ horizontalPatchesSum += rect.width;
+ start = rect.x;
+ }
+ }
+ }
+
+ verticalPatchesSum = 0;
+ if (patchInfo.verticalPatches.size() > 0) {
+ int start = -1;
+ for (Rectangle rect : patchInfo.verticalPatches) {
+ if (rect.y > start) {
+ verticalPatchesSum += rect.height;
+ start = rect.y;
+ }
+ }
+ } else {
+ int start = -1;
+ for (Rectangle rect : patchInfo.patches) {
+ if (rect.y > start) {
+ verticalPatchesSum += rect.height;
+ start = rect.y;
+ }
+ }
+ }
+
+ setSize(size);
+ container.validate();
+ repaint();
+ }
+
+ void setPaddingVisible(boolean visible) {
+ showPadding = visible;
+ repaint();
+ }
+
+ private class StretchView extends JComponent {
+ private final Color PADDING_COLOR = new Color(0.37f, 0.37f, 1.0f, 0.5f);
+
+ int scaledWidth;
+ int scaledHeight;
+
+ int remainderHorizontal;
+ int remainderVertical;
+
+ StretchView() {
+ scaledWidth = image.getWidth();
+ scaledHeight = image.getHeight();
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ int x = (getWidth() - scaledWidth) / 2;
+ int y = (getHeight() - scaledHeight) / 2;
+
+ Graphics2D g2 = (Graphics2D) g.create();
+ g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+ g.translate(x, y);
+
+ x = 0;
+ y = 0;
+
+ if (patchInfo.patches.size() == 0) {
+ g.drawImage(image, 0, 0, scaledWidth, scaledHeight, null);
+ g2.dispose();
+ return;
+ }
+
+ int fixedIndex = 0;
+ int horizontalIndex = 0;
+ int verticalIndex = 0;
+ int patchIndex = 0;
+
+ boolean hStretch;
+ boolean vStretch;
+
+ float vWeightSum = 1.0f;
+ float vRemainder = remainderVertical;
+
+ vStretch = patchInfo.verticalStartWithPatch;
+ while (y < scaledHeight - 1) {
+ hStretch = patchInfo.horizontalStartWithPatch;
+
+ int height = 0;
+ float vExtra = 0.0f;
+
+ float hWeightSum = 1.0f;
+ float hRemainder = remainderHorizontal;
+
+ while (x < scaledWidth - 1) {
+ Rectangle r;
+ if (!vStretch) {
+ if (hStretch) {
+ r = patchInfo.horizontalPatches.get(horizontalIndex++);
+ float extra = r.width / horizontalPatchesSum;
+ int width = (int) (extra * hRemainder / hWeightSum);
+ hWeightSum -= extra;
+ hRemainder -= width;
+ g.drawImage(image, x, y, x + width, y + r.height, r.x, r.y,
+ r.x + r.width, r.y + r.height, null);
+ x += width;
+ } else {
+ r = patchInfo.fixed.get(fixedIndex++);
+ g.drawImage(image, 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 = patchInfo.patches.get(patchIndex++);
+ vExtra = r.height / verticalPatchesSum;
+ height = (int) (vExtra * vRemainder / vWeightSum);
+ float extra = r.width / horizontalPatchesSum;
+ int width = (int) (extra * hRemainder / hWeightSum);
+ hWeightSum -= extra;
+ hRemainder -= width;
+ g.drawImage(image, x, y, x + width, y + height, r.x, r.y,
+ r.x + r.width, r.y + r.height, null);
+ x += width;
+ } else {
+ r = patchInfo.verticalPatches.get(verticalIndex++);
+ vExtra = r.height / verticalPatchesSum;
+ height = (int) (vExtra * vRemainder / vWeightSum);
+ g.drawImage(image, 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;
+ }
+
+ if (showPadding) {
+ g.setColor(PADDING_COLOR);
+ g.fillRect(patchInfo.horizontalPadding.first,
+ patchInfo.verticalPadding.first,
+ scaledWidth - patchInfo.horizontalPadding.first
+ - patchInfo.horizontalPadding.second,
+ scaledHeight - patchInfo.verticalPadding.first
+ - patchInfo.verticalPadding.second);
+ }
+
+ g2.dispose();
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ return size;
+ }
+ }
+} \ No newline at end of file
diff --git a/draw9patch/src/test/java/com/android/draw9patch/ui/PatchInfoTest.java b/draw9patch/src/test/java/com/android/draw9patch/ui/PatchInfoTest.java
new file mode 100644
index 0000000..7ca4fdd
--- /dev/null
+++ b/draw9patch/src/test/java/com/android/draw9patch/ui/PatchInfoTest.java
@@ -0,0 +1,104 @@
+/*
+ *
+ * Copyright (C) 2013 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.draw9patch.ui;
+
+import junit.framework.TestCase;
+
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+
+public class PatchInfoTest extends TestCase {
+ private BufferedImage createImage(String[] data) {
+ int h = data.length;
+ int w = data[0].length();
+ BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+
+ for (int row = 0; row < h; row++) {
+ for (int col = 0; col < w; col++) {
+ char c = data[row].charAt(col);
+ image.setRGB(col, row, c == '*' ? PatchInfo.BLACK_TICK : 0);
+ }
+ }
+ return image;
+ }
+
+ public void testPatchInfo() {
+ BufferedImage image = createImage(new String[] {
+ "0123**6789",
+ "1........*",
+ "*........*",
+ "3........*",
+ "412*****89",
+ });
+ PatchInfo pi = new PatchInfo(image);
+
+ // The left and top patch markers don't begin from the first pixel
+ assertFalse(pi.horizontalStartWithPatch);
+ assertFalse(pi.verticalStartWithPatch);
+
+ // There should be one patch in the middle where the left and top patch markers intersect
+ assertEquals(1, pi.patches.size());
+ assertEquals(new Rectangle(4, 2, 2, 1), pi.patches.get(0));
+
+ // There should be 2 horizontal stretchable areas - area below the top marker but excluding
+ // the main patch
+ assertEquals(2, pi.horizontalPatches.size());
+ assertEquals(new Rectangle(4, 1, 2, 1), pi.horizontalPatches.get(0));
+ assertEquals(new Rectangle(4, 3, 2, 1), pi.horizontalPatches.get(1));
+
+ // Similarly, there should be 2 vertical stretchable areas
+ assertEquals(2, pi.verticalPatches.size());
+ assertEquals(new Rectangle(1, 2, 3, 1), pi.verticalPatches.get(0));
+ assertEquals(new Rectangle(6, 2, 3, 1), pi.verticalPatches.get(1));
+
+ // The should be 4 fixed regions - the regions that don't fall under the patches
+ assertEquals(4, pi.fixed.size());
+
+ // The horizontal padding is described by the bottom bar.
+ // In this case, there is a 2 pixel (pixels 1 & 2) padding at start and 1 pixel (pixel 8)
+ // padding at end
+ assertEquals(2, pi.horizontalPadding.first.intValue());
+ assertEquals(1, pi.horizontalPadding.second.intValue());
+
+ // The vertical padding is described by the bar at the right.
+ // In this case, there is no padding as the content area matches the image area
+ assertEquals(0, pi.verticalPadding.first.intValue());
+ assertEquals(0, pi.verticalPadding.second.intValue());
+ }
+
+ public void testPadding() {
+ BufferedImage image = createImage(new String[] {
+ "0123**6789",
+ "1.........",
+ "2.........",
+ "3........*",
+ "4........*",
+ "5***456789",
+ });
+ PatchInfo pi = new PatchInfo(image);
+
+ // 0 pixel padding at start and 5 pixel padding at the end (pixels 4 through 8 inclusive)
+ assertEquals(0, pi.horizontalPadding.first.intValue());
+ assertEquals(5, pi.horizontalPadding.second.intValue());
+
+ // 2 pixel padding at the start and 0 at the end
+ assertEquals(2, pi.verticalPadding.first.intValue());
+ assertEquals(0, pi.verticalPadding.second.intValue());
+ }
+}