Jay Gorio
Jay Gorio

Reputation: 335

How to use getWidth() and getHeight() to center the star shape in java

I'm having problem centering the star shape. The problem is that I hard coded the points for each coordinate. And the problem with this is that Im not getting the right position although I created a perfect 5 star. On more problem is that what if I want to rescale the shape to make it bigger or small by clicking a button? Here's my code.

public void run() {
            DifferentShapes panel = new DifferentShapes();
            JFrame frame = new JFrame("Different Shapes");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setSize(400,400);
            frame.setVisible(true);  
            frame.add(panel);
            frame.setJMenuBar(panel.getMenuBAr());
        }

    public void star(Graphics shape) {
    //        int sizeReq = 1;
    int [] starX =  new int[] {250, 262, 304, 268, 278, 250, 222, 232, 196, 238};
    int []starY =  new int[] {200, 236, 236, 254, 296, 272, 296, 254, 236, 236};
    shape.fillPolygon(starX, starY, starX.length);    

}

Upvotes: 1

Views: 2370

Answers (2)

MadProgrammer
MadProgrammer

Reputation: 347332

Without the Shapes API

You could spend a lot of time manually translating the polygon array, but a better solution might be to translate the polygon to 0x0, so that the top/left corner will be at 0x0

Basically, I calculated the min/max values for each array and then subtracted the "min" value from all the values...

protected static int[] minMax(int[] values) {
    int min = Integer.MAX_VALUE;
    int max = Integer.MIN_VALUE;
    for (int value : values) {
        min = Math.min(min, value);
        max = Math.max(max, value);
    }

    return new int[]{min, max};
}

protected static int[] normalise(int[] values, int min) {
    for (int index = 0; index < values.length; index++) {
        values[index] = values[index] - min;
    }
    return values;
}

So starting with...

[250, 262, 304, 268, 278, 250, 222, 232, 196, 238]
[200, 236, 236, 254, 296, 272, 296, 254, 236, 236]

translated to...

[54, 66, 108, 72, 82, 54, 26, 36, 0, 42]
[0, 36, 36, 54, 96, 72, 96, 54, 36, 36]

which when painted, looks something like...

Top/Left

You're probably thinking about now, well, that's not much good, I want it in the centre, but to get in the center, we need the width and height of the polygon

To achieve this, I took the translated arrays and passed them through the minMax method and used the second element of the return value, which gave me a value of 108x96 (widthxheight), now we have the information we need to center the shape

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics copy = g.create();
    int x = (getWidth() - maxWidth) / 2;
    int y = (getHeight() - maxHeight) / 2;
    copy.translate(x, y);
    copy.fillPolygon(starX, starY, starX.length);
    copy.dispose();
}

Basically, all this does is uses the maxWidth and maxHeight values to calculate the center location within the current component, translate the Graphics context to the appropriate offset and paints the polygon. The translation moves the origin position (the 0x0 point of the Graphics context) which allows use to change where the polygon will be painted.

Star in the middle

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.Arrays;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    protected static int[] minMax(int[] values) {
        int min = Integer.MAX_VALUE;
        int max = Integer.MIN_VALUE;
        for (int value : values) {
            min = Math.min(min, value);
            max = Math.max(max, value);
        }

        return new int[]{min, max};
    }

    protected static int[] normalise(int[] values, int min) {
        for (int index = 0; index < values.length; index++) {
            values[index] = values[index] - min;
        }
        return values;
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private int starX[];
        private int starY[];

        private int maxWidth, maxHeight;

        public TestPane() {
            starX = new int[]{250, 262, 304, 268, 278, 250, 222, 232, 196, 238};
            starY = new int[]{200, 236, 236, 254, 296, 272, 296, 254, 236, 236};

            int[] minMaxX = minMax(starX);
            int[] minMaxY = minMax(starY);

            starX = normalise(starX, minMaxX[0]);
            starY = normalise(starY, minMaxY[0]);

            minMaxX = minMax(starX);
            minMaxY = minMax(starY);

            maxWidth = minMaxX[1];
            maxHeight = minMaxY[1];
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics copy = g.create();
            int x = (getWidth() - maxWidth) / 2;
            int y = (getHeight() - maxHeight) / 2;
            copy.translate(x, y);
            copy.fillPolygon(starX, starY, starX.length);
            copy.dispose();
        }

    }

}

So, about know, you should see the importance of using a 0x0 origin for your shapes.

Have a look at 2D Graphics and Transforming Shapes, Text, and Images for more details

Scaling...

Scaling can be done through Graphics2D#scale...

Scale

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D copy = (Graphics2D) g.create();
    double scale = slider.getValue() / 100d;
    int x = (getWidth() - maxWidth) / 2;
    int y = (getHeight() - maxHeight) / 2;
    copy.translate(x, y);
    copy.scale(scale, scale);
    copy.fillPolygon(starX, starY, starX.length);
    copy.dispose();
}

but this scales the pixels which might not give the results you want.

Before someone jumps down my throat, if you want to get it centered again, you'll need to scale the maxWidth and maxHeight values as well

With the Shapes API

As I've said in the past, you will get better results with the 2D shapes API, they are self contained, they are easily moved and you will get a better results when they are scaled

For example, using the "translated" values from above, you should create a nice custom, re-usable, class...

public class StarShape extends Path2D.Double {

