001: /*
002: * Copyright (c) 2005-2008 Substance Kirill Grouchnikov. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of Substance Kirill Grouchnikov nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030: package test;
031:
032: import java.awt.*;
033: import java.awt.event.*;
034: import java.awt.geom.GeneralPath;
035: import java.awt.geom.Point2D;
036: import java.awt.image.BufferedImage;
037: import java.io.*;
038: import java.util.ArrayList;
039:
040: import javax.imageio.ImageIO;
041: import javax.swing.*;
042: import javax.swing.border.TitledBorder;
043:
044: import org.jvnet.substance.SubstanceLookAndFeel;
045: import org.jvnet.substance.shaperpack.CanonicalPath;
046: import org.jvnet.substance.shaperpack.ShaperRepository;
047:
048: public class ShapeEditor extends JFrame {
049: private ArrayList<Point2D> majorPoints;
050:
051: private ArrayList<Point2D> minorPoints;
052:
053: private boolean isInEditMajorMode;
054:
055: private boolean isShowImage;
056:
057: private boolean isShowPath;
058:
059: private JButton loadImageButton;
060:
061: private JButton loadContourButton = new JButton("Load contour");
062:
063: private JCheckBox showImageCB;
064:
065: private JCheckBox showPathCB;
066:
067: private JRadioButton editMajorRB;
068:
069: private JRadioButton editMinorRB;
070:
071: private JRadioButton addModeRB;
072:
073: private JRadioButton deleteModeRB;
074:
075: private JRadioButton editModeRB;
076:
077: private JButton saveContourButton;
078:
079: private JButton cancelButton;
080:
081: private ShapePanel shapePanel;
082:
083: private static class ShapePanel extends JPanel {
084: private BufferedImage image;
085:
086: private ShapeEditor editorFrame;
087:
088: private int selectedPointIndex;
089:
090: public ShapePanel(final ShapeEditor editorFrame) {
091: this .editorFrame = editorFrame;
092: this .selectedPointIndex = -1;
093:
094: this .addMouseListener(new MouseAdapter() {
095: @Override
096: public void mousePressed(MouseEvent e) {
097: if (editorFrame.addModeRB.isSelected()) {
098: if (selectedPointIndex >= 0) {
099: // add new major point at click
100: ArrayList<Point2D> major = editorFrame.majorPoints;
101: ArrayList<Point2D> minor = editorFrame.minorPoints;
102: Point2D newMajor = new Point2D.Double(
103: (double) e.getX() / getWidth(),
104: (double) e.getY() / getHeight());
105: double selectedX = major.get(
106: selectedPointIndex).getX();
107: double selectedY = major.get(
108: selectedPointIndex).getY();
109: Point2D newMinor = new Point2D.Double(
110: (getWidth() * selectedX + e.getX())
111: / (2.0 * getWidth()),
112: (getHeight() * selectedY + e.getY())
113: / (2.0 * getHeight()));
114: major.add(selectedPointIndex + 1, newMajor);
115: minor.add(selectedPointIndex, newMinor);
116: selectedPointIndex++;
117:
118: Point2D majorNext = (selectedPointIndex == (major
119: .size() - 1)) ? major.get(0)
120: : major.get(selectedPointIndex + 1);
121: minor.get(selectedPointIndex).setLocation(
122: 0.5 * (newMajor.getX() + majorNext
123: .getX()),
124: 0.5 * (newMajor.getY() + majorNext
125: .getY()));
126:
127: editorFrame.repaint();
128: return;
129: }
130: }
131:
132: int majorIndex = getMajorPointIndex(e.getPoint());
133: if (majorIndex >= 0) {
134: if (editorFrame.deleteModeRB.isSelected()) {
135: if (editorFrame.majorPoints.size() > 1) {
136: // delete major and previous minor
137: editorFrame.majorPoints
138: .remove(majorIndex);
139: Point2D minorToRemove = null;
140: if (majorIndex != 0) {
141: minorToRemove = editorFrame.minorPoints
142: .get(majorIndex - 1);
143: } else {
144: minorToRemove = editorFrame.minorPoints
145: .get(editorFrame.minorPoints
146: .size() - 1);
147: }
148: double newMinorX = 0.5 * (minorToRemove
149: .getX() + editorFrame.minorPoints
150: .get(majorIndex).getX());
151: double newMinorY = 0.5 * (minorToRemove
152: .getY() + editorFrame.minorPoints
153: .get(majorIndex).getY());
154: editorFrame.minorPoints.get(majorIndex)
155: .setLocation(newMinorX,
156: newMinorY);
157: editorFrame.minorPoints
158: .remove(minorToRemove);
159: if (majorIndex == 0) {
160: editorFrame.minorPoints
161: .add(editorFrame.minorPoints
162: .remove(0));
163: }
164: selectedPointIndex = -1;
165: }
166: } else {
167: selectedPointIndex = majorIndex;
168: editorFrame.editMajorRB.setSelected(true);
169: editorFrame.isInEditMajorMode = true;
170: }
171: editorFrame.repaint();
172: return;
173: }
174: if (!editorFrame.addModeRB.isSelected()) {
175: int minorIndex = getMinorPointIndex(e
176: .getPoint());
177: if (minorIndex >= 0) {
178: selectedPointIndex = minorIndex;
179: editorFrame.editMinorRB.setSelected(true);
180: editorFrame.isInEditMajorMode = false;
181: editorFrame.repaint();
182: return;
183: }
184: }
185: selectedPointIndex = -1;
186: editorFrame.repaint();
187: }
188: });
189:
190: this .addMouseMotionListener(new MouseMotionAdapter() {
191: @Override
192: public void mouseDragged(MouseEvent e) {
193: if (!editorFrame.editModeRB.isSelected())
194: return;
195:
196: if (selectedPointIndex < 0)
197: return;
198:
199: double newX = (double) e.getX() / getWidth();
200: double newY = (double) e.getY() / getHeight();
201:
202: if (editorFrame.isInEditMajorMode) {
203: editorFrame.majorPoints.get(selectedPointIndex)
204: .setLocation(newX, newY);
205: } else {
206: editorFrame.minorPoints.get(selectedPointIndex)
207: .setLocation(newX, newY);
208: }
209: editorFrame.repaint();
210: }
211: });
212: }
213:
214: public Point2D translate(Point2D relative) {
215: return new Point2D.Double(
216: relative.getX() * this .getWidth(), relative.getY()
217: * this .getHeight());
218: }
219:
220: public int getMajorPointIndex(Point point) {
221: for (int i = 0; i < this .editorFrame.majorPoints.size(); i++) {
222: Point2D majorPoint = this .editorFrame.majorPoints
223: .get(i);
224: if (point.distance(this .translate(majorPoint)) < 3.0) {
225: return i;
226: }
227: }
228: return -1;
229: }
230:
231: public int getMinorPointIndex(Point point) {
232: for (int i = 0; i < this .editorFrame.minorPoints.size(); i++) {
233: Point2D minorPoint = this .editorFrame.minorPoints
234: .get(i);
235: if (point.distance(this .translate(minorPoint)) < 3.0) {
236: return i;
237: }
238: }
239: return -1;
240: }
241:
242: @Override
243: public void paint(Graphics g) {
244: Graphics2D graphics = (Graphics2D) g.create();
245: graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
246: RenderingHints.VALUE_ANTIALIAS_ON);
247:
248: int width = this .getWidth();
249: int height = this .getHeight();
250: graphics.setFont(new Font("Tahoma", Font.PLAIN, 10));
251:
252: // image
253: if ((this .image != null) && (this .editorFrame.isShowImage)) {
254: graphics.drawImage(this .image, 0, 0, width, height, 0,
255: 0, this .image.getWidth(), this .image
256: .getHeight(), null);
257: }
258:
259: // grid
260: graphics.setColor(new Color(0, 0, 0, 128));
261: graphics.setStroke(new BasicStroke(1.0f,
262: BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL,
263: 1.0f, new float[] { 3.0f, 2.0f }, 0.0f));
264: for (int i = 0; i <= 10; i++) {
265: int x = i * width / 10;
266: graphics.drawLine(x, 0, x, height);
267: int y = i * height / 10;
268: graphics.drawLine(0, y, width, y);
269: }
270:
271: graphics.setStroke(new BasicStroke(1.0f));
272: // points
273: for (int i = 0; i < this .editorFrame.majorPoints.size(); i++) {
274: Point2D majorPoint = this
275: .translate(this .editorFrame.majorPoints.get(i));
276: int x = (int) majorPoint.getX();
277: int y = (int) majorPoint.getY();
278: this .paintOval(graphics, x, y, 3, Color.black,
279: new Color(0, 128, 255));
280:
281: boolean isSelected = this .editorFrame.isInEditMajorMode
282: && (i == this .selectedPointIndex);
283: if (isSelected) {
284: this .paintOval(graphics, x, y - 4, 1, Color.green,
285: new Color(0, 128, 0));
286: this .paintOval(graphics, x - 4, y, 1, Color.green,
287: new Color(0, 128, 0));
288: this .paintOval(graphics, x, y + 4, 1, Color.green,
289: new Color(0, 128, 0));
290: this .paintOval(graphics, x + 4, y, 1, Color.green,
291: new Color(0, 128, 0));
292: }
293:
294: graphics.setColor(Color.black);
295: graphics.drawString("" + i, x + 5, y);
296: }
297:
298: for (int i = 0; i < this .editorFrame.minorPoints.size(); i++) {
299: Point2D minorPoint = this
300: .translate(this .editorFrame.minorPoints.get(i));
301: int x = (int) minorPoint.getX();
302: int y = (int) minorPoint.getY();
303: this .paintOval(graphics, x, y, 3, Color.black,
304: new Color(128, 255, 255));
305:
306: boolean isSelected = (!this .editorFrame.isInEditMajorMode)
307: && (i == this .selectedPointIndex);
308: if (isSelected) {
309: this .paintOval(graphics, x, y - 4, 1, Color.green,
310: new Color(0, 128, 0));
311: this .paintOval(graphics, x - 4, y, 1, Color.green,
312: new Color(0, 128, 0));
313: this .paintOval(graphics, x, y + 4, 1, Color.green,
314: new Color(0, 128, 0));
315: this .paintOval(graphics, x + 4, y, 1, Color.green,
316: new Color(0, 128, 0));
317: }
318:
319: graphics.setColor(Color.black);
320: graphics.drawString("" + i, x + 5, y);
321: }
322:
323: // path
324: if (this .editorFrame.isShowPath) {
325: CanonicalPath canonical = new CanonicalPath(
326: this .editorFrame.majorPoints,
327: this .editorFrame.minorPoints, this .getRatio());
328: GeneralPath path = canonical.getPath(width, height,
329: null);
330:
331: graphics.setColor(new Color(96, 0, 0, 200));
332: graphics.setStroke(new BasicStroke(1.4f));
333: graphics.draw(path);
334: }
335:
336: graphics.dispose();
337: }
338:
339: private void paintOval(Graphics2D graphics, int x, int y,
340: int radius, Color outer, Color inner) {
341: graphics.setColor(inner);
342: graphics.fillOval(x - radius, y - radius, 2 * radius,
343: 2 * radius);
344: graphics.setColor(outer);
345: graphics.drawOval(x - radius, y - radius, 2 * radius,
346: 2 * radius);
347:
348: }
349:
350: public double getRatio() {
351: if (this .image == null)
352: return 1.0;
353: return (double) this .image.getWidth()
354: / (double) this .image.getHeight();
355: }
356: }
357:
358: public ShapeEditor() {
359: this .loadImageButton = new JButton("Load image");
360: this .loadContourButton = new JButton("Load contour");
361: this .showImageCB = new JCheckBox("Show image");
362: this .showPathCB = new JCheckBox("Show path");
363: this .editMajorRB = new JRadioButton("Edit major points");
364: this .editMinorRB = new JRadioButton("Edit minor points");
365: this .addModeRB = new JRadioButton("Add mode");
366: this .deleteModeRB = new JRadioButton("Delete mode");
367: this .editModeRB = new JRadioButton("Edit mode");
368: this .saveContourButton = new JButton("Save contour");
369: this .cancelButton = new JButton("Cancel");
370:
371: ButtonGroup editGroup = new ButtonGroup();
372: editGroup.add(this .editMajorRB);
373: editGroup.add(this .editMinorRB);
374:
375: ButtonGroup addDeleteGroup = new ButtonGroup();
376: addDeleteGroup.add(this .addModeRB);
377: addDeleteGroup.add(this .deleteModeRB);
378: addDeleteGroup.add(this .editModeRB);
379:
380: this .editMajorRB.setSelected(true);
381: this .addModeRB.setSelected(true);
382:
383: this .showPathCB.setSelected(true);
384: this .isShowPath = true;
385: this .showImageCB.setSelected(true);
386: this .isShowImage = true;
387: this .showImageCB.setEnabled(false);
388:
389: this .loadImageButton.addActionListener(new ActionListener() {
390: public void actionPerformed(ActionEvent e) {
391: JFileChooser chooser = new JFileChooser();
392: chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
393: // modalize on the main frame
394: int returnVal = chooser
395: .showOpenDialog(ShapeEditor.this );
396: if (returnVal == JFileChooser.APPROVE_OPTION) {
397: File selected = chooser.getSelectedFile();
398: try {
399: BufferedImage image = ImageIO.read(selected);
400: shapePanel.image = image;
401: showImageCB.setEnabled(true);
402: } catch (Exception exc) {
403: shapePanel.image = null;
404: showImageCB.setEnabled(false);
405: }
406: repaint();
407: }
408: }
409: });
410:
411: this .loadContourButton.addActionListener(new ActionListener() {
412: public void actionPerformed(ActionEvent e) {
413: JFileChooser chooser = new JFileChooser();
414: chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
415: // modalize on the main frame
416: int returnVal = chooser
417: .showOpenDialog(ShapeEditor.this );
418: if (returnVal == JFileChooser.APPROVE_OPTION) {
419: File selected = chooser.getSelectedFile();
420: try {
421: CanonicalPath path = ShaperRepository
422: .read(new FileInputStream(selected));
423: if (path != null) {
424: majorPoints = path.getMajorPoints();
425: minorPoints = path.getMinorPoints();
426: }
427: } catch (Exception exc) {
428: }
429: repaint();
430: }
431: }
432: });
433:
434: this .saveContourButton.addActionListener(new ActionListener() {
435: public void actionPerformed(ActionEvent e) {
436: JFileChooser chooser = new JFileChooser();
437: chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
438: // modalize on the main frame
439: int returnVal = chooser
440: .showOpenDialog(ShapeEditor.this );
441: if (returnVal == JFileChooser.APPROVE_OPTION) {
442: File selected = chooser.getSelectedFile();
443: try {
444: CanonicalPath path = new CanonicalPath(
445: majorPoints, minorPoints, shapePanel
446: .getRatio());
447: ShaperRepository.write(new FileOutputStream(
448: selected), path);
449: } catch (Exception exc) {
450: }
451: repaint();
452: }
453: }
454: });
455:
456: this .showImageCB.addActionListener(new ActionListener() {
457: public void actionPerformed(ActionEvent e) {
458: isShowImage = showImageCB.isSelected();
459: repaint();
460: }
461: });
462:
463: this .showPathCB.addActionListener(new ActionListener() {
464: public void actionPerformed(ActionEvent e) {
465: isShowPath = showPathCB.isSelected();
466: repaint();
467: }
468: });
469:
470: this .addModeRB.addActionListener(new ActionListener() {
471: public void actionPerformed(ActionEvent e) {
472: editMajorRB.setSelected(true);
473: isInEditMajorMode = true;
474: repaint();
475: }
476: });
477:
478: JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
479: topPanel.add(this .loadImageButton);
480: topPanel.add(this .loadContourButton);
481:
482: JPanel midPanel = new JPanel(new GridLayout(3, 1));
483: JPanel showPanel = new JPanel(new GridLayout(2, 1));
484: showPanel.add(this .showImageCB);
485: showPanel.add(this .showPathCB);
486: showPanel.setBorder(new TitledBorder("Show controls"));
487: midPanel.add(showPanel);
488:
489: JPanel editModePanel = new JPanel(new GridLayout(2, 1));
490: editModePanel.add(this .editMajorRB);
491: editModePanel.add(this .editMinorRB);
492: editModePanel.setBorder(new TitledBorder("Major / minor mode"));
493: midPanel.add(editModePanel);
494:
495: JPanel addDeleteModePanel = new JPanel(new GridLayout(3, 1));
496: addDeleteModePanel.add(this .addModeRB);
497: addDeleteModePanel.add(this .deleteModeRB);
498: addDeleteModePanel.add(this .editModeRB);
499: addDeleteModePanel.setBorder(new TitledBorder("Work mode"));
500: midPanel.add(addDeleteModePanel);
501:
502: JPanel bottomPanel = new JPanel(new FlowLayout(
503: FlowLayout.CENTER));
504: bottomPanel.add(this .saveContourButton);
505: bottomPanel.add(this .cancelButton);
506:
507: JPanel controlPanel = new JPanel(new BorderLayout());
508: controlPanel.add(topPanel, BorderLayout.NORTH);
509: controlPanel.add(midPanel, BorderLayout.CENTER);
510: controlPanel.add(bottomPanel, BorderLayout.SOUTH);
511:
512: this .setLayout(new BorderLayout());
513: this .add(controlPanel, BorderLayout.WEST);
514:
515: this .shapePanel = new ShapePanel(this );
516: this .add(this .shapePanel, BorderLayout.CENTER);
517:
518: this .setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
519:
520: this .majorPoints = new ArrayList<Point2D>();
521: this .majorPoints.add(new Point2D.Double(0.5f, 0.5f));
522: this .minorPoints = new ArrayList<Point2D>();
523: this .minorPoints.add(new Point2D.Double(0.55f, 0.55f));
524: }
525:
526: public static void main(String... args) {
527: try {
528: UIManager.setLookAndFeel(new SubstanceLookAndFeel());
529: } catch (Exception exc) {
530: }
531:
532: ShapeEditor editor = new ShapeEditor();
533: editor.setPreferredSize(new Dimension(600, 400));
534: editor.setSize(editor.getPreferredSize());
535: editor.setLocation(200, 200);
536: editor.setVisible(true);
537: }
538:
539: }
|