For a class project I and a few others are working on an isometric game. Everything is being drawn in one JPanel using a buffered image. Each piece of artwork is done in fireworks and saved as a .png.
On Linux, the time it takes to redraw the map every game tick is around 3ms. On Windows (and also OSx) it's around 100ms spiking to 500ms.
This effect has been observed on 4 different computers ranging from typical laptop to an i7-3770K + 660 gaming machine. The CPU usage when this occurs is around 10-20% with RAM usage of the program being around 1GB. The problem has been researched on the internet in many places to no avail and also our section leaders (who are in charge of the project) were stumped. Any ideas?
Here is the paint component in the JPanel and the setEntityImage method. You can see where the time stamps are pulled from at the bottom of the paintComponent. The setEntityImage is being set every 500ms with a new BufferedImage that is pre-drawn on another thread before being passed into this JPanel. I'm including that code as I'm also curious if it's a possible threading issue.
This code is called every 500 ms to consist of a 'game tick', it draws the images (which are all pre-loaded in a static singleton class).
BufferedImage entityImg = ImageUtil.getFromGraphics(map.getWidth() * TILE_WIDTH
+ TILE_WIDTH, map.getHeight() * TILE_HEIGHT + TILE_HEIGHT);
Graphics2D g2 = (Graphics2D) entityImg.getGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
for (int i = 0; i < map.getHeight(); i++)
{
for (int j = 0; j < map.getWidth(); j++)
{
try
{
int x, y;
x = (j * TILE_WIDTH_HALF - i * TILE_WIDTH_HALF)
+ map.getWidth() * TILE_WIDTH_HALF;
y = j * TILE_HEIGHT_HALF + i * TILE_HEIGHT_HALF;
Entity e = map.getTile(i, j).getEntity();
if (e != null)
{
g2.drawImage(e.getImage(), x, y, 32, 32, null);
}
}
catch (Exception e)
{
}
}
}
panel.setEntityImage(entityImg);
This code is in our MapPanel which extends a JPanel.
public void setEntityImage(BufferedImage entityImg)
{
this.entityImg = entityImg;
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics g2 = (Graphics2D) g;
g2.fillRect(0, 0, tileImg.getWidth(), tileImg.getHeight());
// Draw tiles
g2.drawImage(tileImg, 0, 0, null);
// Draw box over square mouse is hovering over
if (hover)
{
g2.setColor(new Color(255, 255, 255, 128));
int screenX = (this.x * 16 - this.y * 16) + Window.WIDTH * 16;
int screenY = (this.x * 8 + this.y * 8) + 16;
g2.drawLine(screenX, screenY + 8, screenX + 16, screenY);
g2.drawLine(screenX + 16, screenY, screenX + 32, screenY + 8);
g2.drawLine(screenX, screenY + 8, screenX + 16, screenY + 16);
g2.drawLine(screenX + 16, screenY + 16, screenX + 32, screenY + 8);
}
long time = System.currentTimeMillis();
g2.drawImage(entityImg, 0, 0, null);
System.out.println(System.currentTimeMillis() - time);
}
There's some kind of internal optimisation caching going, the particulars I'm not aware of. Essentially, if the BufferedImage is not updated, it paints in 0ms.
I did two optimizations, not sure if either work alone or not but it made a difference in the performance, but not in the time delay (ie, When I dragged the map about, it was significantly better)
The first was in Window#tickUpdate. Rather the creating a new BufferedImage every 500ms, this simply re-uses a single BufferedImage only creating a new instance if there wasn't a previous instance or the size has changed...
public void tickUpdate(Map map) {
int width = map.getWidth() * TILE_WIDTH + TILE_WIDTH;
int height = map.getHeight() * TILE_HEIGHT + TILE_HEIGHT;
if (entityImg == null || entityImg.getWidth() != width || entityImg.getHeight() != height) {
entityImg = ImageUtil.getFromGraphics(width, height);
}
Graphics2D g2 = (Graphics2D) entityImg.getGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
g2.setBackground(new Color(255, 255, 255, 0));
g2.clearRect(0, 0, width, height);
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
for (int i = 0; i < map.getHeight(); i++) {
for (int j = 0; j < map.getWidth(); j++) {
Entity e = map.getTile(i, j).getEntity();
if (e != null) {
int x, y;
x = (j * TILE_WIDTH_HALF - i * TILE_WIDTH_HALF)
+ map.getWidth() * TILE_WIDTH_HALF;
y = j * TILE_HEIGHT_HALF + i * TILE_HEIGHT_HALF;
g2.drawImage(e.getImage(), x, y, 32, 32, null);
}
}
}
g2.dispose();
panel.setEntityImage(entityImg);
panel.repaint();
}
And because the BufferedImage could be updated while a paint was occurring, in the MapPanel, I created a another BufferedImage onto which the "entity" image was painted.
public void setEntityImage(BufferedImage img) {
entityLock.lock();
try {
if (entityImg == null || entityImg.getWidth() != img.getWidth() || entityImg.getHeight() != img.getHeight()) {
entityImg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
}
Graphics2D g2d = entityImg.createGraphics();
g2d.drawImage(img, 0, 0, null);
g2d.dispose();
} finally {
entityLock.unlock();
}
}
entitytLock is just a ReentrantLock which I used to stop the paintComponent method from trying to paint the BufferedImage while it was been updated.
This doesn't change the timing, but made it, visually, better...
An alternative is to use a VolatileImage...
Related
I'm new at Java and I'm trying to make a chess with it for training.
I'm having some trouble with the Graphic methods though.
I listed the functions which the mistake could have been in.
The code below shows a function called in main.
This function is inside my package that handle Graphic stuff.
public class GUI_main extends JComponent{
private final int tam = 100;
private final int tamTab = 8 * tam;
Image torre;
public void inicializaTabuleiro(Torre t)
{ //Sets the window properties and the "tile" proper image.
JFrame janela = new JFrame("Xadrez");
janela.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
janela.setSize(816, 839);
janela.getContentPane().add(new GUI_main());
janela.setLocationRelativeTo(null);
janela.setResizable(false);
janela.setVisible(true);
paintPeca(t); /*this one is the function that sets the correct image
for the tile that will be on the board*/
System.out.println("Janela inicializada com sucesso!");
}
...
next one is the "paintPeca" function, which should initialize "Image torre".
public void paintPeca(Torre t)
{
System.out.println(t.imgPeca());
torre = Toolkit.getDefaultToolkit().getImage(t.imgPeca());
}
and finally my paint function:
public void paint(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
g2.setPaint(Color.black);
int x = 0, y = 0;
while(y<tamTab) //draw board
{
if(y%200 == 0)
{
g2.draw(new Rectangle2D.Double(x, y, tam, tam));
x = x + tam;
g2.fill(new Rectangle2D.Double(x, y, tam, tam));
x = x + tam;
}
else
{
g2.fill(new Rectangle2D.Double(x, y, tam, tam));
x = x + tam;
g2.draw(new Rectangle2D.Double(x, y, tam, tam));
x = x + tam;
}
if(x == tamTab)
{
y = y + tam;
x = 0;
}
}
x = 14;
y = 19;
g2.drawImage(torre, x, y, this); //draw tower
g2.finalize();
...
Together these functions should have draw a board and a tower on it.
It's showing me the board, but not the tower (Image torre), and I do not have a clue on the reason.
"t.imgPeca()" returns a string (path to image), and it's correct, as I have tested before.
Since I'm new at Java this might be some kind of of silly mistake, but I have not found the answer in other pages of stack that talked about the drawImage function.
In my game I have a gamepanel which draws my map layers: 'ground/buildings/objects layer', then player sprite, then enemies/npcs/mobs, then 'above layer(tiles to draw above player)'. This was working great and running smoothly.
I then started to work on a minimap JInternalFrame. It actually looks great for what I need but I am concerned with performance. After adding the minimap I noted some slowdown of painting. My biggest resolution supports a map of:
else if (scrnsize.width >= 1440 && scrnsize.height >= 1024){ //large&wide
//45x29(32x32px tiles)
//1440, 1024
Basically my question is, is there a better way I can do this(than below code) or methods I can call for offscreen buffer or something?
This is the Minimap code. As you can see I have logic in the paintComponent to not redraw unless the refreshMinimap == true(the player moves or dies). This helped get rid of most noticable lag, but I still am noticing some. Any help would be super appreciated.
public MinimapGamePanel() {
super();
logger.addAppender(GUILog4JFileHelper.fileAppender);
gamePanelImage = new BufferedImage(32 * MyClient.xTiles, 32 * MyClient.yTiles, BufferedImage.TYPE_INT_ARGB);
setLayout(new BorderLayout());
setBorder(BorderFactory.createLineBorder(Color.black));
}
public void paintComponent(Graphics g) {
logger.trace("begin: REPAINTNG...");
g2 = (Graphics2D)g;
//map
if (refreshMinimap){
RefreshMinimap();
refreshMinimap = false;
}
g2.drawImage(gamePanelImage, 0, 0, null);
g2.dispose();
logger.trace("end: REPAINTNG...");
}
private void RefreshMinimap() {
logger.trace("Map drawing started.");
int count = (int) ((MyClient.characterX - (MyClient.xTiles*1.5)) + ((MyClient.characterY - (MyClient.yTiles*2)) * MyClient.mapWidth));
for (int x = 0; x < MyClient.xTiles*3; x++){
for (int y = 0; y < MyClient.yTiles*4; y++){
if (count > -1 && count < (MyClient.mapWidth * MyClient.mapHeight)){
if (!MyClient.groundLayer[count].equals("0")){ //don't draw full transparent tiles
//SpriteStore.get().getSprite("images/tiles/" + MyClient.groundLayer[count] + ".png").draw(gamePanelImage, x, y);
SpriteStore.get().getSprite("images/tiles/" + MyClient.groundLayer[count] + ".png").drawFirstPixel(gamePanelImage, x, y);
}
if (!MyClient.buildingLayer[count].equals("0")){ //don't draw full transparent tiles
SpriteStore.get().getSprite("images/tiles/" + MyClient.buildingLayer[count] + ".png").drawFirstPixel(gamePanelImage, x, y);
}
if (!MyClient.objectLayer[count].equals("0")){ //don't draw full transparent tiles
SpriteStore.get().getSprite("images/tiles/" + MyClient.objectLayer[count] + ".png").drawFirstPixel(gamePanelImage, x, y);
}
} else {
SpriteStore.get().getSprite("images/tiles/" + MyClient.groundLayer[0] + ".png").drawFirstPixel(gamePanelImage, x, y);
}
count += MyClient.mapWidth;
}
count -= MyClient.yTiles * 4 * MyClient.mapWidth;
count++;
}
logger.trace("Map drawing done.");
}
This is the drawing code which just draws pixel 0,0
public void drawFirstPixel(BufferedImage gamePanelImage, int xDraw, int yDraw) {
BufferedImage bufferedVersion = (BufferedImage) image;
gamePanelImage.getGraphics().drawImage(bufferedVersion.getSubimage(0, 0, 1, 1), xDraw, yDraw, null);
}
I am actually pretty happy with the look of just taking pixel 0,0. Apologies for the bad quality, shaky cell phone pic.
After adding the minimap I noted some slowdown of painting
SpriteStore.get().getSprite("images/tiles/" + MyClient.buildingLayer
Don't do I/O in a painting method. All images should be read into memory when you create your class.
Is this in tune to what you guys were suggesting I do?
public class MinimapGamePanel extends JPanel {
...
private static Thread t;
public MinimapGamePanel() {
super();
logger.addAppender(GUILog4JFileHelper.fileAppender);
gamePanelImage = new BufferedImage(320, 320, BufferedImage.TYPE_INT_ARGB);
//TODO:: change values above upon picking minimap size
setLayout(new BorderLayout());
setBorder(BorderFactory.createLineBorder(Color.black));
t = new Thread(new RefreshMinimapThread());
}
public void paintComponent(Graphics g) {
logger.trace("begin: REPAINTNG...");
g2 = (Graphics2D)g;
if (refreshMinimap){
t.start();
//RefreshMinimap();
refreshMinimap = false;
}
g2.drawImage(gamePanelImage, 0, 0, null);
g2.dispose();
logger.trace("end: REPAINTNG...");
}
private class RefreshMinimapThread implements Runnable {
#Override
public void run() {
RefreshMinimap();
}
}
In the script it is going from around the 300x300 mark down to 60x60. Need to improve the overall image quality as it is coming out very poorly at the moment.
public static Boolean resizeImage(String sourceImg, String destImg, Integer Width, Integer Height, Integer whiteSpaceAmount)
{
BufferedImage origImage;
try
{
origImage = ImageIO.read(new File(sourceImg));
int type = origImage.getType() == 0? BufferedImage.TYPE_INT_ARGB : origImage.getType();
int fHeight = Height;
int fWidth = Width;
int whiteSpace = Height + whiteSpaceAmount; //Formatting all to squares so don't need two whiteSpace calcs..
double aspectRatio;
//Work out the resized dimensions
if (origImage.getHeight() > origImage.getWidth()) //If the pictures height is greater than the width then scale appropriately.
{
fHeight = Height; //Set the height to 60 as it is the biggest side.
aspectRatio = (double)origImage.getWidth() / (double)origImage.getHeight(); //Get the aspect ratio of the picture.
fWidth = (int)Math.round(Width * aspectRatio); //Sets the width as created via the aspect ratio.
}
else if (origImage.getHeight() < origImage.getWidth()) //If the pictures width is greater than the height scale appropriately.
{
fWidth = Width; //Set the height to 60 as it is the biggest side.
aspectRatio = (double)origImage.getHeight() / (double)origImage.getWidth(); //Get the aspect ratio of the picture.
fHeight = (int)Math.round(Height * aspectRatio); //Sets the height as created via the aspect ratio.
}
int extraHeight = whiteSpace - fHeight;
int extraWidth = whiteSpace - fWidth;
BufferedImage resizedImage = new BufferedImage(whiteSpace, whiteSpace, type);
Graphics2D g = resizedImage.createGraphics();
g.setColor(Color.white);
g.fillRect(0, 0, whiteSpace, whiteSpace);
g.setComposite(AlphaComposite.Src);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.drawImage(origImage, extraWidth/2, extraHeight/2, fWidth, fHeight, null);
g.dispose();
ImageIO.write(resizedImage, "jpg", new File(destImg));
}
catch (IOException ex)
{
return false;
}
return true;
}
Really just need to know if their is something I can plug in that will bump up the quality or if I need to look at something else entirely.
EDIT: Picture comparison.
Source, just picked a random washing machine from google.
http://www.essexappliances.co.uk/images/categories/washing-machine.jpg
The same picture converted in Photoshop to what I need it to be.
http://imgur.com/78B1p
What it looks like being converted like this.
http://imgur.com/8WlXD
Scaling an image down over a large range is inherently dangerous (from the point of view of quality), especially using a single step.
The recommended method is to use a divide and conquer method. Basically, you scale the image down in steps of 50% until you reach your desired size.
So, I took the original image of 650x748 and scaled it down to fit within a 60x60 region (52x60).
Divide and conquer compared to one step...
public class TestImageResize {
public static void main(String[] args) {
new TestImageResize();
}
public TestImageResize() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new ScalePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class ScalePane extends JPanel {
private BufferedImage original;
private BufferedImage scaled;
public ScalePane() {
try {
original = ImageIO.read(new File("path/to/master.jpg"));
scaled = getScaledInstanceToFit(original, new Dimension(60, 60));
ImageIO.write(scaled, "jpg", new File("scaled.jpg"));
BufferedImage image = new BufferedImage(52, 60, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = image.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.drawImage(original, 0, 0, 52, 60, this);
g2d.dispose();
ImageIO.write(image, "jpg", new File("test.jpg"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
if (original != null) {
if (scaled != null) {
size.width = original.getWidth() + scaled.getWidth();
size.height = original.getHeight();
} else {
size.width = original.getWidth();
size.height = original.getHeight();
}
}
return size;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if (original != null) {
int x = 0;
int y = (getHeight() - original.getHeight()) / 2;;
if (scaled != null) {
x = (getWidth() - (original.getWidth() + scaled.getWidth())) / 2;
} else {
x = (getWidth() - original.getWidth()) / 2;
}
g2d.drawImage(original, x, y, this);
if (scaled != null) {
x += original.getWidth();
y = (getHeight() - scaled.getHeight()) / 2;
g2d.drawImage(scaled, x, y, this);
}
}
g2d.dispose();
}
public BufferedImage getScaledInstanceToFit(BufferedImage img, Dimension size) {
float scaleFactor = getScaleFactorToFit(img, size);
return getScaledInstance(img, scaleFactor);
}
public float getScaleFactorToFit(BufferedImage img, Dimension size) {
float scale = 1f;
if (img != null) {
int imageWidth = img.getWidth();
int imageHeight = img.getHeight();
scale = getScaleFactorToFit(new Dimension(imageWidth, imageHeight), size);
}
return scale;
}
public float getScaleFactorToFit(Dimension original, Dimension toFit) {
float scale = 1f;
if (original != null && toFit != null) {
float dScaleWidth = getScaleFactor(original.width, toFit.width);
float dScaleHeight = getScaleFactor(original.height, toFit.height);
scale = Math.min(dScaleHeight, dScaleWidth);
}
return scale;
}
public float getScaleFactor(int iMasterSize, int iTargetSize) {
float scale = 1;
if (iMasterSize > iTargetSize) {
scale = (float) iTargetSize / (float) iMasterSize;
} else {
scale = (float) iTargetSize / (float) iMasterSize;
}
return scale;
}
public BufferedImage getScaledInstance(BufferedImage img, double dScaleFactor) {
BufferedImage imgBuffer = null;
imgBuffer = getScaledInstance(img, dScaleFactor, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true);
return imgBuffer;
}
protected BufferedImage getScaledInstance(BufferedImage img, double dScaleFactor, Object hint, boolean higherQuality) {
int targetWidth = (int) Math.round(img.getWidth() * dScaleFactor);
int targetHeight = (int) Math.round(img.getHeight() * dScaleFactor);
int type = (img.getTransparency() == Transparency.OPAQUE)
? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = (BufferedImage) img;
if (targetHeight > 0 || targetWidth > 0) {
int w, h;
if (higherQuality) {
w = img.getWidth();
h = img.getHeight();
} else {
w = targetWidth;
h = targetHeight;
}
do {
if (higherQuality && w > targetWidth) {
w /= 2;
if (w < targetWidth) {
w = targetWidth;
}
}
if (higherQuality && h > targetHeight) {
h /= 2;
if (h < targetHeight) {
h = targetHeight;
}
}
BufferedImage tmp = new BufferedImage(Math.max(w, 1), Math.max(h, 1), type);
Graphics2D g2 = tmp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
} while (w != targetWidth || h != targetHeight);
} else {
ret = new BufferedImage(1, 1, type);
}
return ret;
}
}
}
You may, also, find The Perils of Image.getScaledInstance() of interest.
The issue you are seeing is actually related to the resampling filter used for downscaling. Obviously, the one used by your library is a bad one for the situation. Nearest neighbor, bilinear and bicubic are typical bad examples to be used when downscaling. I don't know the exact resampling filter Photoshop uses, but I used 3-lobed lanczos and got the following result:
So, to solve your problem, you need to use a smarter resampling filter.
dutchman, this is why I maintain the imgscalr library -- to make this kind of stuff painfully easy.
In your example, a single method call would do the trick, right after your first ImageIO.read line:
origImage = ImageIO.read(new File(sourceImg));
you can do the following to get what you want (javadoc for this method):
origImage = Scalr.resize(origImage, Method.ULTRA_QUALITY, 60);
and if that still looked a little jagged (because you are removing so much information from the image, you can add the following OP to the command to apply a light anti-aliasing filter to the image so it looks smoother):
origImage = Scalr.resize(origImage, Method.ULTRA_QUALITY, 60, Scalr.OP_ANTIALIAS);
That will replace all the remainder of the code logic you have. The only other thing I would recommend is saving out your really small samples as PNG's so there is no more compression/lossy conversion done on the image OR make sure you use little to none compression on the JPG if you really want it in JPG format. (Here is an article on how to do it; it utilizes the ImageWriteParam class)
imgscalr is licensed under an Apache 2 license and hosted on GitHub so you can do what you want with it; it also includes asynchronous scaling support if you are using the library in a server-side app and queuing up huge numbers of scaling operations and don't want to kill the server.
As already stated, Java's Graphics2D does not provide a very good algorithm for down-scaling. If you don't want to implement a sophisticated algorithm yourself you could try out the current open source libs specialized for this: Thumbnailator, imgscalr and a Java interface for ImageMagick.
While researching for a private project I tried them out (except ImageMagick) and here are the visual results with Photoshop as reference:
A. Thumbnailator 0.4.8 with default settings (no additional internal resizing)
B. imgscalr 4.2 with ULTRA_QUALTY setting
C. Photoshop CS5 bicubic filter (save for web)
D. Graphics2d with all HQ render hints
Here is the used code
Thumbnailator and PS create similar results, while imgscalr seems to be softer. It is subjective which one of the libs creates the preferable results. Another point to consider though is the performance. While Thumbnailator and Graphics2d have similar runtime, imgscalr is considerably slower (with ULTRA_QUALITY) in my benchmarks.
For more info, read this post providing more detail on this matter.
I've created a function to where I can click somewhere in a Jpanel and it draws a shape at the position where the mouse clicked. The problem I am having is when I click in a new position, it moves the shape and redraws it. I would like the previous shape to "Burn" into the screen and stay there. It doesn't have to have any data tied to it, I just want the image of the shape to show where it used to be each time. I have tried many different things, but no success. here is what I mean:
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.BLUE);
g2.fillRect(n, m, 32, 32); //I want each one of these shapes to be new, not
//moving, but redrawn
////////////////////////////////////////////////
//This is just drawing a grid and doing other things(irrelevant)
g2.fill(new Ellipse2D.Double(x, y, 32, 32));
for (int i = 0; i < 500; i += 32) {
g2.drawRect(i, j, 32, 32);
for (int j = 0; j < 500; j += 32) {
g2.drawRect(i, j, 32, 32);
}
}
if (paintColBlock){
System.out.println("Drawing Block at " + n +"," + m);
paintColBlock = false;
}
/////////////////////////////////////////////////////////////////////
}
Keep an ArrayList of Points like this:
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.BLUE);
for(Point p : points)
g2.fillRect(p.x, p.y, 32, 32);
Adding a new Point to the array at each mouse click, and call repaint():
public void mousePressed(MouseEvent evt){
points.add(new Point(evt.getX(),evt.getY());
repaint();
}
I am having trouble getting a rotated BufferedImage to display. I think the rotation is working just fine, but I can't actually draw it to the screen. My code:
Class extends JPanel {
BufferedImage img;
int rotation = 0;
public void paintComponent(Graphics g) {
g.clearRect(0, 0, getWidth(), getHeight());
img2d = img.createGraphics();
img2d.rotate(Math.toRadians(rotation), img.getWidth() / 2, img.getHeight() / 2);
g.drawImage(img, imgx, imgy, null);
this.repaint();
}
}
This is not working for me. I could not find any way to draw the rotated img2d onto g.
EDIT: I have multiple objects that are being drawn onto g, so I can't rotate that. I need to be able to rotate things individually.
Maybe you should try using AffineTransform like this:
AffineTransform transform = new AffineTransform();
transform.rotate(radians, bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2);
AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);
bufferedImage = op.filter(bufferedImage, null);
Hope this helps.
I would use Graphics2D.drawImage(image, affinetranform, imageobserver).
The code example below rotates and translates an image to the center of the component. This is a screenshot of the result:
public static void main(String[] args) throws IOException {
JFrame frame = new JFrame("Test");
frame.add(new JComponent() {
BufferedImage image = ImageIO.read(
new URL("http://upload.wikimedia.org/wikipedia/en/2/24/Lenna.png"));
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// create the transform, note that the transformations happen
// in reversed order (so check them backwards)
AffineTransform at = new AffineTransform();
// 4. translate it to the center of the component
at.translate(getWidth() / 2, getHeight() / 2);
// 3. do the actual rotation
at.rotate(Math.PI / 4);
// 2. just a scale because this image is big
at.scale(0.5, 0.5);
// 1. translate the object so that you rotate it around the
// center (easier :))
at.translate(-image.getWidth() / 2, -image.getHeight() / 2);
// draw the image
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(image, at, null);
// continue drawing other stuff (non-transformed)
//...
}
});
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
frame.setVisible(true);
}
You are rotating the graphics for drawing into your image, not the image. Thats why you see no effect. Apply the rotation to the graphics you are painting on and it will draw the image rotated:
public void paintComponent(Graphics g) {
g.clearRect(0, 0, getWidth(), getHeight());
g.rotate(Math.toRadians(rotation), img.getWidth() / 2, img.getHeight() / 2);
g.drawImage(img, imgx, imgy, null);
this.repaint();
}
This will probably not draw entirely what you expect, the rotation will revolve around the coordinate origin. For the image to be rotate around its center you need to apply a coordinate translation before the rotation, for example:
g.translate(imgx >> 1, imgy >> 1);
The Graphics2D Tutorial has some more examples.
I know this question is old but I came up with a solution that has some advantages:
creates image of correct size.
correct offset.
does not unnecessarily rotate by 0° or 360°.
works for negative angles (e.g. -90°).
works when input is BufferedImage.TYPE_CUSTOM.
As it is, it is assumed that the angle is a multiple of 90°. The only improvement that one might need is to use an Enum for angle instead of just int.
Here's my code:
public static BufferedImage rotateBufferedImage(BufferedImage img, int angle) {
if (angle < 0) {
angle = 360 + (angle % 360);
}
angle %= 360;
if (angle == 0) {
return img;
}
final boolean r180 = angle == 180;
if (angle != 90 && !r180 && angle != 270)
throw new IllegalArgumentException("Invalid angle.");
final int w = r180 ? img.getWidth() : img.getHeight();
final int h = r180 ? img.getHeight() : img.getWidth();
final int type = img.getType() == BufferedImage.TYPE_CUSTOM ? BufferedImage.TYPE_INT_ARGB : img.getType();
final BufferedImage rotated = new BufferedImage(w, h, type);
final Graphics2D graphic = rotated.createGraphics();
graphic.rotate(Math.toRadians(angle), w / 2d, h / 2d);
final int offset = r180 ? 0 : (w - h) / 2;
graphic.drawImage(img, null, offset, -offset);
graphic.dispose();
return rotated;
}
public static BufferedImage rotateBufferedImage(String img, int angle) throws IOException {
return rotateBufferedImage(Paths.get(img), angle);
}
public static BufferedImage rotateBufferedImage(Path img, int angle) throws IOException {
return rotateBufferedImage(ImageIO.read(img.toFile()), angle);
}