so recently I decided to try and make some form of sprite-esque game, similar to the older Final Fantasy games. I'm mostly doing this as a code learning process rather than using things like RPGMaker to actually create an idea.
The biggest part I'm concerned about are how to properly paint multiple layers of images (such as static Sprites, background images, etc) in a simple way. While I would normally just test a bunch of things to see rather than asking, I realize there are many (possibly easy) methods of ways to implement any one thing in coding.
What are some ways (or Classes) to implement this? Preferably a way that is easy to change what layers are where (layer A in front of B... oh wait no B in front of A! at any time)
I do hope that's not too broad of a question.. .
Thanks.
The most basic approach is the painter's algorithm, simply paint layers in reverse order of the distance to the screen (first background, then characters, then foreground for instance).
You have each layer represented by a simple class, something like:
public class Layer
{
List<Sprite> sprites;
public void draw(Graphics g)
{
for (Sprite sprite : sprites)
sprite.draw(g);
}
}
where a sprite would be:
public class Sprite
{
BufferedImage image;
float x, y, w, h;
public void draw(Graphics g)
{
g.drawImage(image, (int)x, (int)y, (int)(x+w), (int)(y+h), 0, 0, image.getWidth(), image.getHeight(), null);
}
}
Here, the background would be a layer with a single sprite that is the size of the screen. You can subclass the background layer to achieve effects like parallax.
Finally, your layers would be part of a scene:
public class Scene
{
List<Layer> layers;
public void draw(Graphics g)
{
for (Layer layer : layers)
layer.draw(g);
}
}
Changing the order of rendering is as simple as changing the order in the 'layers' list in a scene.
Are you looking to put foreground images into a background image? If yes, then the following code will help:
public class Image {
public static void main(String args[]) throws IOException {
BufferedImage bgImage = ImageIO.read(new File("1.jpg"));
BufferedImage fgImage =ImageIO.read(new File("2.jpg"));
BufferedImage overlayedImage = overlay(bgImage, fgImage);
if (overlayedImage != null) {
File outputfile = new File("3.jpg");
ImageIO.write(overlayedImage, "jpg", outputfile);
} else {}
}
public static BufferedImage overlay(BufferedImage bgImage,
BufferedImage fgImage) {
Graphics2D g = bgImage.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.drawImage(bgImage, 0, 0, null);
g.drawImage(fgImage, 0, 0, null);
g.dispose();
return bgImage;
}
}
Related
I'm creating a game using Java Swing, and I'm finding the need for graphical displays of what's going on at this point. The current display uses a grid of JButtons to represent the tiles on a battlefield. Would it be possible to display floating/disappearing damage numbers over the JButtons, using perhaps custom made pixel art GIFs? If so, how would I go about implementing this?
Almost all Swing components can be extended to change its presentation.
Create an own button class extending JButton and override its paintComponent method to display the required damage. This method receives a Graphic (actually an instance of Graphics2D) on which you can draw the damage, if one is active.
Very simple example:
public class DamageButton extends JButton {
private String damage = null;
public DamageButton(String text) {
super(text);
}
public void setDamage(String damage) {
this.damage = damage;
System.out.println(damage);
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (damage != null) {
Graphics2D gg = (Graphics2D) g.create();
try {
gg.setColor(Color.RED);
gg.drawString(damage, 10, 10);
} finally {
gg.dispose();
}
}
}
}
Creating a new Graphics (gg) so the settings of g are not changed and need not to be restored.
Call setDamage() with some text to have it displayed over the button or with null to cancel the effect.
Animation or other effects can (more or less) easily be added. Just be aware that the given Graphics2D has a clipping area set to the dimension of the button.
I have a function that is drawing an image and then saving it immediately after but the problem is that it seems to be drawing it twice, once for the view on the screen and then once to save it to the disk
public class myFrame {
public static void main(String[] args) {
JFrame lv_frame = new JFrame();
// setup jframe here
lv_frame.add(new image());
lv_frame.setVisible(true);
}
}
class image extends JPanel {
public void paintComponent(Graphics graphic) {
super.paintComponent(graphic);
draw(graphic);
save();
}
public void draw(Graphics graphic) {
Graphics2D graphic2D = (Graphics2D) graphic;
graphic2D.fillArc(0, 0, 250, 250, 0, 90);
}
public void save() {
BufferedImage image = new BufferedImage(250, 250, BufferedImage.TYPE_4BYTE_ABGR_PRE);
Graphics2D graphic2D = image.createGraphics();
try {
File output = new File("file.png");
// is it possible to use the graphic I've already
// drawn here instead of re-drawing?
draw(graphic2D);
ImageIO.write(image, "png", output);
} catch(IOException log) {
System.out.println(log);
}
}
}
I have a function draw which creates the image on the gui screen and another function save which saves it to the disk but the save function is calling draw as well.
Is it possible to save the image that has already been drawn without re-calling the draw function as it is being done in my code?
I was answering your question on Display to GUI and Save to Disk with a Single Object/Variable, however it was closed probably due to the similar nature of your question.
I think there are several issues which you seemed confused with and I would like to write my solution here which also addresses your question in your duplicated post.
Is it possible to save the image that has already been drawn without re-calling the draw function as it is being done in my code?
Don't be confused between drawing an image on the Panel and saving it. The following shows a working example on saving a drawn image.
class DrawingSpace extends JPanel
{
private BufferedImage buf;
public DrawingSpace(){
setPreferredSize(new Dimension(200, 200));
buf = new BufferedImage(200, 200, BufferedImage.TYPE_INT_ARGB);
drawOnBuffer();
}
public void drawOnBuffer(){
Graphics g = buf.createGraphics();
g.setColor(Color.BLUE);
g.fillOval(0,0,200,200);
g.setColor(Color.RED);
g.fillOval(50,50,100,100);
g.dispose();
}
public void saveBufferAsImage(String pathname){
String fmt = "";
if(!(pathname == null || pathname.equals(""))){
fmt = pathname.substring(pathname.lastIndexOf(".")+1);
File outputfile = new File(pathname);
try{
ImageIO.write(buf, fmt, outputfile);
}catch(IOException ioe){System.out.println("Unable to save file");}
}
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawImage(buf, 0, 0, 200, 200, null); //Only for user to see
}
}
To save an image, you not necessarily have to draw and display it in the JPanel first. Note that I created a BufferedImage called buf and I am drawing on buf. Once I have drawn onto a BufferedImage, I can simply save that image as a file (I don't have to draw it to the panel first).
Notice that I did the following:
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawImage(buf, 0, 0, 200, 200, null); //Only for user to see
}
g.drawImage(buf, 0, 0, 200, 200, null) will draw whatever on buf onto the JPanel. It can be deleted and the saving will still work. When I draw it on the panel, it is only for the user to see the outcome of the drawing.
To test the program:
class SaveImageRunner{
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run(){
JFrame frame = new JFrame("Saving a buffered image");
DrawingSpace ds = new DrawingSpace();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(ds);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
ds.saveBufferAsImage("BlueCircle.png");
}
});
}
}
Saved Image:
Some pointers on drawing:
paintComponent(Graphics) shall only contain codes for drawing and nothing else. Do not implement your codes for saving the image to a file inside here.
Try not to create a new BufferedImage in the paintComponent(Graphics). If not, a new instance of a BufferedImage will be created on every repaint.
Change the place where you create the graphics2D object and reuse it.
Try this out.
public class myFrame {
public static void main(String[] args) {
JFrame lv_frame = new JFrame();
// setup jframe here
lv_frame.add(new image());
lv_frame.setVisible(true);
}
}
class image extends JPanel {
public void paintComponent(Graphics graphic) {
super.paintComponent(graphic);
// Image and graphics are now created here
BufferedImage image = new BufferedImage(250, 250, BufferedImage.TYPE_4BYTE_ABGR_PRE);
Graphics2D graphic2D = image.createGraphics();
draw(graphic2D);
save(image);
}
public void draw(Graphics graphic) {
Graphics2D graphic2D = (Graphics2D) graphic;
graphic2D.fillArc(0, 0, 250, 250, 0, 90);
}
public void save(BufferedImage image) {
try {
File output = new File("file.png");
ImageIO.write(image, "png", output);
} catch(IOException log) {
System.out.println(log);
}
}
}
EDIT
I have found the answer in another post. It is not a bad idea to make the drawing twice. What you are doing is, like #Hovercraft says in this post, separating the screen drawing from the writing to files so that you don't see your graphics drawing performance hurt.
I have tried to make the drawing only once but you have no easy method to store the drawing Graphics object. Probably, it is implemented to prevent this. If you see the way the method works, you are given the Graphics object with the only purpose to draw on it. Using it in other ways could impact the performance.
I put this code together based on a lot of examples I found around here on stackoverflow. When I run the program the entire screen flickers intensely. I'm sure there is something simple I'm overlooking, but so far have been unable to track down a solution. I've been debugging this for a couple hours mostly with the help of online forum reading, so I figured it was time to ask the audience.
public class Screen extends JComponent {
#Override
public Dimension getPreferredSize(){
Dimension tempDimension = Toolkit.getDefaultToolkit().getScreenSize();
return tempDimension;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2D = (Graphics2D)bufferStrategy.getDrawGraphics();
g2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); //sprites overlap instead of overwrite
if(game==null){
drawSplash(g2D);
}else{
drawBoard(g2D);
}
g2D.dispose();
bufferStrategy.show();
}
}
If any additional code is required, I can provide it. Thank you for your help, stackoverflow!
To achieve the results you are getting, you either have another class which extends from Canvas or are using the BufferStrategy from the top level container. In either case, both must be visible on the screen.
Basically, they are fighting each other, as they are two different painting algorithms. Swing, which is a passive painting algorithm, paints updates as they are needed and the BufferStrategy, which uses an active algorithm, requiring your to schedule the updates to the buffer as required.
Both use a double buffering algorithm.
So, you should pick one or the other...
public class Screen extends JComponent {
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2D = (Graphics2D) g.create();
g2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); //sprites overlap instead of overwrite
if (game == null) {
drawSplash(g2D);
} else {
drawBoard(g2D);
}
g2D.dispose();
}
}
or something like...
public void gameEngine(BufferStrategy strategy) {
// Main loop
while (!done) {
// Prepare for rendering the next frame
// ...
// Render single frame
do {
// The following loop ensures that the contents of the drawing buffer
// are consistent in case the underlying surface was recreated
do {
// Get a new graphics context every time through the loop
// to make sure the strategy is validated
Graphics2D g2D = (Graphics2D) strategy.getDrawGraphics();
// Render to graphics
g2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); //sprites overlap instead of overwrite
if (game == null) {
drawSplash(g2D);
} else {
drawBoard(g2D);
}
// Dispose the graphics
g2D.dispose();
// Repeat the rendering if the drawing buffer contents
// were restored
} while (strategy.contentsRestored());
// Display the buffer
strategy.show();
// Repeat the rendering if the drawing buffer was lost
} while (strategy.contentsLost());
}
}
Which was pretty much ripped from the JavaDocs for BufferStrategy
BTW, this...
#Override
public Dimension getPreferredSize(){
Dimension tempDimension = Toolkit.getDefaultToolkit().getScreenSize();
return tempDimension;
}
is a really bad design, you are making assumptions about the state of the component which may not meet reality. You should allow the window to decide how large it ultimately wants to be, which can be achieved by using setExtendedState and passing it JFrame.MAXIMIZED_BOTH, which will take into consideration other OS elements, like the task bar or dock
As part of a program I am writing, I wish to print an image to SVG format. I need it to be in SVG format, so that I can modify it using Adobe Illustrator later on. As it stands, although I can draw a rectangle directly in the print method and export that successfully to SVG format.
When I draw the same rectangle in my getTagCloud method, the result (when printed to SVG) is a rectangle made up of a huge number of tiny rectangles. I am at a loss as to why this might be so, though hopefully the answer will be blindingly obvious to someone reading this!
Ultimately, I need to print out more than just a rectangle but the exported "Group" in Illustrator is so large (containing as it does all these tiny rectangles of varying sizes) that I am unable to find the other objects I have drawn (as everything, no matter what colour I originally used, is rendered in black). Any help would be greatly appreciated.
Below is the relevant code. I have not included the import statements as I don't have any problems compiling the code.
public class TagCloud {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
System.out.println("Created GUI on EDT? "+
SwingUtilities.isEventDispatchThread());
JFrame f = new JFrame("Tag Cloud Generator");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MyPanel myPanel = new MyPanel();
Toolkit tk = f.getToolkit();
Dimension wndSize = tk.getScreenSize();
f.setBounds(0, 0,
wndSize.width, wndSize.height);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {System.exit(0);}
});
f.add("Center",myPanel);
f.pack();
f.setVisible(true);
myPanel.printTagCloud();
}
}
class MyPanel extends JPanel implements Printable{
private int squareX = 50;
private int squareY = 50;
private int squareW = 20;
private int squareH = 20;
private int x_offset = 30;
private int y_offset = 30;
private BufferedImage img = null;
private int defaultFontSize = 16;
public int print(Graphics g, PageFormat pf, int page) throws
PrinterException {
if (page > 0) { /* We have only one page, and 'page' is zero-based */
return NO_SUCH_PAGE;
}
Graphics2D g2d = (Graphics2D)g;
g2d.translate(pf.getImageableX(), pf.getImageableY());
if (img == null){
getTagCloudImage();
}
g.setColor(Color.red);
g.fillRect(0, 0, 250, 400);
//g.drawImage(img, 0, 0, null);
g.dispose();
return PAGE_EXISTS;
}
public void printTagCloud(){
PrinterJob job = PrinterJob.getPrinterJob();
PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet();
PageFormat pf = job.pageDialog(aset);
job.setPrintable(this);
boolean ok = job.printDialog(aset);
if (ok) {
try {
job.print(aset);
} catch (PrinterException ex) {
}
}
}
public MyPanel() {
setBorder(BorderFactory.createLineBorder(Color.black));
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
moveSquare(e.getX(),e.getY());
}
});
addMouseMotionListener(new MouseAdapter() {
public void mouseDragged(MouseEvent e) {
moveSquare(e.getX(),e.getY());
}
});
}
private void moveSquare(int x, int y) {
int OFFSET = 1;
if ((squareX!=x) || (squareY!=y)) {
repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
squareX=x;
squareY=y;
repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
}
}
public Dimension getPreferredSize() {
return new Dimension(1000,800);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (img == null){
getTagCloudImage();
}
else{
g.drawImage(img, x_offset, y_offset, null);
}
}
public void getTagCloudImage(){
img = new BufferedImage(250, 250, BufferedImage.TYPE_INT_RGB);
Graphics g = img.getGraphics();
Graphics2D g2 = (Graphics2D)g;
Rectangle r = new Rectangle (0,0,250,250);
g2.draw(r);
g2.setColor(Color.black);
g2.fill(r);
g2.dispose();
}
I can't help much without knowledge about Java BufferedImage,Graphics, PrinterJob and other related classes implementation. (You may start a bounty for this question to possibly draw attention of people having more intristic knowledge about java awt graphic stuff).
As you obviously must have noticed using BufferedImage (or not using it) is what makes a difference in SVG output. In the version that works you draw the rectangle directly on Graphic context provided to you as print() method argument and I believe that's how it was designed to be used by authors of Printable interface and printing framework.
In the second approach (that doesn't work correct) you first draw rectangle onto new BufferedImage object and then draw this image on the provided Graphic context. So you do something much less straightforward than just drawing directly on the context. There is well known truth or intuition among developers that the less straightforward way do you use some API, the bigger is a chance that you do something unexpected by its authors :(.
My hypotesis is following: BufferedImage is (as you can deduce from its Javadocs)just a raster image, ie. grid of pixels. That's why svg file is populated with lots of small rectangles (trying to mimic pixels). The Graphics object provided by draw() method may be more abstract and operate on shapes rather than pixels, which is much more appropriate to be written to vector graphics formats like SVG. But that's just hypotesis.
The question is, do you really need to use BufferedImage? If I understand correctly, you want user to be able to edit rectangle on screen and when ready export it to SVG. Can't you just remember for example upper left corner and dimensions of rectangle edited by user and then use this data to recreate this rectangle directly on Graphics object provided by print(), like:
public int print(Graphics g, PageFormat pf, int page)
throws PrinterException {
...
g.fillRect(userRect.x,userRect.y,userRect.width,userRect.height);
...
}
?
(userRect is object of your own custom class which just stores data about image edited by user)
I want to load an image from my computer into 2D Graphics so that I can edit it afterwards and then I want to add it to JPanel. If you need to see my project I can send it to you.
void loadImage()
{
FileDialog fd = new FileDialog(new Frame(), "Please choose a file:", FileDialog.LOAD);
fd.show();
if (fd.getFile() != null)
{
File fil = new File(fd.getDirectory(), fd.getFile());
strDirectory = fd.getDirectory();
strFileType = fd.getFile();
mainImage.setIcon(new ImageIcon(fil.toString()));
getFileList(strDirectory);
checkFileType(strFileType);
}
}
Thanks in advance
To load your image into the memory, you can use ImageIO.read(File). To edit it afterwards, obtain a Graphics2D instance from it by calling createGraphics():
BufferedImage img = ImageIO.read(yourFile);
Graphics2D g = img.createGraphics();
// Draw here on the graphics
g.dispose();
You can even turn on anti-alias by setting a RenderingHint before drawing:
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIASING_ON);
Then, to add it to a JPanel, create your custom JComponent and add an instance of that component to your JPanel:
public class JImageComponent extends JComponent
{
private BufferedImage img;
public JImageComponent(BufferedImage bi)
{
img = bi;
}
#Override
public void paintComponent(Graphics g)
{
g.drawImg(img, 0, 0, this);
}
}
please read this tutorials about Icon in Swing and your Image/ImageIcon would by placed to the JLabel, this way eliminated all troubles came from paint/paintComponents ...
For image loading you should use ImageIO object with method read(File file) see docs. Then you will get BufferedImage instance of which you can make your changes through Graphics2D instance which you'll obtain by calling createGraphics() on the image instance see docs. Last thing, override method paintComponent() from JPanel or better JComponent see docs and there you can draw your image on Graphics instance which you'll get as parameter in paintComponent(Graphics g) method by calling drawImage(Image img, int x, int y, ImageObserver observer) see docs where ImageObserver set to null.