Search code examples
javaswinggraphics2dstroke

java Graphics2D set stroke width dynamically


I have this code for Java Paint App. I have added a slider that changes from 1 to 20 with a default size of 1.This slider is for changing the strokeSize dynamically,so i have created a variable strokeSizeVal and the slider + Listener for it and the new stroke size updates and works just fine, but it updates for all the shapes that are added in the array..Even for shapes that are already drawn. I save individual stroke weights in a multidimensional array along with the objects. How can i change this,so that it doesn’t update all the shapes in the array and is valid only for newly created shapes?

I guess this line makes the problem: graphSettings.setStroke(strokeSizeCounter.next());

thanks

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import java.awt.event.*;
import java.awt.*;
import java.awt.geom.*;
import java.text.DecimalFormat;
import java.util.*;

@SuppressWarnings("serial")
public class Lesson48 extends JFrame{
    
    JButton brushBut, lineBut, ellipseBut, rectBut, strokeBut, fillBut;
    
    JSlider transSlider;
    
    JLabel transLabel;
    
    DecimalFormat dec = new DecimalFormat("#.##");
    
    JSlider strokeSizeSlider;
    
    JLabel strokeSizeLabel;
    
    Graphics2D graphSettings;
    
    int strokeSize;
    
    //graphSettings.setStroke(new BasicStroke(5f));
    
    int currentAction = 1;
    
    float transparentVal = 1.0f;
    
    Color strokeColor = Color.BLACK, fillColor = Color.BLACK;
    
    public static void main(String[] args) {
        
        new Lesson48();
        
    }
    
    public Lesson48() {
        
        this.setSize(1000, 600);
        this.setTitle("Java Paint");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        JPanel buttonPanel = new JPanel();
        
        Box theBox = Box.createHorizontalBox();
        
        brushBut = makeMeButtons("./src/Brush.png", 1);
        lineBut = makeMeButtons("./src/Line.png", 2);
        ellipseBut = makeMeButtons("./src/Ellipse.png", 3);
        rectBut = makeMeButtons("./src/Rectangle.png", 4);
        
        strokeBut = makeMeColorButton("./src/Stroke.png", 5, true);
        fillBut = makeMeColorButton("./src/Fill.png", 6, false);
        
        theBox.add(brushBut);
        theBox.add(lineBut);
        theBox.add(ellipseBut);
        theBox.add(rectBut);
        theBox.add(strokeBut);
        theBox.add(fillBut);
        
        transLabel = new JLabel("  Transparent: 1");
        
        transSlider = new JSlider(1, 99, 99);
        
        ListenerForSlider lForSlider = new ListenerForSlider();
        
        transSlider.addChangeListener(lForSlider);
        
        theBox.add(transLabel);
        theBox.add(transSlider);
        
        // changing the strokeSize dynamically
        
        strokeSizeLabel = new JLabel("  Stroke: 1");
        
        strokeSizeSlider = new JSlider(1, 20, 1);
        
        ListenerForSlider2 sForSlider = new ListenerForSlider2();
        
        strokeSizeSlider.addChangeListener(sForSlider);
        
        theBox.add(strokeSizeLabel);
        theBox.add(strokeSizeSlider);
        
        buttonPanel.add(theBox);
        
        this.add(buttonPanel, BorderLayout.SOUTH);
        
        this.add(new DrawingBoard(), BorderLayout.CENTER);
        
        this.setVisible(true);
        
    }
    
