Core Java Technologies Tech Tips Tips, Techniques, and Sample Code Here is one solution to the challenge presented in the Core Java(tm) Technologies Tech Tips for December 23, 2003. package project; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.imageio.*; import java.io.*; import java.awt.image.*; import java.util.ResourceBundle; public class DrawJ extends JFrame { static ResourceBundle res = ResourceBundle.getBundle("project.Res"); JPanel contentPane; JMenuBar menuBar = new JMenuBar(); JMenu fileMenu = new JMenu(); JMenuItem exitSystem = new JMenuItem(); JToolBar toolbar = new JToolBar(); JToggleButton drawRect = new JToggleButton(); JToggleButton fillRect = new JToggleButton(); JToggleButton drawOval = new JToggleButton(); JToggleButton fillOval = new JToggleButton(); JToggleButton selectArea = new JToggleButton(); JToggleButton drawLine = new JToggleButton(); JToggleButton normal = new JToggleButton(); BorderLayout borderLayout1 = new BorderLayout(); JMenuItem newCanvas = new JMenuItem(); JMenuItem saveFile = new JMenuItem(); JMenu editMenu = new JMenu(); JMenu attributeMenu = new JMenu(); JMenuItem setBackground = new JMenuItem(); JMenuItem setForeground = new JMenuItem(); JMenuItem setStrokeSize = new JMenuItem(); JMenuItem clear = new JMenuItem(); JMenuItem rotate = new JMenuItem(); DrawingCanvas canvas = new DrawingCanvas(640, 480); JScrollPane scrollPane = new JScrollPane(); ButtonGroup actionButtonGroup = new ButtonGroup(); JMenuItem horizontalFlip = new JMenuItem(); JMenuItem verticalFlip = new JMenuItem(); public DrawJ() { setDefaultCloseOperation(EXIT_ON_CLOSE); contentPane = (JPanel) this.getContentPane(); contentPane.setLayout(borderLayout1); this.setTitle(res.getString("DrawJ")); fileMenu.setMnemonic( res.getString("File_mnemonic").charAt(0)); fileMenu.setText(res.getString("File")); exitSystem.setMnemonic( res.getString("Exit_mnemonic").charAt(0)); exitSystem.setText(res.getString("Exit")); exitSystem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { exitSystem_actionPerformed(e); } }); normal.setSelected(true); normal.setText(res.getString("Normal")); normal.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { normal_actionPerformed(e); } }); drawLine.setText(res.getString("Draw_Line")); drawLine.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { drawLine_actionPerformed(e); } }); drawRect.setText(res.getString("Draw_Rect")); drawRect.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { drawRect_actionPerformed(e); } }); fillRect.setText(res.getString("Fill_Rect")); fillRect.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { fillRect_actionPerformed(e); } }); drawOval.setText(res.getString("Draw_Oval")); drawOval.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { drawOval_actionPerformed(e); } }); fillOval.setText(res.getString("Fill_Oval")); fillOval.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { fillOval_actionPerformed(e); } }); selectArea.setText(res.getString("Select_Area")); selectArea.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { selectArea_actionPerformed(e); } }); newCanvas.setMnemonic( res.getString("New_mnemonic").charAt(0)); newCanvas.setText(res.getString("New")); newCanvas.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { newCanvas_actionPerformed(e); } }); saveFile.setMnemonic( res.getString("Save_mnemonic").charAt(0)); saveFile.setText(res.getString("Save")); saveFile.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { saveFile_actionPerformed(e); } }); editMenu.setMnemonic( res.getString("Edit_mnemonic").charAt(0)); editMenu.setText(res.getString("Edit")); attributeMenu.setMnemonic( res.getString("Attributes_mnemonic").charAt(0)); attributeMenu.setText(res.getString("Attributes")); setBackground.setMnemonic( res.getString("Background_mnemonic").charAt(0)); setBackground.setText(res.getString("Background")); setBackground.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { setBackground_actionPerformed(e); } }); setForeground.setMnemonic( res.getString("Foreground_mnemonic").charAt(0)); setForeground.setText( res.getString("Foreground")); setForeground.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { setForeground_actionPerformed(e); } }); setStrokeSize.setMnemonic( res.getString("Stroke_Size_mnemonic").charAt(0)); setStrokeSize.setText(res.getString("Stroke_Size")); setStrokeSize.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { setStrokeSize_actionPerformed(e); } }); clear.setMnemonic( res.getString("Clear_mnemonic").charAt(0)); clear.setText(res.getString("Clear")); clear.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { clear_actionPerformed(e); } }); rotate.setText(res.getString("Rotate")); rotate.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { rotate_actionPerformed(e); } }); rotate.setMnemonic( res.getString("Rotate_mnemonic").charAt(0)); scrollPane.setViewportView(canvas); horizontalFlip.setMnemonic( res.getString("Horizontal_Flip_mnemonic").charAt(0)); horizontalFlip.setText(res.getString("Horizontal_Flip")); horizontalFlip.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { horizontalFlip_actionPerformed(e); } }); verticalFlip.setMnemonic( res.getString("Vertical_Flip_mnemonic").charAt(0)); verticalFlip.setText(res.getString("Vertical_Flip")); verticalFlip.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { verticalFlip_actionPerformed(e); } }); contentPane.add(scrollPane, BorderLayout.CENTER); toolbar.add(normal); toolbar.add(drawLine); toolbar.add(drawRect); toolbar.add(fillRect); toolbar.add(drawOval); toolbar.add(fillOval); toolbar.add(selectArea); fileMenu.add(newCanvas); fileMenu.add(saveFile); fileMenu.add(exitSystem); menuBar.add(fileMenu); menuBar.add(editMenu); menuBar.add(attributeMenu); this.setJMenuBar(menuBar); contentPane.add(toolbar, BorderLayout.NORTH); attributeMenu.add(setBackground); attributeMenu.add(setForeground); attributeMenu.add(setStrokeSize); editMenu.add(clear); editMenu.add(rotate); editMenu.add(horizontalFlip); editMenu.add(verticalFlip); actionButtonGroup.add(normal); actionButtonGroup.add(drawLine); actionButtonGroup.add(drawRect); actionButtonGroup.add(fillRect); actionButtonGroup.add(drawOval); actionButtonGroup.add(fillOval); actionButtonGroup.add(selectArea); } void exitSystem_actionPerformed(ActionEvent e) { System.exit(0); } void normal_actionPerformed(ActionEvent e) { canvas.setMode(DrawingCanvas.MODE_NORMAL); } void drawLine_actionPerformed(ActionEvent e) { canvas.setMode(DrawingCanvas.MODE_LINE); } void drawRect_actionPerformed(ActionEvent e) { canvas.setMode(DrawingCanvas.MODE_DRAWRECT); } void fillRect_actionPerformed(ActionEvent e) { canvas.setMode(DrawingCanvas.MODE_FILLRECT); } void drawOval_actionPerformed(ActionEvent e) { canvas.setMode(DrawingCanvas.MODE_DRAWOVAL); } void fillOval_actionPerformed(ActionEvent e) { canvas.setMode(DrawingCanvas.MODE_FILLOVAL); } void selectArea_actionPerformed(ActionEvent e) { canvas.setMode(DrawingCanvas.MODE_SELECT); } void newCanvas_actionPerformed(ActionEvent e) { canvas.clear(); } void setBackground_actionPerformed(ActionEvent e) { Color newBackground = JColorChooser.showDialog( canvas, res.getString("Set_Background"), canvas.getBackground()); if (newBackground != null) { canvas.setBackground(newBackground); } } void setForeground_actionPerformed(ActionEvent e) { Color newForeground = JColorChooser.showDialog( canvas, res.getString("Set_Foreground"), canvas.getForeground()); if (newForeground != null) { canvas.setForeground(newForeground); } } void setStrokeSize_actionPerformed(ActionEvent e) { Object[] possibleValues = { "1", "1.5", "2", "2.5", "3", "3.5", "4", "4.5", "5" }; Object selectedValue = JOptionPane.showInputDialog( canvas, res.getString("Choose_stroke_size"), res.getString("Stroke_Size"), JOptionPane.INFORMATION_MESSAGE, null, possibleValues, possibleValues[0]); if (selectedValue != null) { float size = Float.parseFloat((String)selectedValue); canvas.setStrokeSize(size); } } void saveFile_actionPerformed(ActionEvent e) { JFileChooser chooser = new JFileChooser("."); int status = chooser.showSaveDialog(canvas); if (status == JFileChooser.APPROVE_OPTION) { final File file = chooser.getSelectedFile(); // Get canvas size Dimension dim = canvas.getPreferredSize(); // Convert into RenderedImage (BufferedImage) final BufferedImage image = new BufferedImage( dim.width, dim.height, BufferedImage.TYPE_INT_RGB); Graphics bufferedGraphics = image.getGraphics(); canvas.paint(bufferedGraphics); new Thread() { public void run() { try { // Save as JPEG ImageIO.write(image, "jpg", file); } catch (Exception ex) { JOptionPane.showMessageDialog( DrawJ.this, res.getString("Error_Saving"), res.getString("Save_Error"), JOptionPane.WARNING_MESSAGE); } } }.start(); } } void clear_actionPerformed(ActionEvent e) { canvas.clearArea(); } void rotate_actionPerformed(ActionEvent e) { Object[] possibleValues = { "45", "90", "135", "180", "225", "270", "315" }; Object selectedValue = JOptionPane.showInputDialog( canvas, res.getString("Choose_angle"), res.getString("Rotate_Angle"), JOptionPane.INFORMATION_MESSAGE, null, possibleValues, possibleValues[0]); if (selectedValue != null) { int angle = Integer.parseInt((String)selectedValue); canvas.rotate(angle); } } void horizontalFlip_actionPerformed(ActionEvent e) { canvas.horizontalFlip(); } void verticalFlip_actionPerformed(ActionEvent e) { canvas.verticalFlip(); } private static class FrameShower implements Runnable { final Frame frame; public FrameShower(Frame frame) { this.frame = frame; } public void run() { frame.show(); } } public static void main(String args[]) { JFrame frame = new DrawJ(); frame.setSize(500, 400); frame.setLocationRelativeTo(null); Runnable runner = new FrameShower(frame); EventQueue.invokeLater(runner); } } package project; import javax.swing.*; import java.awt.event.*; import javax.swing.event.*; import java.awt.*; import java.awt.image.BufferedImage; import java.awt.geom.AffineTransform; import java.awt.image.CropImageFilter; public class DrawingCanvas extends JComponent { public static final int MODE_NORMAL = 0; public static final int MODE_LINE = 1; public static final int MODE_DRAWRECT = 2; public static final int MODE_FILLRECT = 3; public static final int MODE_DRAWOVAL = 4; public static final int MODE_FILLOVAL = 5; public static final int MODE_SELECT = 6; private BufferedImage buffer; private Dimension oldSize; private Rectangle selectedArea; private int lastX, lastY; private int currentMode = MODE_NORMAL; private Stroke stroke; public DrawingCanvas(int width, int height) { setPreferredSize(new Dimension(width, height)); setBackground(Color.WHITE); setForeground(Color.BLACK); setStrokeSize(1); setOpaque(true); MouseInputAdapter listener = new MouseInputAdapter() { public void mousePressed(MouseEvent e) { lastX = e.getX(); lastY = e.getY(); } public void mouseDragged(MouseEvent e) { Graphics g; int newX = e.getX(); int newY = e.getY(); switch (currentMode) { case MODE_NORMAL: default: g = buffer.getGraphics(); Graphics2D g2d = (Graphics2D)g; g2d.setColor(getForeground()); g2d.setStroke(stroke); g2d.drawLine(lastX, lastY, newX, newY); lastX = newX; lastY = newY; repaint(); g.dispose(); break; case MODE_LINE: g = getGraphics(); g.drawImage(buffer, 0, 0, DrawingCanvas.this); g.setColor(Color.RED); g.drawLine(lastX, lastY, newX, newY); g.dispose(); break; case MODE_DRAWRECT: g = getGraphics(); g.drawImage(buffer, 0, 0, DrawingCanvas.this); g.setColor(Color.RED); g.drawRect(Math.min(lastX, newX), Math.min(lastY, newY), Math.abs(lastX - newX), Math.abs(lastY - newY)); g.dispose(); break; case MODE_FILLRECT: g = getGraphics(); g.drawImage(buffer, 0, 0, DrawingCanvas.this); g.setColor(Color.RED); g.fillRect(Math.min(lastX, newX), Math.min(lastY, newY), Math.abs(lastX - newX), Math.abs(lastY - newY)); g.dispose(); break; case MODE_DRAWOVAL: g = getGraphics(); g.drawImage(buffer, 0, 0, DrawingCanvas.this); g.setColor(Color.RED); g.drawOval(Math.min(lastX, newX), Math.min(lastY, newY), Math.abs(lastX - newX), Math.abs(lastY - newY)); g.dispose(); break; case MODE_FILLOVAL: g = getGraphics(); g.drawImage(buffer, 0, 0, DrawingCanvas.this); g.setColor(Color.RED); g.fillOval(Math.min(lastX, newX), Math.min(lastY, newY), Math.abs(lastX - newX), Math.abs(lastY - newY)); g.dispose(); break; case MODE_SELECT: g = getGraphics(); g.drawImage(buffer, 0, 0, DrawingCanvas.this); g.setColor(Color.RED); g.drawRect(Math.min(lastX, newX), Math.min(lastY, newY), Math.abs(lastX - newX), Math.abs(lastY - newY)); g.dispose(); break; } } public void mouseReleased(MouseEvent e) { Graphics2D g = (Graphics2D)buffer.getGraphics(); g.setColor(getForeground()); g.setStroke(stroke); int newX = e.getX(); int newY = e.getY(); switch (currentMode) { case MODE_NORMAL: default: break; case MODE_LINE: g.drawLine(lastX, lastY, newX, newY); break; case MODE_DRAWRECT: g.drawRect(Math.min(lastX, newX), Math.min(lastY, newY), Math.abs(lastX - newX), Math.abs(lastY - newY)); break; case MODE_FILLRECT: g.fillRect(Math.min(lastX, newX), Math.min(lastY, newY), Math.abs(lastX - newX), Math.abs(lastY - newY)); break; case MODE_DRAWOVAL: g.drawOval(Math.min(lastX, newX), Math.min(lastY, newY), Math.abs(lastX - newX), Math.abs(lastY - newY)); break; case MODE_FILLOVAL: g.fillOval(Math.min(lastX, newX), Math.min(lastY, newY), Math.abs(lastX - newX), Math.abs(lastY - newY)); break; case MODE_SELECT: selectedArea = new Rectangle( Math.min(lastX, newX), Math.min(lastY, newY), Math.abs(lastX - newX), Math.abs(lastY - newY)); break; } // +1 width/height to clear temporary // bounding rect repaint(Math.min(lastX, newX), Math.min(lastY, newY), Math.abs(lastX - newX)+1, Math.abs(lastY - newY)+1); g.dispose(); } }; addMouseListener(listener); addMouseMotionListener(listener); } public void paintComponent(Graphics g) { if (oldSize == null) { oldSize = getSize(); int width = getWidth(); int height = getHeight(); buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics bg = buffer.getGraphics(); bg.setColor(getBackground()); bg.fillRect(0, 0, width, height); bg.dispose(); } g.drawImage(buffer, 0, 0, this); if ((currentMode == MODE_SELECT) && (selectedArea != null)) { g.setColor(Color.RED); g.drawRect(selectedArea.x, selectedArea.y, selectedArea.width, selectedArea.height); } } public void setStrokeSize(float strokeSize) { stroke = new BasicStroke(strokeSize); } public void setMode(int mode) { this.currentMode = mode; } public void clear() { oldSize = null; repaint(); } public void clearArea() { if ((buffer != null) && (selectedArea != null)) { Graphics g = buffer.getGraphics(); g.setColor(getBackground()); g.fillRect(selectedArea.x, selectedArea.y, selectedArea.width, selectedArea.height); // +1 width/height to clear // temporary bounding rect repaint(selectedArea.x, selectedArea.y, selectedArea.width+1, selectedArea.height+1); selectedArea = null; g.dispose(); } } public void rotate(int angle) { if ((buffer != null) && (selectedArea != null)) { // Get sub image BufferedImage subImage = buffer.getSubimage( selectedArea.x, selectedArea.y, selectedArea.width, selectedArea.height); Graphics2D g = (Graphics2D)buffer.getGraphics(); // Rotate AffineTransform transform = AffineTransform.getRotateInstance( Math.toRadians(angle), selectedArea.getCenterX(), selectedArea.getCenterY()); g.setTransform(transform); g.drawImage(subImage, selectedArea.x, selectedArea.y, this); selectedArea = null; g.dispose(); repaint(); } } public void horizontalFlip() { if ((buffer != null) && (selectedArea != null)) { Graphics g = buffer.getGraphics(); g.drawImage(buffer, selectedArea.x + selectedArea.width, selectedArea.y, selectedArea.x, selectedArea.y + selectedArea.height, selectedArea.x, selectedArea.y, selectedArea.x + selectedArea.width, selectedArea.y + selectedArea.height, this); // +1 width/height to clear // temporary bounding rect repaint(selectedArea.x, selectedArea.y, selectedArea.width+1, selectedArea.height+1); selectedArea = null; g.dispose(); } } public void verticalFlip() { if ((buffer != null) && (selectedArea != null)) { Graphics g = buffer.getGraphics(); g.drawImage(buffer, selectedArea.x, selectedArea.y + selectedArea.height, selectedArea.x + selectedArea.width, selectedArea.y, selectedArea.x, selectedArea.y, selectedArea.x + selectedArea.width, selectedArea.y + selectedArea.height, this); // +1 width/height to clear // temporary bounding rect repaint(selectedArea.x, selectedArea.y, selectedArea.width+1, selectedArea.height+1); selectedArea = null; g.dispose(); } } } package project; import java.util.*; public class Res extends ListResourceBundle { private static final Object[][] contents = new String[][]{ { "DrawJ", "DrawJ" }, { "File_mnemonic", "F" }, { "File", "File" }, { "Exit_mnemonic", "X" }, { "Exit", "Exit" }, { "Normal", "Normal" }, { "Draw_Line", "Draw Line" }, { "Draw_Rect", "Draw Rect" }, { "Fill_Rect", "Fill Rect" }, { "Draw_Oval", "Draw Oval" }, { "Fill_Oval", "Fill Oval" }, { "Select_Area", "Select Area" }, { "New_mnemonic", "N" }, { "New", "New" }, { "Save_mnemonic", "S" }, { "Save", "Save" }, { "Edit_mnemonic", "E" }, { "Edit", "Edit" }, { "Attributes_mnemonic", "A" }, { "Attributes", "Attributes" }, { "Background_mnemonic", "B" }, { "Background", "Background" }, { "Foreground_mnemonic", "F" }, { "Foreground", "Foreground" }, { "Stroke_Size_mnemonic", "S" }, { "Stroke_Size", "Stroke Size" }, { "Clear_mnemonic", "L" }, { "Clear", "Clear" }, { "Rotate", "Rotate" }, { "Rotate_mnemonic", "R" }, { "Horizontal_Flip_mnemonic", "H" }, { "Horizontal_Flip", "Horizontal Flip" }, { "Vertical_Flip_mnemonic", "V" }, { "Vertical_Flip", "Vertical Flip" }, { "Set_Background", "Set Background" }, { "Set_Foreground", "Set Foreground" }, { "Choose_stroke_size", "Choose stroke size" }, { "Save_Error", "Save Error" }, { "Error_Saving", "Error Saving" }, { "Rotate_Angle", "Rotate Angle" }, { "Choose_angle", "Choose angle" }}; public Object[][] getContents() { return contents; } }