    public StarShape() {
        moveTo(54, 0);
        lineTo(66, 36);
        lineTo(108, 36);
        lineTo(75, 54);
        lineTo(82, 96);
        lineTo(54, 72);
        lineTo(26, 96);
        lineTo(36, 54);
        lineTo(0, 36);
        lineTo(42, 36);
        closePath();
    }

}

Know, I don't know about you, but that is so much easier to read and you could easily plot this on some graph paper.

Now it's easily filled by doing something as simple as...

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g.create();
    g2d.fill(new StarShape());
    g2d.dispose();
}

But, wait, we want it centered, all to easy...

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g.create();
    Rectangle bounds = starShape.getBounds();
    int x = (getWidth() - bounds.width) / 2;
    int y = (getHeight() - bounds.height) / 2;
    g2d.fill(starShape.createTransformedShape(AffineTransform.getTranslateInstance(x, y)));
    g2d.dispose();
}

Well, that was a lot less code than the non-shapes API approach...

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class StarShape extends Path2D.Double {

        public StarShape() {
            moveTo(54, 0);
            lineTo(66, 36);
            lineTo(108, 36);
            lineTo(75, 54);
            lineTo(82, 96);
            lineTo(54, 72);
            lineTo(26, 96);
            lineTo(36, 54);
            lineTo(0, 36);
            lineTo(42, 36);
            closePath();
        }

    }

    public class TestPane extends JPanel {

        private StarShape starShape;

        public TestPane() {
            starShape = new StarShape();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            Rectangle bounds = starShape.getBounds();
            int x = (getWidth() - bounds.width) / 2;
            int y = (getHeight() - bounds.height) / 2;
            g2d.fill(starShape.createTransformedShape(AffineTransform.getTranslateInstance(x, y)));
            g2d.dispose();
        }

    }

}

Scaling

Generally speaking, scaling uses the same process as translating, BUT, what you get, is the benefit that you can scale a Shape and then translate it separately, allowing you to get the Rectangle bounds of the scaled Shape independently. It also scales the vector (points) and not pixels which will give you a better result...

Scales

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g.create();
    double scale = slider.getValue() / 100d;
    Shape shape = starShape.createTransformedShape(AffineTransform.getScaleInstance(scale, scale));
    Rectangle bounds = shape.getBounds();
    int x = (getWidth() - bounds.width) / 2;
    int y = (getHeight() - bounds.height) / 2;
    GeneralPath path = new GeneralPath();
    path.append(shape.getPathIterator(AffineTransform.getTranslateInstance(x, y)), true);
    g2d.fill(path);
    g2d.dispose();
}

So, the short answer is, use the Shapes API, the long answer is, use the Shapes API, you won't need to reinvent the wheel for each problem you face

Upvotes: 5

camickr
camickr

Reputation: 324207

You might want to consider using the Shape Utils helper class to generate the star for you so you don't need to manually calculate all the points on the star.

Basically you just need two lines of code to use the class:

star = ShapeUtils.radiusShape(10, 55, 22);

This will generate a star with 10 points alternating from 55 to 22 pixels from the center.

The first point generated will be on the horizontal line. Since there are 5 large points this means they will be generate every 72 degrees. Since you want the top point to be on the vertical axis you will then need to rotate all the points by -18 degrees:

star = ShapeUtils.rotate(star, -18);

A simple example showing how to paint the star centered in a panel is as follows:

import java.awt.*;
import javax.swing.*;

public class ShapeSSCCE extends JPanel
{
    private Shape star;

    public ShapeSSCCE()
    {
        star = ShapeUtils.radiusShape(10, 55, 22);
        star = ShapeUtils.rotate(star, -18);
    }

    @Override
    public Dimension getPreferredSize()
    {
        Rectangle bounds = star.getBounds();

        return new Dimension(bounds.width, bounds.height);
    }

    @Override
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        Graphics2D g2d = (Graphics2D) g.create();
        Rectangle bounds = star.getBounds();
        int x = (getWidth()  - bounds.width)  / 2;
        int y = (getHeight() - bounds.height) / 2;
        g2d.translate(x, y);
        g2d.fill( star );
        g2d.dispose();
    }

    private static void createAndShowUI()
    {
        JFrame frame = new JFrame("ShapeSSCCE");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add( new ShapeSSCCE() );
        frame.setSize(200, 200);
        frame.setLocationByPlatform( true );
        frame.setVisible( true );
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                createAndShowUI();
            }
        });
    }
}

Try the code with/without the rotated shape to see the difference.

If you don't want to do custom painting then you can use the ShapeIcon class also found in the above link. Then you can treat the star like an Icon and add it to a label:

Shape star = ShapeUtils.radiusShape(10, 55, 22);
star = ShapeUtils.rotate(star, -18);
ShapeIcon starIcon = new ShapeIcon(star, Color.RED);
JLabel starLabel = new JLabel( starIcon );

I want to rescale the shape to make it bigger or small by clicking a button?

You could:

  1. Create a new shape using the ShapeUtils class with new values
  2. Scale the existing shape using an AffineTransform. Check out the ShapeUtils.rotate(...) method to see how this is done for a rotation. The logic would be similar for the scaling.
  3. Dynamically scale the shape when you paint it using an AffineTransform. Read the Graphics2D class for information about setting a transform.

Upvotes: 2

Related Questions