    public JButton makeMeButtons(String iconFile, final int actionNum) {
        
        JButton theBut = new JButton();
        Icon butIcon = new ImageIcon(iconFile);
        theBut.setIcon(butIcon);
        
        theBut.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                currentAction = actionNum;
            }
            
        });
        
        return theBut;
        
    }
    
    public JButton makeMeColorButton(String iconFile, final int actionNum, final boolean stroke) {
        
        JButton theBut = new JButton();
        Icon butIcon = new ImageIcon(iconFile);
        theBut.setIcon(butIcon);
        
        theBut.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                if(stroke) {
                    strokeColor = JColorChooser.showDialog(null, "Pick a Stroke", Color.BLACK);
                } else {
                    fillColor = JColorChooser.showDialog(null, "Pick a Fill", Color.BLACK);
                }
            }
            
        });
        
        return theBut;
        
    }
    
    private class DrawingBoard extends JComponent{
        
        ArrayList<Shape> shapes = new ArrayList<Shape>();
        ArrayList<Color> shapeFill = new ArrayList<Color>();
        ArrayList<Color> shapeStroke = new ArrayList<Color>();
        
        ArrayList<Float> transPercent = new ArrayList<Float>();
        
        ArrayList<Stroke> shapeStrokeSize = new ArrayList<Stroke>();
        
        Point drawStart, drawEnd;
        
        public DrawingBoard() {
            
            this.addMouseListener(new MouseAdapter() {
                
                public void mousePressed(MouseEvent e) {
                    
                    if(currentAction != 1) {
                    
                    drawStart = new Point(e.getX(), e.getY());
                    drawEnd = drawStart;
                    repaint();
                    }
                }
                
                public void mouseReleased(MouseEvent e) {
                    
                    if(currentAction != 1) {
                        
                        Shape aShape = null;
                        
                        if(currentAction == 2) {
                            aShape = drawLine(drawStart.x, drawStart.y, e.getX(), e.getY());
                        } else
                            
                        if(currentAction == 3) {
                            aShape = drawEllipse(drawStart.x, drawStart.y, e.getX(), e.getY());
                        } else
                            
                        if(currentAction == 4) {
                            aShape = drawRectangle(drawStart.x, drawStart.y, e.getX(), e.getY());
                        }
                        
                        shapes.add(aShape);
                        shapeFill.add(fillColor);
                        shapeStroke.add(strokeColor);
                        
                        transPercent.add(transparentVal);
                        
                        //shapeStrokeSize.add(strokeSize);
                        
                        drawStart = null;
                        drawEnd = null;
                        repaint();
                    }
                    
                }
                
            });
            
            this.addMouseMotionListener(new MouseMotionAdapter() {
                
                public void mouseDragged(MouseEvent e) {
                    
                    if(currentAction == 1) {
                        
                        int x = e.getX();
                        int y = e.getY();
                        
                        Shape aShape = null;
                        
                        strokeColor = fillColor;
                        
                        aShape = drawBrush(x, y, 2, 2);
                        
                        shapes.add(aShape);
                        shapeFill.add(fillColor);
                        shapeStroke.add(strokeColor);
                        
                        transPercent.add(transparentVal);
                        
                        //shapeStrokeSize.add(strokeSize);
                        
                    }
                    
                    drawEnd = new Point(e.getX(), e.getY());
                    repaint();
                }
                
            });
            
        }
        
        public void paint(Graphics g) {
            
            graphSettings = (Graphics2D)g;
            
            graphSettings.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            
            graphSettings.setStroke(new BasicStroke(strokeSize));
            
            Iterator<Color> strokeCounter = shapeStroke.iterator();
            Iterator<Color> fillCounter = shapeFill.iterator();
            
            Iterator<Float> transCounter = transPercent.iterator();
            
            Iterator<Stroke> strokeSizeCounter = shapeStrokeSize.iterator();
            
            for(Shape s : shapes) {
                
                //graphSettings.setStroke(strokeSizeCounter.next());
                //graphSettings.draw(s);
                
                graphSettings.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, transCounter.next()));
                
                graphSettings.setPaint(strokeCounter.next());
                graphSettings.draw(s);
                graphSettings.setPaint(fillCounter.next());
                graphSettings.fill(s);
            }
            
            if(drawStart != null && drawEnd != null) {
                
                graphSettings.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4f));
                
                graphSettings.setPaint(Color.LIGHT_GRAY);
                
                Shape aShape = null;
                
                if(currentAction == 2) {
                    aShape = drawLine(drawStart.x, drawStart.y, drawEnd.x, drawEnd.y);
                } else
                    
                if(currentAction == 3) {
                    aShape = drawEllipse(drawStart.x, drawStart.y, drawEnd.x, drawEnd.y);
                } else
                    
                if(currentAction == 4) {
                    aShape = drawRectangle(drawStart.x, drawStart.y, drawEnd.x, drawEnd.y);
                }
                
                graphSettings.draw(aShape);
                
            }
            
        }
        
        private Rectangle2D.Float drawRectangle(int x1, int y1, int x2, int y2){
            
            int x = Math.min(x1, x2);
            int y = Math.min(y1, y2);
            
            int width = Math.abs(x1-x2);
            int height = Math.abs(y1-y2);
            
            return new Rectangle2D.Float(x, y, width, height);
            
        }
        
        private Ellipse2D.Float drawEllipse(int x1, int y1, int x2, int y2){
            
            int x = Math.min(x1, x2);
            int y = Math.min(y1, y2);
            
            int width = Math.abs(x1-x2);
            int height = Math.abs(y1-y2);
            
            return new Ellipse2D.Float(x, y, width, height);
            
        }
        
        private Line2D.Float drawLine(int x1, int y1, int x2, int y2){
            
            return new Line2D.Float(x1, y1, x2, y2);
            
        }
        
        private Ellipse2D.Float drawBrush(int x1, int y1, int brushStrokeWidth, int brushStrokeHeight){
            
            return new Ellipse2D.Float(x1, y1, brushStrokeWidth, brushStrokeHeight);
            
        }
        
    }
    
    private class ListenerForSlider implements ChangeListener{

        public void stateChanged(ChangeEvent e) {
            
            if(e.getSource() == transSlider) {
                
                transLabel.setText("  Transparent: " + dec.format(transSlider.getValue() * .01));
                
                transparentVal = (float) (transSlider.getValue() * .01);
                
            }
            
        }
        
    }
    
    private class ListenerForSlider2 implements ChangeListener{

        public void stateChanged(ChangeEvent e) {
            
            if(e.getSource() == strokeSizeSlider) {
                
                strokeSizeLabel.setText("  Stroke: " + strokeSizeSlider.getValue());
                
                strokeSize = strokeSizeSlider.getValue();
            }
            
        }
        
    }
    
}

