diff options
Diffstat (limited to 'draw9patch')
21 files changed, 1964 insertions, 0 deletions
diff --git a/draw9patch/Android.mk b/draw9patch/Android.mk new file mode 100644 index 0000000..934495d --- /dev/null +++ b/draw9patch/Android.mk @@ -0,0 +1,17 @@ +# 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. + +DRAW9PATCH_LOCAL_DIR := $(call my-dir) +include $(DRAW9PATCH_LOCAL_DIR)/etc/Android.mk +include $(DRAW9PATCH_LOCAL_DIR)/src/Android.mk diff --git a/draw9patch/MODULE_LICENSE_APACHE2 b/draw9patch/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/draw9patch/MODULE_LICENSE_APACHE2 diff --git a/draw9patch/etc/Android.mk b/draw9patch/etc/Android.mk new file mode 100644 index 0000000..8d7d080 --- /dev/null +++ b/draw9patch/etc/Android.mk @@ -0,0 +1,20 @@ +# 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_PREBUILT_EXECUTABLES := draw9patch +include $(BUILD_HOST_PREBUILT) + diff --git a/draw9patch/etc/draw9patch b/draw9patch/etc/draw9patch new file mode 100755 index 0000000..5d272a6 --- /dev/null +++ b/draw9patch/etc/draw9patch @@ -0,0 +1,63 @@ +#!/bin/sh +# Copyright 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. + +# Set up prog to be the path of this script, including following symlinks, +# and set up progdir to be the fully-qualified pathname of its directory. +prog="$0" +while [ -h "${prog}" ]; do + newProg=`/bin/ls -ld "${prog}"` + newProg=`expr "${newProg}" : ".* -> \(.*\)$"` + if expr "x${newProg}" : 'x/' >/dev/null; then + prog="${newProg}" + else + progdir=`dirname "${prog}"` + prog="${progdir}/${newProg}" + fi +done +oldwd=`pwd` +progdir=`dirname "${prog}"` +cd "${progdir}" +progdir=`pwd` +prog="${progdir}"/`basename "${prog}"` +cd "${oldwd}" + +jarfile=draw9patch.jar +frameworkdir="$progdir" +if [ ! -r "$frameworkdir/$jarfile" ] +then + frameworkdir=`dirname "$progdir"`/tools/lib + libdir=`dirname "$progdir"`/tools/lib +fi +if [ ! -r "$frameworkdir/$jarfile" ] +then + frameworkdir=`dirname "$progdir"`/framework + libdir=`dirname "$progdir"`/lib +fi +if [ ! -r "$frameworkdir/$jarfile" ] +then + echo `basename "$prog"`": can't find $jarfile" + exit 1 +fi + +if [ "$OSTYPE" = "cygwin" ] ; then + jarpath=`cygpath -w "$frameworkdir/$jarfile"` + progdir=`cygpath -w "$progdir"` +else + jarpath="$frameworkdir/$jarfile" +fi + +# need to use "java.ext.dirs" because "-jar" causes classpath to be ignored +# might need more memory, e.g. -Xmx128M +exec java -Djava.ext.dirs="$frameworkdir" -jar "$jarpath" "$@" diff --git a/draw9patch/etc/draw9patch.bat b/draw9patch/etc/draw9patch.bat new file mode 100755 index 0000000..e267b06 --- /dev/null +++ b/draw9patch/etc/draw9patch.bat @@ -0,0 +1,41 @@ +@echo off +rem Copyright (C) 2008 The Android Open Source Project +rem +rem Licensed under the Apache License, Version 2.0 (the "License"); +rem you may not use this file except in compliance with the License. +rem You may obtain a copy of the License at +rem +rem http://www.apache.org/licenses/LICENSE-2.0 +rem +rem Unless required by applicable law or agreed to in writing, software +rem distributed under the License is distributed on an "AS IS" BASIS, +rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +rem See the License for the specific language governing permissions and +rem limitations under the License. + +rem don't modify the caller's environment +setlocal + +rem Set up prog to be the path of this script, including following symlinks, +rem and set up progdir to be the fully-qualified pathname of its directory. +set prog=%~f0 + +rem Change current directory and drive to where the script is, to avoid +rem issues with directories containing whitespaces. +cd /d %~dp0 + +set jarfile=draw9patch.jar +set frameworkdir= +set libdir= + +if exist %frameworkdir%%jarfile% goto JarFileOk + set frameworkdir=lib\ + +if exist %frameworkdir%%jarfile% goto JarFileOk + set frameworkdir=..\framework\ + +:JarFileOk + +set jarpath=%frameworkdir%%jarfile% + +call java -Djava.ext.dirs=%frameworkdir% -jar %jarpath% %* diff --git a/draw9patch/etc/manifest.txt b/draw9patch/etc/manifest.txt new file mode 100644 index 0000000..b2e3528 --- /dev/null +++ b/draw9patch/etc/manifest.txt @@ -0,0 +1,2 @@ +Main-Class: com.android.draw9patch.Application +Class-Path: swing-worker-1.1.jar diff --git a/draw9patch/src/Android.mk b/draw9patch/src/Android.mk new file mode 100644 index 0000000..3dc9db4 --- /dev/null +++ b/draw9patch/src/Android.mk @@ -0,0 +1,26 @@ +# 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-subdir-java-files) +LOCAL_JAVA_RESOURCE_DIRS := resources + +LOCAL_JAR_MANIFEST := ../etc/manifest.txt +LOCAL_JAVA_LIBRARIES := \ + swing-worker-1.1 +LOCAL_MODULE := draw9patch + +include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/draw9patch/src/com/android/draw9patch/Application.java b/draw9patch/src/com/android/draw9patch/Application.java new file mode 100644 index 0000000..c7c6aaf --- /dev/null +++ b/draw9patch/src/com/android/draw9patch/Application.java @@ -0,0 +1,54 @@ +/* + * 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.draw9patch; + +import com.android.draw9patch.ui.MainFrame; + +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; + +public class Application { + private static void initUserInterface() { + System.setProperty("apple.laf.useScreenMenuBar", "true"); + System.setProperty("com.apple.mrj.application.apple.menu.about.name", "Draw 9-patch"); + + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (UnsupportedLookAndFeelException e) { + e.printStackTrace(); + } + } + + public static void main(String... args) { + initUserInterface(); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + MainFrame frame = new MainFrame(); + frame.setDefaultCloseOperation(MainFrame.EXIT_ON_CLOSE); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + } + }); + } +} diff --git a/draw9patch/src/com/android/draw9patch/graphics/GraphicsUtilities.java b/draw9patch/src/com/android/draw9patch/graphics/GraphicsUtilities.java new file mode 100644 index 0000000..c6c182c --- /dev/null +++ b/draw9patch/src/com/android/draw9patch/graphics/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.draw9patch.graphics; + +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/draw9patch/src/com/android/draw9patch/ui/GradientPanel.java b/draw9patch/src/com/android/draw9patch/ui/GradientPanel.java new file mode 100644 index 0000000..bc1465f --- /dev/null +++ b/draw9patch/src/com/android/draw9patch/ui/GradientPanel.java @@ -0,0 +1,47 @@ +/* + * 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.draw9patch.ui; + +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.GradientPaint; +import java.awt.Color; +import java.awt.Rectangle; +import java.awt.BorderLayout; +import javax.swing.JPanel; + +class GradientPanel extends JPanel { + private static final int DARK_BLUE = 0x202737; + + GradientPanel() { + super(new BorderLayout()); + } + + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2 = (Graphics2D) g; + Rectangle clip = g2.getClipBounds(); + Paint paint = g2.getPaint(); + + g2.setPaint(new GradientPaint(0.0f, getHeight() * 0.22f, new Color(DARK_BLUE), + 0.0f, getHeight() * 0.9f, Color.BLACK)); + g2.fillRect(clip.x, clip.y, clip.width, clip.height); + + g2.setPaint(paint); + } +} diff --git a/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java b/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java new file mode 100644 index 0000000..86c801f --- /dev/null +++ b/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java @@ -0,0 +1,1129 @@ +/* + * 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.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.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.GridBagConstraints; +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.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.AWTEventListener; +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.io.File; +import java.net.URL; +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; + +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; + + private String name; + private BufferedImage image; + private boolean is9Patch; + + private ImageViewer viewer; + private StretchesViewer stretchesViewer; + private JLabel xLabel; + private JLabel yLabel; + + 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(); + } + + private void loadSupport() { + try { + URL resource = getClass().getResource("/images/checker.png"); + BufferedImage checker = GraphicsUtilities.loadCompatibleImage(resource); + texture = new TexturePaint(checker, new Rectangle2D.Double(0, 0, + checker.getWidth(), checker.getHeight())); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void buildImageViewer() { + viewer = new ImageViewer(); + + JSplitPane splitter = new JSplitPane(); + splitter.setContinuousLayout(true); + splitter.setResizeWeight(0.8); + splitter.setBorder(null); + + JScrollPane scroller = new JScrollPane(viewer); + scroller.setOpaque(false); + scroller.setBorder(null); + scroller.getViewport().setBorder(null); + scroller.getViewport().setOpaque(false); + + splitter.setLeftComponent(scroller); + splitter.setRightComponent(buildStretchesViewer()); + + add(splitter); + } + + private JComponent buildStretchesViewer() { + stretchesViewer = new StretchesViewer(); + JScrollPane scroller = new JScrollPane(stretchesViewer); + scroller.setBorder(null); + scroller.getViewport().setBorder(null); + scroller.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); + scroller.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + return scroller; + } + + private void buildStatusPanel() { + JPanel status = new JPanel(new GridBagLayout()); + status.setOpaque(false); + + JLabel label = new JLabel(); + label.setForeground(Color.WHITE); + label.setText("Zoom: "); + label.putClientProperty("JComponent.sizeVariant", "small"); + status.add(label, new GridBagConstraints(0, 0, 1, 1, 0.0f, 0.0f, + GridBagConstraints.LINE_END, GridBagConstraints.NONE, + new Insets(0, 6, 0, 0), 0, 0)); + + label = new JLabel(); + label.setForeground(Color.WHITE); + label.setText("100%"); + label.putClientProperty("JComponent.sizeVariant", "small"); + status.add(label, new GridBagConstraints(1, 0, 1, 1, 0.0f, 0.0f, + GridBagConstraints.LINE_END, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0)); + + JSlider zoomSlider = new JSlider(1, 16, DEFAULT_ZOOM); + zoomSlider.setSnapToTicks(true); + zoomSlider.putClientProperty("JComponent.sizeVariant", "small"); + zoomSlider.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent evt) { + viewer.setZoom(((JSlider) evt.getSource()).getValue()); + } + }); + status.add(zoomSlider, new GridBagConstraints(2, 0, 1, 1, 0.0f, 0.0f, + GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0)); + + JLabel maxZoomLabel = new JLabel(); + maxZoomLabel.setForeground(Color.WHITE); + maxZoomLabel.putClientProperty("JComponent.sizeVariant", "small"); + maxZoomLabel.setText("800%"); + status.add(maxZoomLabel, new GridBagConstraints(3, 0, 1, 1, 0.0f, 0.0f, + GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0)); + + label = new JLabel(); + label.setForeground(Color.WHITE); + label.setText("Patch scale: "); + label.putClientProperty("JComponent.sizeVariant", "small"); + status.add(label, new GridBagConstraints(0, 1, 1, 1, 0.0f, 0.0f, + GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 6, 0, 0), 0, 0)); + + label = new JLabel(); + label.setForeground(Color.WHITE); + label.setText("2x"); + label.putClientProperty("JComponent.sizeVariant", "small"); + status.add(label, new GridBagConstraints(1, 1, 1, 1, 0.0f, 0.0f, + GridBagConstraints.LINE_END, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0)); + + zoomSlider = new JSlider(200, 600, (int) (DEFAULT_SCALE * 100.0f)); + zoomSlider.setSnapToTicks(true); + zoomSlider.putClientProperty("JComponent.sizeVariant", "small"); + zoomSlider.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent evt) { + stretchesViewer.setScale(((JSlider) evt.getSource()).getValue() / 100.0f); + } + }); + status.add(zoomSlider, new GridBagConstraints(2, 1, 1, 1, 0.0f, 0.0f, + GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0)); + + maxZoomLabel = new JLabel(); + maxZoomLabel.setForeground(Color.WHITE); + maxZoomLabel.putClientProperty("JComponent.sizeVariant", "small"); + maxZoomLabel.setText("6x"); + status.add(maxZoomLabel, new GridBagConstraints(3, 1, 1, 1, 0.0f, 0.0f, + GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0)); + + JCheckBox showLock = new JCheckBox("Show lock"); + showLock.setOpaque(false); + showLock.setForeground(Color.WHITE); + showLock.setSelected(true); + showLock.putClientProperty("JComponent.sizeVariant", "small"); + showLock.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + viewer.setLockVisible(((JCheckBox) event.getSource()).isSelected()); + } + }); + status.add(showLock, new GridBagConstraints(4, 0, 1, 1, 0.0f, 0.0f, + GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 12, 0, 0), 0, 0)); + + JCheckBox showPatches = new JCheckBox("Show patches"); + showPatches.setOpaque(false); + showPatches.setForeground(Color.WHITE); + showPatches.putClientProperty("JComponent.sizeVariant", "small"); + showPatches.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + viewer.setPatchesVisible(((JCheckBox) event.getSource()).isSelected()); + } + }); + status.add(showPatches, new GridBagConstraints(4, 1, 1, 1, 0.0f, 0.0f, + GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 12, 0, 0), 0, 0)); + + JCheckBox showPadding = new JCheckBox("Show content"); + showPadding.setOpaque(false); + showPadding.setForeground(Color.WHITE); + showPadding.putClientProperty("JComponent.sizeVariant", "small"); + showPadding.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + stretchesViewer.setPaddingVisible(((JCheckBox) event.getSource()).isSelected()); + } + }); + status.add(showPadding, new GridBagConstraints(5, 0, 1, 1, 0.0f, 0.0f, + GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 12, 0, 0), 0, 0)); + + status.add(Box.createHorizontalGlue(), new GridBagConstraints(6, 0, 1, 1, 1.0f, 1.0f, + GridBagConstraints.LINE_START, GridBagConstraints.BOTH, + new Insets(0, 0, 0, 0), 0, 0)); + + label = new JLabel("X: "); + label.setForeground(Color.WHITE); + label.putClientProperty("JComponent.sizeVariant", "small"); + status.add(label, new GridBagConstraints(7, 0, 1, 1, 0.0f, 0.0f, + GridBagConstraints.LINE_END, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0)); + + xLabel = new JLabel("0px"); + xLabel.setForeground(Color.WHITE); + xLabel.putClientProperty("JComponent.sizeVariant", "small"); + status.add(xLabel, new GridBagConstraints(8, 0, 1, 1, 0.0f, 0.0f, + GridBagConstraints.LINE_END, GridBagConstraints.NONE, + new Insets(0, 0, 0, 6), 0, 0)); + + label = new JLabel("Y: "); + label.setForeground(Color.WHITE); + label.putClientProperty("JComponent.sizeVariant", "small"); + status.add(label, new GridBagConstraints(7, 1, 1, 1, 0.0f, 0.0f, + GridBagConstraints.LINE_END, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0)); + + yLabel = new JLabel("0px"); + yLabel.setForeground(Color.WHITE); + yLabel.putClientProperty("JComponent.sizeVariant", "small"); + status.add(yLabel, new GridBagConstraints(8, 1, 1, 1, 0.0f, 0.0f, + GridBagConstraints.LINE_END, GridBagConstraints.NONE, + new Insets(0, 0, 0, 6), 0, 0)); + + add(status, BorderLayout.SOUTH); + } + + private void checkImage() { + is9Patch = name.endsWith(EXTENSION_9PATCH); + if (!is9Patch) { + convertTo9Patch(); + } else { + ensure9Patch(); + } + } + + private void ensure9Patch() { + 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 void convertTo9Patch() { + BufferedImage buffer = GraphicsUtilities.createTranslucentCompatibleImage( + image.getWidth() + 2, image.getHeight() + 2); + + Graphics2D g2 = buffer.createGraphics(); + g2.drawImage(image, 1, 1, null); + g2.dispose(); + + image = buffer; + name = name.substring(0, name.lastIndexOf('.')) + ".9.png"; + } + + File chooseSaveFile() { + if (is9Patch) { + return new File(name); + } else { + JFileChooser chooser = new JFileChooser(); + chooser.setFileFilter(new PngFileFilter()); + int choice = chooser.showSaveDialog(this); + if (choice == JFileChooser.APPROVE_OPTION) { + File file = chooser.getSelectedFile(); + if (!file.getAbsolutePath().endsWith(EXTENSION_9PATCH)) { + String path = file.getAbsolutePath(); + if (path.endsWith(".png")) { + path = path.substring(0, path.lastIndexOf(".png")) + EXTENSION_9PATCH; + } else { + path = path + EXTENSION_9PATCH; + } + name = path; + is9Patch = true; + return new File(path); + } + is9Patch = true; + return file; + } + } + return null; + } + + 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; + } + } + } + + verticalPatchesSum = 0; + if (verticalPatches.size() > 0) { + int start = -1; + for (Rectangle rect : verticalPatches) { + 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 || horizontalPatches.size() == 0 || + verticalPatches.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; + private boolean showPatches; + private boolean showLock = true; + + private Dimension size; + + private boolean locked; + + private int[] row; + private int[] column; + + private int lastPositionX; + private int lastPositionY; + private boolean showCursor; + + private JLabel helpLabel; + private boolean eraseMode; + + private JButton checkButton; + private List<Rectangle> corruptedPatches; + private boolean showBadPatches; + + private JPanel helpPanel; + + 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"); + 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); + + setZoom(DEFAULT_ZOOM); + findPatches(); + + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent event) { + paint(event.getX(), event.getY(), event.isShiftDown() ? MouseEvent.BUTTON3 : + event.getButton()); + } + }); + addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseDragged(MouseEvent event) { + if (!checkLockedRegion(event.getX(), event.getY())) { + paint(event.getX(), event.getY(), event.isShiftDown() ? MouseEvent.BUTTON3 : + event.getButton()); + } + } + + @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"); + } + } + } + + private void paint(int x, int y, int button) { + int color; + switch (button) { + case MouseEvent.BUTTON1: + color = 0xFF000000; + break; + case MouseEvent.BUTTON3: + color = 0; + break; + default: + 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(); + if (((x == 0 || x == width - 1) && (y > 0 && y < height - 1)) || + ((x > 0 && x < width - 1) && (y == 0 || y == height - 1))) { + image.setRGB(x, y, color); + 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 = ((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 (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; + size = new Dimension(width * zoom, height * zoom); + + 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 { + 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 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 == 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); + } + + 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/com/android/draw9patch/ui/ImageTransferHandler.java b/draw9patch/src/com/android/draw9patch/ui/ImageTransferHandler.java new file mode 100644 index 0000000..a62884f --- /dev/null +++ b/draw9patch/src/com/android/draw9patch/ui/ImageTransferHandler.java @@ -0,0 +1,63 @@ +/* + * 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.draw9patch.ui; + +import javax.swing.TransferHandler; +import javax.swing.JComponent; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.net.MalformedURLException; + +class ImageTransferHandler extends TransferHandler { + private final MainFrame mainFrame; + + ImageTransferHandler(MainFrame mainFrame) { + this.mainFrame = mainFrame; + } + + @Override + public boolean importData(JComponent component, Transferable transferable) { + try { + Object data = transferable.getTransferData(DataFlavor.javaFileListFlavor); + //noinspection unchecked + final File file = ((List<File>) data).get(0); + mainFrame.open(file).execute(); + } catch (UnsupportedFlavorException e) { + return false; + } catch (MalformedURLException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + return true; + } + + @Override + public boolean canImport(JComponent component, DataFlavor[] dataFlavors) { + for (DataFlavor flavor : dataFlavors) { + if (flavor.isFlavorJavaFileListType()) { + return true; + } + } + return false; + } +} diff --git a/draw9patch/src/com/android/draw9patch/ui/MainFrame.java b/draw9patch/src/com/android/draw9patch/ui/MainFrame.java new file mode 100644 index 0000000..9ffd93e --- /dev/null +++ b/draw9patch/src/com/android/draw9patch/ui/MainFrame.java @@ -0,0 +1,164 @@ +/* + * 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.draw9patch.ui; + +import com.android.draw9patch.ui.action.ExitAction; +import com.android.draw9patch.ui.action.OpenAction; +import com.android.draw9patch.ui.action.SaveAction; +import com.android.draw9patch.graphics.GraphicsUtilities; + +import javax.swing.JFrame; +import javax.swing.JMenuBar; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.ActionMap; +import javax.swing.JFileChooser; +import javax.imageio.ImageIO; +import java.awt.HeadlessException; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.concurrent.ExecutionException; + +import org.jdesktop.swingworker.SwingWorker; + +public class MainFrame extends JFrame { + private ActionMap actionsMap; + private JMenuItem saveMenuItem; + private ImageEditorPanel imageEditor; + + public MainFrame() throws HeadlessException { + super("Draw 9-patch"); + + buildActions(); + buildMenuBar(); + buildContent(); + + showOpenFilePanel(); + + // pack(); + setSize(1024, 600); + } + + private void buildActions() { + actionsMap = new ActionMap(); + actionsMap.put(OpenAction.ACTION_NAME, new OpenAction(this)); + actionsMap.put(SaveAction.ACTION_NAME, new SaveAction(this)); + actionsMap.put(ExitAction.ACTION_NAME, new ExitAction(this)); + } + + private void buildMenuBar() { + JMenu fileMenu = new JMenu("File"); + JMenuItem openMenuItem = new JMenuItem(); + saveMenuItem = new JMenuItem(); + JMenuItem exitMenuItem = new JMenuItem(); + + openMenuItem.setAction(actionsMap.get(OpenAction.ACTION_NAME)); + fileMenu.add(openMenuItem); + + saveMenuItem.setAction(actionsMap.get(SaveAction.ACTION_NAME)); + saveMenuItem.setEnabled(false); + fileMenu.add(saveMenuItem); + + exitMenuItem.setAction(actionsMap.get(ExitAction.ACTION_NAME)); + fileMenu.add(exitMenuItem); + + JMenuBar menuBar = new JMenuBar(); + menuBar.add(fileMenu); + setJMenuBar(menuBar); + } + + private void buildContent() { + setContentPane(new GradientPanel()); + } + + private void showOpenFilePanel() { + add(new OpenFilePanel(this)); + } + + public SwingWorker<?, ?> open(File file) { + if (file == null) { + JFileChooser chooser = new JFileChooser(); + chooser.setFileFilter(new PngFileFilter()); + int choice = chooser.showOpenDialog(this); + if (choice == JFileChooser.APPROVE_OPTION) { + return new OpenTask(chooser.getSelectedFile()); + } else { + return null; + } + } else { + return new OpenTask(file); + } + } + + void showImageEditor(BufferedImage image, String name) { + getContentPane().removeAll(); + imageEditor = new ImageEditorPanel(this, image, name); + add(imageEditor); + saveMenuItem.setEnabled(true); + validate(); + repaint(); + } + + public SwingWorker<?, ?> save() { + if (imageEditor == null) { + return null; + } + + File file = imageEditor.chooseSaveFile(); + return file != null ? new SaveTask(file) : null; + } + + private class SaveTask extends SwingWorker<Boolean, Void> { + private final File file; + + SaveTask(File file) { + this.file = file; + } + + protected Boolean doInBackground() throws Exception { + try { + ImageIO.write(imageEditor.getImage(), "PNG", file); + } catch (Exception e) { + e.printStackTrace(); + } + return true; + } + } + + private class OpenTask extends SwingWorker<BufferedImage, Void> { + private final File file; + + OpenTask(File file) { + this.file = file; + } + + protected BufferedImage doInBackground() throws Exception { + return GraphicsUtilities.loadCompatibleImage(file.toURI().toURL()); + } + + @Override + protected void done() { + try { + showImageEditor(get(), file.getAbsolutePath()); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + } + } +} diff --git a/draw9patch/src/com/android/draw9patch/ui/OpenFilePanel.java b/draw9patch/src/com/android/draw9patch/ui/OpenFilePanel.java new file mode 100644 index 0000000..a444332 --- /dev/null +++ b/draw9patch/src/com/android/draw9patch/ui/OpenFilePanel.java @@ -0,0 +1,51 @@ +package com.android.draw9patch.ui; + +import com.android.draw9patch.graphics.GraphicsUtilities; + +import javax.swing.JComponent; +import java.awt.image.BufferedImage; +import java.awt.Graphics; +import java.io.IOException; +import java.net.URL;/* + * 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. + */ + +class OpenFilePanel extends JComponent { + private BufferedImage dropHere; + + OpenFilePanel(MainFrame mainFrame) { + setOpaque(false); + loadSupportImage(); + setTransferHandler(new ImageTransferHandler(mainFrame)); + } + + private void loadSupportImage() { + try { + URL resource = getClass().getResource("/images/drop.png"); + dropHere = GraphicsUtilities.loadCompatibleImage(resource); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + protected void paintComponent(Graphics g) { + int x = (getWidth() - dropHere.getWidth()) / 2; + int y = (getHeight() - dropHere.getHeight()) / 2; + + g.drawImage(dropHere, x, y, null); + } + +} diff --git a/draw9patch/src/com/android/draw9patch/ui/PngFileFilter.java b/draw9patch/src/com/android/draw9patch/ui/PngFileFilter.java new file mode 100644 index 0000000..8f8885a --- /dev/null +++ b/draw9patch/src/com/android/draw9patch/ui/PngFileFilter.java @@ -0,0 +1,32 @@ +/* + * 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.draw9patch.ui; + +import javax.swing.filechooser.FileFilter; +import java.io.File; + +class PngFileFilter extends FileFilter { + @Override + public boolean accept(File f) { + return f.isDirectory() || f.getName().toLowerCase().endsWith(".png"); + } + + @Override + public String getDescription() { + return "PNG Image (*.png)"; + } +} diff --git a/draw9patch/src/com/android/draw9patch/ui/action/BackgroundAction.java b/draw9patch/src/com/android/draw9patch/ui/action/BackgroundAction.java new file mode 100644 index 0000000..85d9d4f --- /dev/null +++ b/draw9patch/src/com/android/draw9patch/ui/action/BackgroundAction.java @@ -0,0 +1,29 @@ +/* + * 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.draw9patch.ui.action; + +import org.jdesktop.swingworker.SwingWorker; + +import javax.swing.AbstractAction; + +public abstract class BackgroundAction extends AbstractAction { + protected void executeBackgroundTask(SwingWorker<?, ?> worker) { + if (worker != null) { + worker.execute(); + } + } +} diff --git a/draw9patch/src/com/android/draw9patch/ui/action/ExitAction.java b/draw9patch/src/com/android/draw9patch/ui/action/ExitAction.java new file mode 100644 index 0000000..b6f047d --- /dev/null +++ b/draw9patch/src/com/android/draw9patch/ui/action/ExitAction.java @@ -0,0 +1,44 @@ +/* + * 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.draw9patch.ui.action; + +import javax.swing.AbstractAction; +import javax.swing.KeyStroke; +import javax.swing.JFrame; +import java.awt.event.KeyEvent; +import java.awt.event.ActionEvent; +import java.awt.Toolkit; + +public class ExitAction extends AbstractAction { + public static final String ACTION_NAME = "exit"; + private JFrame frame; + + public ExitAction(JFrame frame) { + putValue(NAME, "Quit"); + putValue(SHORT_DESCRIPTION, "Quit"); + putValue(LONG_DESCRIPTION, "Quit"); + putValue(MNEMONIC_KEY, KeyEvent.VK_Q); + putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_Q, + Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + this.frame = frame; + } + + public void actionPerformed(ActionEvent e) { + frame.dispose(); + System.exit(0); + } +} diff --git a/draw9patch/src/com/android/draw9patch/ui/action/OpenAction.java b/draw9patch/src/com/android/draw9patch/ui/action/OpenAction.java new file mode 100644 index 0000000..45ee5be --- /dev/null +++ b/draw9patch/src/com/android/draw9patch/ui/action/OpenAction.java @@ -0,0 +1,43 @@ +/* + * 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.draw9patch.ui.action; + +import com.android.draw9patch.ui.MainFrame; + +import javax.swing.KeyStroke; +import java.awt.event.KeyEvent; +import java.awt.event.ActionEvent; +import java.awt.Toolkit; + +public class OpenAction extends BackgroundAction { + public static final String ACTION_NAME = "open"; + private MainFrame frame; + + public OpenAction(MainFrame frame) { + this.frame = frame; + putValue(NAME, "Open 9-patch..."); + putValue(SHORT_DESCRIPTION, "Open..."); + putValue(LONG_DESCRIPTION, "Open 9-patch..."); + putValue(MNEMONIC_KEY, KeyEvent.VK_O); + putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_O, + Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + } + + public void actionPerformed(ActionEvent e) { + executeBackgroundTask(frame.open(null)); + } +} diff --git a/draw9patch/src/com/android/draw9patch/ui/action/SaveAction.java b/draw9patch/src/com/android/draw9patch/ui/action/SaveAction.java new file mode 100644 index 0000000..5c1dc52 --- /dev/null +++ b/draw9patch/src/com/android/draw9patch/ui/action/SaveAction.java @@ -0,0 +1,43 @@ +/* + * 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.draw9patch.ui.action; + +import com.android.draw9patch.ui.MainFrame; + +import javax.swing.KeyStroke; +import java.awt.event.KeyEvent; +import java.awt.event.ActionEvent; +import java.awt.Toolkit; + +public class SaveAction extends BackgroundAction { + public static final String ACTION_NAME = "save"; + private MainFrame frame; + + public SaveAction(MainFrame frame) { + this.frame = frame; + putValue(NAME, "Save 9-patch..."); + putValue(SHORT_DESCRIPTION, "Save..."); + putValue(LONG_DESCRIPTION, "Save 9-patch..."); + putValue(MNEMONIC_KEY, KeyEvent.VK_S); + putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_S, + Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + } + + public void actionPerformed(ActionEvent e) { + executeBackgroundTask(frame.save()); + } +} diff --git a/draw9patch/src/resources/images/checker.png b/draw9patch/src/resources/images/checker.png Binary files differnew file mode 100644 index 0000000..78908f4 --- /dev/null +++ b/draw9patch/src/resources/images/checker.png diff --git a/draw9patch/src/resources/images/drop.png b/draw9patch/src/resources/images/drop.png Binary files differnew file mode 100644 index 0000000..7a7436a --- /dev/null +++ b/draw9patch/src/resources/images/drop.png |