/* * 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 java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.TexturePaint; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; import java.io.File; import java.io.IOException; import java.net.URL; 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.border.EmptyBorder; import javax.swing.event.AncestorEvent; import javax.swing.event.AncestorListener; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; public class ImageEditorPanel extends JPanel { private static final String EXTENSION_9PATCH = ".9.png"; private static final Color HELP_COLOR = new Color(0xffffe1); 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 JSlider zoomSlider; public ImageEditorPanel(MainFrame mainFrame, BufferedImage image, String name) { this.image = image; this.name = name; if (mainFrame != null) { setTransferHandler(new ImageTransferHandler(mainFrame)); } setOpaque(false); setLayout(new BorderLayout()); is9Patch = name.endsWith(EXTENSION_9PATCH); if (!is9Patch) { this.image = convertTo9Patch(image); this.name = name.substring(0, name.lastIndexOf('.')) + ".9.png"; } else { ensure9Patch(image); } loadSupport(); buildImageViewer(); buildStatusPanel(); addAncestorListener(new AncestorListener() { @Override public void ancestorAdded(AncestorEvent event) { } @Override public void ancestorRemoved(AncestorEvent event) { } @Override public void ancestorMoved(AncestorEvent event) { // allow the image viewer to set the optimal zoom level and ensure that the // zoom slider's setting is in sync with the image viewer's zoom removeAncestorListener(this); synchronizeImageViewerZoomLevel(); } }); } private void synchronizeImageViewerZoomLevel() { zoomSlider.setValue(viewer.getZoom()); } public ImageViewer getViewer() { return viewer; } 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() { JPanel panel = new JPanel(new BorderLayout()); panel.add(createHelpPanel(), BorderLayout.NORTH); 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); 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()); panel.add(splitter, BorderLayout.CENTER); add(panel); } private static Component createHelpPanel() { JPanel panel = new JPanel(new BorderLayout()); panel.setBorder(new EmptyBorder(0, 6, 0, 6)); panel.setBackground(HELP_COLOR); JLabel label = new JLabel("Press Control/Shift while dragging on the border to modify layout bounds."); label.putClientProperty("JComponent.sizeVariant", "small"); // Labels are not opaque by default, as a result, if there is not enough space, // the label will be painted over the button we add below. However, we still want the same // background as the panel, so we explicitly set that background as well // https://code.google.com/p/android/issues/detail?id=62576 label.setOpaque(true); label.setBackground(HELP_COLOR); panel.add(label, BorderLayout.WEST); return panel; } private JComponent buildStretchesViewer() { stretchesViewer = new StretchesViewer(this, viewer, texture); 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()); JLabel label = new JLabel(); 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.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)); zoomSlider = new JSlider(ImageViewer.MIN_ZOOM, ImageViewer.MAX_ZOOM, ImageViewer.DEFAULT_ZOOM); zoomSlider.putClientProperty("JComponent.sizeVariant", "small"); zoomSlider.addChangeListener(new ChangeListener() { @Override 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.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.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.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)); JSlider jSlider = new JSlider(200, 600, (int) (StretchesViewer.DEFAULT_SCALE * 100.0f)); jSlider.putClientProperty("JComponent.sizeVariant", "small"); jSlider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent evt) { stretchesViewer.setScale(((JSlider) evt.getSource()).getValue() / 100.0f); } }); status.add(jSlider, 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.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.setSelected(false); showLock.putClientProperty("JComponent.sizeVariant", "small"); showLock.addActionListener(new ActionListener() { @Override 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.putClientProperty("JComponent.sizeVariant", "small"); showPatches.addActionListener(new ActionListener() { @Override 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.putClientProperty("JComponent.sizeVariant", "small"); showPadding.addActionListener(new ActionListener() { @Override 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)); JCheckBox showBadPatches = new JCheckBox("Show bad patches"); showBadPatches.setOpaque(false); showBadPatches.putClientProperty("JComponent.sizeVariant", "small"); showBadPatches.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { viewer.setShowBadPatches(((JCheckBox) event.getSource()).isSelected()); } }); status.add(showBadPatches, new GridBagConstraints(5, 1, 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.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.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.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.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 static void ensure9Patch(BufferedImage image) { int width = image.getWidth(); int height = image.getHeight(); for (int i = 0; i < width; i++) { int pixel = image.getRGB(i, 0); if (pixel != 0 && pixel != PatchInfo.BLACK_TICK && pixel != PatchInfo.RED_TICK) { image.setRGB(i, 0, 0); } pixel = image.getRGB(i, height - 1); 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 != PatchInfo.BLACK_TICK && pixel != PatchInfo.RED_TICK) { image.setRGB(0, i, 0); } pixel = image.getRGB(width - 1, i); if (pixel != 0 && pixel != PatchInfo.BLACK_TICK && pixel != PatchInfo.RED_TICK) { image.setRGB(width - 1, i, 0); } } } private static BufferedImage convertTo9Patch(BufferedImage image) { BufferedImage buffer = GraphicsUtilities.createTranslucentCompatibleImage( image.getWidth() + 2, image.getHeight() + 2); Graphics2D g2 = buffer.createGraphics(); g2.drawImage(image, 1, 1, null); g2.dispose(); return buffer; } File chooseSaveFile() { if (is9Patch) { return new File(name); } else { JFileChooser chooser = new JFileChooser( name.substring(0, name.lastIndexOf(File.separatorChar))); 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; } public void dispose() { if (viewer != null) { viewer.dispose(); } } }