Solution

  • If this were my project, I'd draw to an off-screen buffer, a BufferedImage, and then display that image in my painting method. This way, the current stroke width would only be used on the newest drawing. I'd also:

    • Override paintComponent, not paint
    • Draw within a class that extends JPanel and not a JComponent since a JPanel is opaque by default and uses a FlowLayout by default
    • Call the super's painting method in my override (super.paintComponent(g) if overriding paintComponent)
    • Not paint points but rather lines
    • Create my line starting points, (x1, y1) in a mouse listener's mousePressed method.
    • Create and draw a new line on the buffered image within mouseDragged (of a mouse motion listener) and mouseReleased in the mouse listener
    • Use a MouseAdapter for both the mouse listener and mouse motion listener.
    • I'd extract the value from the slider on mousePressed, when the current lines are first created
    • Create my Graphics2D object from the buffered image on mousePressed
    • and dispose of it on mouseReleased

    For example in the code below I have a BufferedImage is created to fill the drawing JPanel

    private BufferedImage buffer;
    

    I have a nested class called MyMouse that extends MouseAdapter. This allows it to be used as both a MouseListener and a MouseMotionListener, and in fact this is what I do in the constructor:

    MyMouse myMouse = new MyMouse();
    addMouseListener(myMouse);
    addMouseMotionListener(myMouse);
    

    In this mouse adapter I override the mousePressed, mouseDragged, and mouseReleased methods.

    In mousePressed, I extract a Graphics2D object from the buffered image and get my starting drawing point, my x1, and y1:

    // mousePressed(MouseEvent e) 
    
    // set my x1 and y1 points for drawing.
    x1 = e.getX();
    y1 = e.getY();
    
    // create a graphics object to draw to
    g2d = buffer.createGraphics();
    
    // smooth the drawing
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    

    I also get the stroke width selection from the slider and use it to set my Graphics object's Stroke:

    // use the current stroke width value from the slider
    int strokeWidth = strokeWidthSlider.getValue();
    g2d.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
    

    Just for fun, I generate a random color for each brush stroke

    // generate a random color
    float hue = (float) Math.random();
    float saturation = (float) (0.5 * Math.random() + 0.5);
    float brightness = 1f;
    Color randomColr = Color.getHSBColor(hue, saturation, brightness);
    g2d.setColor(randomColr);
    

    I then draw a "point" by drawing a line of length 0, and then call repaint():

    // draw a single point
    g2d.drawLine(x1, y1, x1, y1);
    repaint();
    }
    

    In mouseDragged and mouseReleased I check to make sure that my graphics object is not null. If it is OK, I get my second point, x2, y2, and draw a line between x1, y1 and x2, y2:

    if (g2d == null) {
        return;
    }
    // get the next point
    int x2 = e.getX();
    int y2 = e.getY();
    
    // draw a connecting line
    g2d.drawLine(x1, y1, x2, y2);
    

    I then reset x1 and y1 with x2, y2 in anticipation of drawing the next line, and then call repaint()

    // reset the points
    x1 = x2;
    y1 = y2;
    repaint();
    

    In mouseReleased I do similar things as above, but then I dispose of the Graphics2D object obtained from the image (it is OK to dispose of a Graphics object that the coder creates, but never do this to a Graphics object given to you by the JVM, such as given via a paintComponent method):

    // same as mouseDragged:
    if (g2d == null) {
        return;
    }
    int x2 = e.getX();
    int y2 = e.getY();
    
    g2d.drawLine(x1, y1, x2, y2);
    repaint();
    
    // dispose of the Graphics object
    g2d.dispose();
    g2d = null;
    

    Then, all the painting method has to do (again here, it is paintComponent, not paint) is draw the buffered image, if it is not null:

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);  // Don't forget this!!!!
        if (buffer != null) {
            g.drawImage(buffer, 0, 0, this);
        }
    }
    

    Don't forget to call the super's method so that housekeeping painting will be done.


    The whole shooting match:

    import javax.swing.*;
    import java.awt.event.*;
    import java.awt.*;
    import java.awt.image.BufferedImage;
    
    public class Lesson48b extends JPanel {
        private static final int BI_WIDTH = 1000;
        private static final int BI_HEIGHT = 600;
        private BufferedImage buffer;
        private JSlider strokeWidthSlider = new JSlider(0, 20, 1);
        private JButton cleaButton = new JButton("Clear");
    
        public Lesson48b() {
            // give the drawing a white background
            clearImage();
    
            strokeWidthSlider.setMajorTickSpacing(5);
            strokeWidthSlider.setMinorTickSpacing(1);
            strokeWidthSlider.setPaintTicks(true);
            strokeWidthSlider.setPaintLabels(true);
            strokeWidthSlider.setOpaque(false);
            add(strokeWidthSlider);
    
            cleaButton.addActionListener(e -> clearImage());
            add(cleaButton);
    
            MyMouse myMouse = new MyMouse();
            addMouseListener(myMouse);
            addMouseMotionListener(myMouse);
        }
    
        private void clearImage() {
            buffer = new BufferedImage(BI_WIDTH, BI_HEIGHT, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2d = buffer.createGraphics();
            g2d.setBackground(Color.WHITE);
            g2d.clearRect(0, 0, BI_WIDTH, BI_HEIGHT);
            g2d.dispose();
            repaint();
        }
    
        @Override
        public Dimension getPreferredSize() {
            return new Dimension(BI_WIDTH, BI_HEIGHT);
        }
    
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (buffer != null) {
                g.drawImage(buffer, 0, 0, this);
            }
        }
    
        class MyMouse extends MouseAdapter {
            private int x1, y1;
            Graphics2D g2d;
    
            public void mousePressed(MouseEvent e) {
                // set my x1 and y1 points for drawing.
                x1 = e.getX();
                y1 = e.getY();
    
                // create a graphics object to draw to
                g2d = buffer.createGraphics();
    
                // smooth the drawing
                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    
                // use the current stroke width value from the slider
                int strokeWidth = strokeWidthSlider.getValue();
                g2d.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
    
                // generate a random color
                float hue = (float) Math.random();
                float saturation = (float) (0.5 * Math.random() + 0.5);
                float brightness = 1f;
                Color randomColr = Color.getHSBColor(hue, saturation, brightness);
                g2d.setColor(randomColr);
    
                // draw a single point
                g2d.drawLine(x1, y1, x1, y1);
                repaint();
            }
    
            public void mouseDragged(MouseEvent e) {
                if (g2d == null) {
                    return;
                }
                // get the next point
                int x2 = e.getX();
                int y2 = e.getY();
    
                // draw a connecting line
                g2d.drawLine(x1, y1, x2, y2);
    
                // reset the points
                x1 = x2;
                y1 = y2;
                repaint();
            }
    
            public void mouseReleased(MouseEvent e) {
                if (g2d == null) {
                    return;
                }
                int x2 = e.getX();
                int y2 = e.getY();
    
                g2d.drawLine(x1, y1, x2, y2);
                repaint();
    
                // dispose of the Graphics object
                g2d.dispose();
                g2d = null;
            }
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    Lesson48b mainPanel = new Lesson48b();
                    JFrame frame = new JFrame("Java Paint");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(mainPanel);
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    }