Does java Shape interface contract and library routines allow combining multiple shapes into one object extending Shape interface?
For example, may I define class Flower which will consist of several ovals for petals and core?
Or the Shape supposes only one continuous outline? If so then is there any class in Java for holding multiple shapes, may be some class for vectorized graphics?
To manipulate shapes in Java like you're describing, you want to use the Area class, which has these operations. Just convert a Shape to an Area with new Area(Shape).
Here is my attempt - using a rotate transform anchored on the center of the flower shape.
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class DaisyDisplay {
DaisyDisplay() {
BufferedImage daisy = new BufferedImage(
200,200,BufferedImage.TYPE_INT_RGB);
Graphics2D g = daisy.createGraphics();
g.setColor(Color.GREEN.darker());
g.fillRect(0, 0, 200, 200);
Daisy daisyPainter = new Daisy();
daisyPainter.paint(g);
g.dispose();
JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(daisy)));
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new DaisyDisplay();
}
});
}
}
class Daisy {
public void paint(Graphics2D g) {
Area daisyArea = getDaisyShape();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
paintDaisyPart(g,daisyArea);
g.setTransform(AffineTransform.getRotateInstance(
Math.PI*1/8,
100,100));
paintDaisyPart(g,daisyArea);
g.setTransform(AffineTransform.getRotateInstance(
Math.PI*3/8,
100,100);
paintDaisyPart(g,daisyArea);
g.setTransform(AffineTransform.getRotateInstance(
Math.PI*2/8,
100,100));
paintDaisyPart(g,daisyArea);
}
public void paintDaisyPart(Graphics2D g, Area daisyArea) {
g.setClip(daisyArea);
g.setColor(Color.YELLOW);
g.fillRect(0, 0, 200, 200);
g.setColor(Color.YELLOW.darker());
g.setClip(null);
g.setStroke(new BasicStroke(3));
g.draw(daisyArea);
}
public Area getDaisyShape() {
Ellipse2D.Double core = new Ellipse2D.Double(70,70,60,60);
Area area = new Area(core);
int size = 200;
int pad = 10;
int petalWidth = 50;
int petalLength = 75;
// left petal
area.add(new Area(new Ellipse2D.Double(
pad,(size-petalWidth)/2,petalLength,petalWidth)));
// right petal
area.add(new Area(new Ellipse2D.Double(
(size-petalLength-pad),(size-petalWidth)/2,petalLength,petalWidth)));
// top petal
area.add(new Area(new Ellipse2D.Double(
(size-petalWidth)/2,pad,petalWidth,petalLength)));
// bottom petal
area.add(new Area(new Ellipse2D.Double(
(size-petalWidth)/2,(size-petalLength-pad),petalWidth,petalLength)));
return area;
}
}
Related
I created the following code example to demonstrate my problem. I want to draw into a BufferedImage by a Graphics2D object, but the edges are not sharp. I tried using different renderinghints, but it didn't help. I also tried a BufferedImageOp as you can see in the code, but I don't understand its meaning in drawImage and don't know its possibilites. Can you help me please?
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
public class SmoothGraphics extends JComponent{
private BufferedImage image;
private static JFrame frame;
private int x = 100;
private int y = 100;
private int size = 200;
public static void main(String[] args){
frame = new JFrame();
SmoothGraphics component = new SmoothGraphics();
frame.add(component);
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.setVisible(true);
frame.pack();
}
public SmoothGraphics(){
super();
setPreferredSize(new Dimension(400,400));
setBounds(100,100,400,400);
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
int width = getWidth();
int height = getHeight();
GraphicsConfiguration gc = frame.getGraphicsConfiguration();
image = gc.createCompatibleImage(width, height);
Graphics2D imageGraphics = (Graphics2D) image.createGraphics();
imageGraphics.setColor(Color.RED);
/*imageGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);*/
drawExample(imageGraphics);
imageGraphics.dispose();
/*the following kernel helps with the smoothness of the rectangle, but not of the circle
int kernelWidth = 3;
int kernelHeight = 3;
float[] data = new float[kernelWidth*kernelHeight];
data[4]=1;
Kernel kernel = new Kernel(kernelWidth, kernelHeight, data);
BufferedImageOp op = new ConvolveOp(kernel);
*/
//draw image
BufferedImageOp op = null;
g2d.drawImage(image,op,0,0);
//I don't know if this line is necessary
image.flush();
}
private void drawExample(Graphics2D g2d){
//Square as path
Path2D.Double path = new Path2D.Double();
path.moveTo(x,y);
path.lineTo(x+size,y);
path.lineTo(x+size,y+size);
path.lineTo(x,y+size);
path.closePath();
g2d.draw(path);
//Circle
g2d.fillOval(x+size/4,y+size/4,size/2,size/2);
}
}
The output of my code. Notice that the edges are not very clean/sharp!
My result with antialiasing and kernel usage:
My wished result:
Okay... I believe the real problem you are facing is initial scaling of the component graphics (g), which makes your image stretched during drawImage(...). I don't get the same problem here.
To get proper smooth (or "sharp" as you call it) rendering, you do want to enable antialiasing. But you also need to paint without pixel scaling (it's possible with image too, by creating a larger image, however, I don't see why you need the extra image in this case).
Here's your sample program rewritten:
import javax.swing.*;
import java.awt.*;
public class SmoothGraphics extends JComponent {
private final int x = 100;
private final int y = 100;
private final int size = 200;
public static void main(String[] args) {
JFrame frame = new JFrame("Smooth");
SmoothGraphics component = new SmoothGraphics();
frame.getContentPane().add(component, BorderLayout.CENTER); // Add to content pane
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack(); // Pack *before* setVisible(true)!
frame.setLocationRelativeTo(null); // Center the frame after pack()
frame.setVisible(true);
}
public SmoothGraphics() {
setOpaque(true); // Minor optimization
setPreferredSize(new Dimension(400, 400)); // No need to use setBounds, pack() will fix that for you
}
#Override
public void paintComponent(Graphics g) {
// No need to call super, as we repaint everything
Graphics2D g2d = (Graphics2D) g;
// Clear background to black
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, getWidth(), getHeight());
// Paint in read, with antialiasing
g2d.setColor(Color.RED);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
drawExample(g2d);
}
private void drawExample(Graphics2D g2d) {
// Square (this way avoids the odd skewed lines)
g2d.drawRect(x, y, size, size);
// Circle
g2d.fillOval(x + size / 4, y + size / 4, size / 2, size / 2);
}
}
Result:
If you really want/need the BufferedImage, I found a way that works for me (MacOS). It might need some minor tweaks to work perfectly on other platforms.
#Override
public void paintComponent(Graphics g) {
// No need to call super, as we repaint everything
Graphics2D g2d = (Graphics2D) g;
GraphicsConfiguration gc = g2d.getDeviceConfiguration();
AffineTransform transform = gc.getNormalizingTransform();
BufferedImage image = gc.createCompatibleImage((int) ceil(getWidth() * transform.getScaleX()), (int) ceil(getHeight() * transform.getScaleY()));
Graphics2D graphics = image.createGraphics();
try {
graphics.setTransform(transform);
graphics.setColor(Color.RED);
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Need antialiasing on
drawExample(graphics);
}
finally {
graphics.dispose();
}
try {
AffineTransform originalTransform = g2d.getTransform();
originalTransform.concatenate(transform.createInverse());
g2d.setTransform(originalTransform);
}
catch (NoninvertibleTransformException e) {
throw new RuntimeException(e); // API oddity, this should have been a RuntimeException
}
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); // Need render QUALITY here, to avoid the stretching/tearing
// g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); // Don't need this on MacOS, but may be needed on other platforms
g2d.drawImage(image, null, 0, 0);
}
Result:
I am wanting to make a game that has each level loaded from an image.
I want to draw up the whole level in Photoshop, and then set it as the background and allow the player to walk over it.
I want another invisible image to go over top which will be black in all places that I want to collide with.
The reason I don't want to use tiles, which are much easier with rectangle collision and such, is because there will be complex corners and not everything will be rectangle.
Is this a good idea, and is it possible to do easily?
Would this be a big CPU hog or is there a better way to do this?
Level image
Obstacles shown in red
..there will be complex corners and not everything will be rectangle.
This could be achieved by drawing and dealing with Shape and Area instances. E.G.
Yellow is a little animated 'player'.
The bounds of the image represent walls that contain the path of the player (it bounces off them).
Obstacles are painted green when not in collision, red otherwise.
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
class ShapeCollision {
private BufferedImage img;
private Area[] obstacles = new Area[4];
private Area walls;
int x;
int y;
int xDelta = 3;
int yDelta = 2;
/** A method to determine if two instances of Area intersect */
public boolean doAreasCollide(Area area1, Area area2) {
boolean collide = false;
Area collide1 = new Area(area1);
collide1.subtract(area2);
if (!collide1.equals(area1)) {
collide = true;
}
Area collide2 = new Area(area2);
collide2.subtract(area1);
if (!collide2.equals(area2)) {
collide = true;
}
return collide;
}
ShapeCollision() {
int w = 400;
int h = 200;
img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
final JLabel imageLabel = new JLabel(new ImageIcon(img));
x = w/2;
y = h/2;
//circle
obstacles[0] = new Area(new Ellipse2D.Double(40, 40, 30, 30));
int[] xTriangle = {330,360,345};
int[] yTriangle = {60,60,40};
//triangle
obstacles[1] = new Area(new Polygon(xTriangle, yTriangle, 3));
int[] xDiamond = {60,80,60,40};
int[] yDiamond = {120,140,160,140};
//diamond
obstacles[2] = new Area(new Polygon(xDiamond, yDiamond, 4));
int[] xOther = {360,340,360,340};
int[] yOther = {130,110,170,150};
// other
obstacles[3] = new Area(new Polygon(xOther, yOther, 4));
walls = new Area(new Rectangle(0,0,w,h));
ActionListener animate = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
animate();
imageLabel.repaint();
}
};
Timer timer = new Timer(50, animate);
timer.start();
JOptionPane.showMessageDialog(null, imageLabel);
timer.stop();
}
public void animate() {
Graphics2D g = img.createGraphics();
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.BLUE);
g.fillRect(0, 0, img.getWidth(), img.getHeight());
x+=xDelta;
y+=yDelta;
int s = 15;
Area player = new Area(new Ellipse2D.Double(x, y, s, s));
// Acid test of edge collision;
if (doAreasCollide(player,walls)) {
if ( x+s>img.getWidth() || x<0 ) {
xDelta *= -1;
}
if(y+s>img.getHeight() || y<0 ) {
yDelta *= -1;
}
}
g.setColor(Color.ORANGE);
for (Area obstacle : obstacles) {
if (doAreasCollide(obstacle, player)) {
g.setColor(Color.RED);
} else {
g.setColor(Color.GREEN);
}
g.fill(obstacle);
}
g.setColor(Color.YELLOW);
g.fill(player);
g.dispose();
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
new ShapeCollision();
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
}
Edit
make it detect all the red color and set that as the collision bounds
At start-up, use the source seen in the Smoothing a jagged path question to get an outline of the red pixels (see the getOutline(Color target, BufferedImage bi) method). Store that Area as the single obstacle on start-up.
I'm trying to rotate one whole Ellipse2D object based on user key input. If the user presses the right key, rotate right and left key means rotate left. The rotAngle is set to 25. I made a separate drawRotEllipse because otherwise it would have always drawn the original one. I think my confusion is happening with the Graphics and Shapes Objects. Tried the AffineTransform business but that didn't work out either. I just want it to rotate about the center. Thanks for any help!
class Canvas extends JPanel implements java.io.Serializable{
int x1 = (int) (this.getWidth()/2)+100;
int y1 = (int) (this.getHeight()/2)+20;
int x2 = (int) this.getWidth()+100;
int y2 = (int) this.getHeight()+200;
#Override
public void paintComponent(Graphics g){
g.setColor(Color.WHITE);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
g.setColor(Color.RED);
drawEllipse(g);
}
public void drawEllipse (Graphics g){
Graphics2D g2d = (Graphics2D) g;
myShape = new Ellipse2D.Double(x1,y1,x2,y2);
g2d.draw(myShape);
this.repaint();
}
public void drawRotEllipse (Graphics g){
g2d.draw(myShape);
this.repaint();
}
}
private void jPanel1KeyPressed(java.awt.event.KeyEvent evt) {
if (evt.getKeyCode()==39){
g2d.rotate(Math.toDegrees(rotAngle));
myCanvas.drawRotEllipse(g2d);
}
else if (evt.getKeyCode()==37){
g2d.rotate(Math.toDegrees(-rotAngle));
myCanvas.drawRotEllipse(g2d);
}
}
if (evt.getKeyCode()==39)
Don't use magic numbers. People don't know that means by just looking at the code.
Instead use variable provided by the API:
if (evt.getKeyCode() == KeyEvent.VK_RIGHT)
You KeyEvent code should not do the actual painting. All the code should do is set the "degrees" property of your class. The setDegrees(...) method will then be responsible for invoking repaint(). Now whenever the component is repainted the shape will be painted at its current degrees of rotation.
Here is an example that uses a JSlider to change the rotation degrees of the class.
It rotates an image. You should be able to change the code rotation the image to just draw your shape:
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.*;
public class Rotation2 extends JPanel
{
BufferedImage image;
int degrees;
int point = 250;
public Rotation2(BufferedImage image)
{
this.image = image;
setDegrees( 0 );
setPreferredSize( new Dimension(600, 600) );
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g.create();
double radians = Math.toRadians( degrees );
g2.translate(point, point);
g2.rotate(radians);
g2.translate(-image.getWidth(this) / 2, -image.getHeight(this) / 2);
g2.drawImage(image, 0, 0, null);
g2.dispose();
g.setColor(Color.RED);
g.fillOval(point - 5, point - 5, 10, 10);
}
public void setDegrees(int degrees)
{
this.degrees = degrees;
repaint();
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
try
{
// String path = "mong.jpg";
String path = "dukewavered.gif";
ClassLoader cl = Rotation2.class.getClassLoader();
BufferedImage bi = ImageIO.read(cl.getResourceAsStream(path));
final Rotation2 r = new Rotation2(bi);
final JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 360, 0);
slider.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
int value = slider.getValue();
r.setDegrees( value );
}
});
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JScrollPane(r));
f.add(slider, BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
catch(IOException e)
{
System.out.println(e);
}
}
});
}
}
I am wanting to make a game that has each level loaded from an image.
I want to draw up the whole level in Photoshop, and then set it as the background and allow the player to walk over it.
I want another invisible image to go over top which will be black in all places that I want to collide with.
The reason I don't want to use tiles, which are much easier with rectangle collision and such, is because there will be complex corners and not everything will be rectangle.
Is this a good idea, and is it possible to do easily?
Would this be a big CPU hog or is there a better way to do this?
Level image
Obstacles shown in red
..there will be complex corners and not everything will be rectangle.
This could be achieved by drawing and dealing with Shape and Area instances. E.G.
Yellow is a little animated 'player'.
The bounds of the image represent walls that contain the path of the player (it bounces off them).
Obstacles are painted green when not in collision, red otherwise.
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
class ShapeCollision {
private BufferedImage img;
private Area[] obstacles = new Area[4];
private Area walls;
int x;
int y;
int xDelta = 3;
int yDelta = 2;
/** A method to determine if two instances of Area intersect */
public boolean doAreasCollide(Area area1, Area area2) {
boolean collide = false;
Area collide1 = new Area(area1);
collide1.subtract(area2);
if (!collide1.equals(area1)) {
collide = true;
}
Area collide2 = new Area(area2);
collide2.subtract(area1);
if (!collide2.equals(area2)) {
collide = true;
}
return collide;
}
ShapeCollision() {
int w = 400;
int h = 200;
img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
final JLabel imageLabel = new JLabel(new ImageIcon(img));
x = w/2;
y = h/2;
//circle
obstacles[0] = new Area(new Ellipse2D.Double(40, 40, 30, 30));
int[] xTriangle = {330,360,345};
int[] yTriangle = {60,60,40};
//triangle
obstacles[1] = new Area(new Polygon(xTriangle, yTriangle, 3));
int[] xDiamond = {60,80,60,40};
int[] yDiamond = {120,140,160,140};
//diamond
obstacles[2] = new Area(new Polygon(xDiamond, yDiamond, 4));
int[] xOther = {360,340,360,340};
int[] yOther = {130,110,170,150};
// other
obstacles[3] = new Area(new Polygon(xOther, yOther, 4));
walls = new Area(new Rectangle(0,0,w,h));
ActionListener animate = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
animate();
imageLabel.repaint();
}
};
Timer timer = new Timer(50, animate);
timer.start();
JOptionPane.showMessageDialog(null, imageLabel);
timer.stop();
}
public void animate() {
Graphics2D g = img.createGraphics();
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.BLUE);
g.fillRect(0, 0, img.getWidth(), img.getHeight());
x+=xDelta;
y+=yDelta;
int s = 15;
Area player = new Area(new Ellipse2D.Double(x, y, s, s));
// Acid test of edge collision;
if (doAreasCollide(player,walls)) {
if ( x+s>img.getWidth() || x<0 ) {
xDelta *= -1;
}
if(y+s>img.getHeight() || y<0 ) {
yDelta *= -1;
}
}
g.setColor(Color.ORANGE);
for (Area obstacle : obstacles) {
if (doAreasCollide(obstacle, player)) {
g.setColor(Color.RED);
} else {
g.setColor(Color.GREEN);
}
g.fill(obstacle);
}
g.setColor(Color.YELLOW);
g.fill(player);
g.dispose();
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
new ShapeCollision();
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
}
Edit
make it detect all the red color and set that as the collision bounds
At start-up, use the source seen in the Smoothing a jagged path question to get an outline of the red pixels (see the getOutline(Color target, BufferedImage bi) method). Store that Area as the single obstacle on start-up.
I am looking for the simplest way to draw some
text around an ellipse object on my app.
I need to create a feeling of "cuddling".
So far, I've used the Graphics2D class to print my drawings
on screen and my "canvas" is a BufferedImage.
The width and height of my ellipses are constant at 50,50 respectively.
Any suggestions?
Here's an example of curved text:
// slightly modified from the original:
// http://examples.oreilly.com/9781565924840/examples/RollingText.java
import javax.swing.*;
import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
public class RollingText extends JFrame {
RollingText() {
super("RollingText v1.0");
super.setSize(650, 350);
super.setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
String s = "What's our vector, Victor?";
Font font = new Font("Serif", Font.PLAIN, 24);
FontRenderContext frc = g2.getFontRenderContext();
g2.translate(40, 80);
GlyphVector gv = font.createGlyphVector(frc, s);
int length = gv.getNumGlyphs();
for (int i = 0; i < length; i++) {
Point2D p = gv.getGlyphPosition(i);
double theta = (double) i / (double) (length - 1) * Math.PI / 4;
AffineTransform at = AffineTransform.getTranslateInstance(p.getX(), p.getY());
at.rotate(theta);
Shape glyph = gv.getGlyphOutline(i);
Shape transformedGlyph = at.createTransformedShape(glyph);
g2.fill(transformedGlyph);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new RollingText().setVisible(true);
}
});
}
}
which produces: