Drawing an image using sub-pixel level accuracy using Graphics2D - java

I am currently attempting to draw images on the screen at a regular rate like in a video game.
Unfortunately, because of the rate at which the image is moving, some frames are identical because the image has not yet moved a full pixel.
Is there a way to provide float values to Graphics2D for on-screen position to draw the image, rather than int values?
Initially here is what I had done:
BufferedImage srcImage = sprite.getImage ( );
Position imagePosition = ... ; //Defined elsewhere
g.drawImage ( srcImage, (int) imagePosition.getX(), (int) imagePosition.getY() );
This of course thresholds, so the picture doesn't move between pixels, but skips from one to the next.
The next method was to set the paint color to a texture instead and draw at a specified position. Unfortunately, this produced incorrect results that showed tiling rather than correct antialiasing.
g.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
BufferedImage srcImage = sprite.getImage ( );
g.setPaint ( new TexturePaint ( srcImage, new Rectangle2D.Float ( 0, 0, srcImage.getWidth ( ), srcImage.getHeight ( ) ) ) );
AffineTransform xform = new AffineTransform ( );
xform.setToIdentity ( );
xform.translate ( onScreenPos.getX ( ), onScreenPos.getY ( ) );
g.transform ( xform );
g.fillRect(0, 0, srcImage.getWidth(), srcImage.getHeight());
What should I do to achieve the desired effect of subpixel rendering of an Image in Java?

You can use a BufferedImage and AffineTransform, draw to the buffered image, then draw the buffered image to the component in the paint event.
/* overrides the paint method */
#Override
public void paint(Graphics g) {
/* clear scene buffer */
g2d.clearRect(0, 0, (int)width, (int)height);
/* draw ball image to the memory image with transformed x/y double values */
AffineTransform t = new AffineTransform();
t.translate(ball.x, ball.y); // x/y set here, ball.x/y = double, ie: 10.33
t.scale(1, 1); // scale = 1
g2d.drawImage(image, t, null);
// draw the scene (double percision image) to the ui component
g.drawImage(scene, 0, 0, this);
}
Check my full example here: http://pastebin.com/hSAkYWqM

You can composite the image yourself using sub-pixel accuracy, but it's more work on your part. Simple bilinear interpolation should work well enough for a game. Below is psuedo-C++ code for doing it.
Normally, to draw a sprite at location (a,b), you'd do something like this:
for (x = a; x < a + sprite.width; x++)
{
for (y = b; y < b + sprite.height; y++)
{
*dstPixel = alphaBlend (*dstPixel, *spritePixel);
dstPixel++;
spritePixel++;
}
dstPixel += destLineDiff; // Move to start of next destination line
spritePixel += spriteLineDiff; // Move to start of next sprite line
}
To do sub-pixel rendering, you do the same loop, but account for the sub-pixel offset like so:
float xOffset = a - floor (a);
float yOffset = b - floor (b);
for (x = floor(a), spriteX = 0; x < floor(a) + sprite.width + 1; x++, spriteX++)
{
for (y = floor(b), spriteY = 0; y < floor (b) + sprite.height + 1; y++, spriteY++)
{
spriteInterp = bilinearInterp (sprite, spriteX + xOffset, spriteY + yOffset);
*dstPixel = alphaBlend (*dstPixel, spriteInterp);
dstPixel++;
spritePixel++;
}
dstPixel += destLineDiff; // Move to start of next destination line
spritePixel += spriteLineDiff; // Move to start of next sprite line
}
The bilinearInterp() function would look something like this:
Pixel bilinearInterp (Sprite* sprite, float x, float y)
{
// Interpolate the upper row of pixels
Pixel* topPtr = sprite->dataPtr + ((floor (y) + 1) * sprite->rowBytes) + floor(x) * sizeof (Pixel);
Pixel* bottomPtr = sprite->dataPtr + (floor (y) * sprite->rowBytes) + floor (x) * sizeof (Pixel);
float xOffset = x - floor (x);
float yOffset = y - floor (y);
Pixel top = *topPtr + ((*(topPtr + 1) - *topPtr) * xOffset;
Pixel bottom = *bottomPtr + ((*(bottomPtr + 1) - *bottomPtr) * xOffset;
return bottom + (top - bottom) * yOffset;
}
This should use no additional memory, but will take additional time to render.

I successfully solved my problem after doing something like lawrencealan proposed.
Originally, I had the following code, where g is transformed to a 16:9 coordinate system before the method is called:
private void drawStar(Graphics2D g, Star s) {
double radius = s.getRadius();
double x = s.getX() - radius;
double y = s.getY() - radius;
double width = radius*2;
double height = radius*2;
try {
BufferedImage image = ImageIO.read(this.getClass().getResource("/images/star.png"));
g.drawImage(image, (int)x, (int)y, (int)width, (int)height, this);
} catch (IOException ex) {
Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex);
}
}
However, as noted by the questioner Kaushik Shankar, turning the double positions into integers makes the image "jump" around, and turning the double dimensions into integers makes it scale "jumpy" (why the hell does g.drawImage not accept doubles?!). What I found working for me was the following:
private void drawStar(Graphics2D g, Star s) {
AffineTransform originalTransform = g.getTransform();
double radius = s.getRadius();
double x = s.getX() - radius;
double y = s.getY() - radius;
double width = radius*2;
double height = radius*2;
try {
BufferedImage image = ImageIO.read(this.getClass().getResource("/images/star.png"));
g.translate(x, y);
g.scale(width/image.getWidth(), height/image.getHeight());
g.drawImage(image, 0, 0, this);
} catch (IOException ex) {
Logger.getLogger(View.class.getName()).log(Level.SEVERE, null, ex);
}
g.setTransform(originalTransform);
}
Seems like a stupid way of doing it though.

Change the resolution of your image accordingly, there's no such thing as a bitmap with sub-pixel coordinates, so basically what you can do is create an in memory image larger than what you want rendered to the screen, but allows you "sub-pixel" accuracy.
When you draw to the larger image in memory, you copy and resample that into the smaller render visible to the end user.
For example: a 100x100 image and it's 50x50 resized / resampled counterpart:
See: http://en.wikipedia.org/wiki/Resampling_%28bitmap%29

Related

how to rotate an image by "alpha" degrees in java language [duplicate]

I need to be able to rotate images individually(in java). The only thing I have found so far is g2d.drawImage(image, affinetransform, ImageObserver ). Unfortunately, I need to draw the image at a specific point, and there is no method with an argument that 1.rotates the image separately and 2. allows me to set the x and y. any help is appreciated
This is how you can do it. This code assumes the existance of a buffered image called 'image' (like your comment says)
// The required drawing location
int drawLocationX = 300;
int drawLocationY = 300;
// Rotation information
double rotationRequired = Math.toRadians (45);
double locationX = image.getWidth() / 2;
double locationY = image.getHeight() / 2;
AffineTransform tx = AffineTransform.getRotateInstance(rotationRequired, locationX, locationY);
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
// Drawing the rotated image at the required drawing locations
g2d.drawImage(op.filter(image, null), drawLocationX, drawLocationY, null);
AffineTransform instances can be concatenated (added together). Therefore you can have a transform that combines 'shift to origin', 'rotate' and 'shift back to desired position'.
A simple way to do it without the use of such a complicated draw statement:
//Make a backup so that we can reset our graphics object after using it.
AffineTransform backup = g2d.getTransform();
//rx is the x coordinate for rotation, ry is the y coordinate for rotation, and angle
//is the angle to rotate the image. If you want to rotate around the center of an image,
//use the image's center x and y coordinates for rx and ry.
AffineTransform a = AffineTransform.getRotateInstance(angle, rx, ry);
//Set our Graphics2D object to the transform
g2d.setTransform(a);
//Draw our image like normal
g2d.drawImage(image, x, y, null);
//Reset our graphics object so we can draw with it again.
g2d.setTransform(backup);
I struggled a little with the existing answers because my image to be rotated is not always a square, furthermore the accepted answer has a comment asking "Any info on how to circumvent the cutoff problem" that is not answered.
So for those who had the issue of image being croped when rotated here is the code that worked for me :
public static BufferedImage rotate(BufferedImage bimg, Double angle) {
double sin = Math.abs(Math.sin(Math.toRadians(angle))),
cos = Math.abs(Math.cos(Math.toRadians(angle)));
int w = bimg.getWidth();
int h = bimg.getHeight();
int neww = (int) Math.floor(w*cos + h*sin),
newh = (int) Math.floor(h*cos + w*sin);
BufferedImage rotated = new BufferedImage(neww, newh, bimg.getType());
Graphics2D graphic = rotated.createGraphics();
graphic.translate((neww-w)/2, (newh-h)/2);
graphic.rotate(Math.toRadians(angle), w/2, h/2);
graphic.drawRenderedImage(bimg, null);
graphic.dispose();
return rotated;
}
public static BufferedImage rotateCw( BufferedImage img )
{
int width = img.getWidth();
int height = img.getHeight();
BufferedImage newImage = new BufferedImage( height, width, img.getType() );
for( int i=0 ; i < width ; i++ )
for( int j=0 ; j < height ; j++ )
newImage.setRGB( height-1-j, i, img.getRGB(i,j) );
return newImage;
}
from https://coderanch.com/t/485958/java/Rotating-buffered-image
Here is a solution for rotations of 90, 180 & 270 degrees.
For these cases, the AffineTransform can introduce some loss/interpolation.
This solution is lossless & can also handle (esoteric?) Colour Models with more than 8 Bits/Pixel which BufferedImage.getRGB(int x, int y) cannot.
The solution proceeds on a pixel-by-pixel basis, which has the advantage of being simple to code.
It is possible to read the original Image row-by-row to gain performance, but its more complex, so I've left that out.
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
/**
* N.B. this example uses the new switch/case/Arrow notation, which requires Java 14.
*/
public enum Rotation {
CLOCKWISE_90,
CLOCKWISE_180,
CLOCKWISE_270;
public BufferedImage rotate(final BufferedImage original) {
final int oW = original.getWidth();
final int oH = original.getHeight();
final BufferedImage rotated =
switch (this) {
case CLOCKWISE_180 -> new BufferedImage(oW, oH, original.getType());
default -> new BufferedImage(oH, oW, original.getType());
};
final WritableRaster rasterOriginal = original.copyData(null);
final WritableRaster rasterRotated = rotated .copyData(null);
/*
* The Data for 1 Pixel...
*/
final int[] onePixel = new int[original.getSampleModel().getNumBands()];
/*
* Copy the Pixels one-by-one into the result...
*/
for (int x = 0; x < oW; x++) {
for (int y = 0; y < oH; y++) {
; rasterOriginal.getPixel( x, y, onePixel);
switch (this) {
case CLOCKWISE_90 -> rasterRotated .setPixel(oH - 1 - y, x, onePixel);
case CLOCKWISE_270 -> rasterRotated .setPixel( y, oW - 1 - x, onePixel);
default -> rasterRotated .setPixel(oW - 1 - x, oH - 1 - y, onePixel);
};
}
}
rotated.setData(rasterRotated);
return rotated;
}
}
Sorry, but all the answers are difficult to understand for me as a beginner in graphics...
After some fiddling, this is working for me and it is easy to reason about.
#Override
public void draw(Graphics2D g) {
AffineTransform tr = new AffineTransform();
// X and Y are the coordinates of the image
tr.translate((int)getX(), (int)getY());
tr.rotate(
Math.toRadians(this.rotationAngle),
img.getWidth() / 2,
img.getHeight() / 2
);
// img is a BufferedImage instance
g.drawImage(img, tr, null);
}
I suppose that if you want to rotate a rectangular image this method wont work and will cut the image, but I thing you should create square png images and rotate that.
I think the sturdiest and easiest approach is to not only rotate the image, but also the coordinates you're working with. This code will turn a bufferedImage around the topleft corner and draw it accordingly without cropping, and the returned image will be drawn in the right place. If you know what a vector and matrix are and look at the wikipedia article for rotation matrix you will understand this code easily. I also added a little hand drawing to it. In the 2nd part of the algorithm we determine the position and dimension of the bigger rectangle, which contains our rotated bufferedImage. The first 4 points we define are technically not used, but they still help you understand where the points are initially.
public void drawRotated(final Graphics g, final BufferedImage bufferedImage, final int x, final int y, final int width, final int height, final double angle) {
final Rectangle collision = new Rectangle(x, y, width, height);
final BufferedImage resize = resize(bufferedImage, collision.width, collision.height);
final BufferedImage rotate = rotate(resize, angle, collision);
g.drawImage(rotate, collision.x, collision.y, collision.width, collision.height, null);
}
public static BufferedImage resize(final BufferedImage bufferedImage, final int newWidth, final int newHeight) {
final BufferedImage resized = new BufferedImage(newWidth, newHeight, bufferedImage.getType());
final Graphics g = resized.createGraphics();
g.drawImage(bufferedImage, 0, 0, newWidth, newHeight, null);
return resized;
}
public static BufferedImage rotate(final BufferedImage bufferedImage, final double angle, final Rectangle collision) {
final double sin = Math.sin(Math.toRadians(angle));
final double cos = Math.cos(Math.toRadians(angle));
final int x1 = collision.x;
final int y1 = collision.y;
final int x2 = collision.x+collision.width;
final int y2 = collision.y;
final int x3 = collision.x;
final int y3 = collision.y+collision.height;
final int x4 = collision.x+collision.width;
final int y4 = collision.y+collision.height;
//turn all 4 points around the top left point
final int newx1 = collision.x;
final int newy1 = collision.y;
//the y component is 0
final int newx2 = (int) (collision.x+collision.width*cos);
final int newy2 = (int) (collision.y+collision.width*sin);
//the x component is 0
final int newx3 = (int) (collision.x-collision.height*sin);
final int newy3 = (int) (collision.y+collision.height*cos);
final int newx4 = (int) (collision.x+collision.width*cos-collision.height*sin);
final int newy4 = (int) (collision.y+collision.width*sin+collision.height*cos);
//determine the new position of our bigger rectangle containing our image
collision.x = Math.min(Math.min(newx1, newx2), Math.min(newx3, newx4));
collision.y = Math.min(Math.min(newy1, newy2), Math.min(newy3, newy4));
//determine the new dimensions of our bigger rectangle containing our image
collision.width = Math.max(Math.max(newx1, newx2), Math.max(newx3, newx4))-collision.x;
collision.height = Math.max(Math.max(newy1, newy2), Math.max(newy3, newy4))-collision.y;
final BufferedImage rotated = new BufferedImage(collision.width, collision.height, bufferedImage.getType());
final Graphics2D g2d = rotated.createGraphics();
g2d.translate(newx1- collision.x, newy1- collision.y);
g2d.rotate(Math.toRadians(angle), 0, 0);
g2d.drawRenderedImage(bufferedImage, null);
g2d.dispose();
return rotated;
}

Visual representation of the Pythagoras tree in Java

I want to make a visual representation of the Pythagoras tree using Java, the code outputs a PNG fixed image.
I started by defining Vector class which starting from two vector components (x,y) can rotate the vector, scale it or add it to another vector.
public class Vector {
public double x;
public double y;
public Vector(double x, double y) {
this.x = x;
this.y = y;
}
public Vector rotated(double alpha) {
double x1 = Math.cos(alpha) * x - Math.sin(alpha) * y;
double y1 = Math.sin(alpha) * x + Math.cos(alpha) * y;
Vector vRotated = new Vector(x1, y1);
return vRotated;
}
public Vector scaled(double s) {
double x1 = x * s;
double y1 = y * s;
Vector vScaled = new Vector(x1, y1);
return vScaled;
}
public Vector added(Vector v) {
double x1 = this.x+v.x;
double y1 = this.y+v.y;
Vector vAdded = new Vector(x1,y1);
return vAdded;
}
}
I have also writen the method for creating the initial image and background and saving it to the desired path
public static void createPythagorasTreeImage(int startSize) throws IOException {
// Creation of the image object
int height = 5 * startSize;
int width = 8 * startSize;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// Create a Graphics2D object from the image and set a white background
Graphics2D g = image.createGraphics();
g.setColor(new Color(255, 255, 255));
g.fillRect(0, 0, width, height);
// Initial position and orientation of the first segment
Vector startPos = new Vector(width / 2, startSize);
Vector up = new Vector(0, 1);
// Start the recursion.
drawSegment(g, startPos, up, startSize, height);
// Save the image as PNG
String OS = System.getProperty("os.name").toLowerCase(); // different for win and unix
String filePath = System.getProperty("user.dir") + (OS.indexOf("win") >= 0 ? "\\" : "/") + "pythagorasTree.png";
System.out.println("Writing pythagoras-tree image to: " + filePath);
ImageIO.write(image, "png", new File(filePath));
}
I have read on wikipedia on how to the tree works, and want to now implement the algorithm.
What I need help with is implementing these two methods using Graphics2D (which I'm not very familiar with):
public static void drawRotatedRect(Graphics2D g, Vector pos, Vector up, int a, int height) {
}
This method should Draw a square using Graphics2D (maybe using g.fillPolygon()?), at position pos, up the vector that indicates the rotation of the square by indicating which direction is up for the square, a is the side of the square and height is the height of the drawing space.
public static void drawSegment(Graphics2D g, Vector pos, Vector up, int a, int height) {
}
This method should draw the first square using the previous method, than compute the positions and rotations of the two new squares and draw them, repeat this recursively until a square has a very small side length (2px).
This is my understanding for the Pythagoras tree, I managed to write the majority of the code and it seems that the idea is correct, only if I get the two missing methods to work.
You can work with the Graphics2D context by drawing a Path2D with floating point (or double) precision. I reccoment this, since you will notice that using int precision might give you weird effects.
To draw a path, do:
Path2D.Double rectangle = new Path2D.Double();
rectangle.moveTo(0, 0);
// ... basically draw the four points of the rectangle here.
rectangle.closePath();
g.setColor(yourColorOfChoice);
g.fill(rectangle);
Notice that you need to draw the rectangular shapes manually, since they need ot be rotated, and Graphics2D does not do well with rotations. You could try using inherent rotations, but you will pixelate your context, and you won't like it.
I am very much looking forward to your results. Could you paste the final image into your question, once you are done :)?

java static map doesn't update in other class

Hi there and thanks for reading,
I have a Map initialized as follows:
static HashMap<Point, Tile> map = new HashMap<Point, Tile>();
a Tile is a small class that holds an image and a function called addFloor(int tileCode):
class Tile {
BufferedImage img;
int imgHeight, imgWidth;
public Tile(BufferedImage img) {
this.img = img;
imgHeight = img.getHeight();
imgWidth = img.getWidth();
}
public Tile addFloor(int tileCode) {
BufferedImage newImg = Helper.joinBufferedImageFloor(this.img, Map.buildings.get(tileCode).img);
Tile newTile = new Tile(newImg);
return newTile;
}
}
The addFloor(int tileCode) function just puts an image obtained by another map via the tileCode as key (Map is just another class that holds the map of Tiles and maps of asset images, and also some unimportant functions for now) on top of the Tile image (this.img) with an offset.
(Are you still with me or do i need to give more info?)
and now:
Tile tileDebug = Map.map.get(new Point(10, 10));
Map.map.replace(new Point(10, 10), Map.map.get(new Point(10, 10)).addFloor(tileCode)); //I've also tried map.put()
tileDebug = Map.map.get(new Point(10, 10)); //MARK 1
panel.repaint();
I checked via eclipse debugger and I checked the addFloor(int tileCode) function seperated:
-the addFloor(int tileCode) function works perfectly. It creates a new image and returns a completely new Tile with the correct image.
-using the debugger I saw that the Tile updated, it changed its image and has another hash now (same as returned by addFloor(int tileCode)).
-Currently the paintComponent(Graphics g) function has just one call to drawMap(Graphics2D g2d) which draws Tile by Tile.
-Since the map.replace() call the map hasn't been edited.
private void drawMap(Graphics2D g2d) {
if (map != null) {
for (int y = 1; y <= 100; y++) {
for (int x = 1; x <= 100; x++) {
Tile currentTile = Map.map.get(new Point(x, y)); //MARK 2
if (x == 10 && y == 10) {
//This line is just so I can add a breakpoint to debug the tile currentTile
System.out.println("");
} //Unimportant from here, just positioning and drawing, that works perfectly
int dx = 350, dy = 0;
dx += Main.viewX;
dy += Main.viewY;
int posX, posY;
posX = (int) ((x * 64 + y * -64));
posY = (int) ((x * 32 + y * 32));
posX -= currentTile.imgWidth;
posY -= currentTile.imgHeight;
g2d.drawImage(currentTile.img, posX + dx, posY + dy, this);
}
}
}
}
The problem is that the drawn Tile isn't the old Tile, it isn't the Tile that got returned by addFloor(int tileCode). The drawn Tile is a new one, with its own hash and image. The image that is drawn is the second image passed to joinBufferedImageFloor(BufferedImage img1, BufferedImage img2) (the one obtained via the tileCode in addFloor(int tileCode).
I followed my entire program from //MARK 1 to //MARK 2, but the map never got explicitly edited but the return value of
Map.map.get(new Point(10, 10))
changed from the correct Tile (MARK 1) to the third Tile (MARK 2)
Is it because the Map is static? (Note: Only 2 Threads explicitly edit the Map, the Main Thread and a KeyListener Thread, but in this use case this Thread wasn't involved)
Whats wrong here?
Am I getting something wrong about Java Maps?
Any help appreciated thanks in advance
Edit 1min after posting it:
I think I described it a bit confusing here and there so please ask any question that might be unanswered in this description.
On wish I can also provide you with the full Project setup etc...
Edit few Hours after:
Edited the Question according to the how-to-ask section of StackOverflow
Edit for anyone interested the joinBufferedImageFloor(BufferdImage img1, BufferedImage img2) function:
public static BufferedImage joinBufferedImageFloor(BufferedImage img1, BufferedImage img2) {
int offsetX = 16;
int offsetY = -32;
int width = img1.getWidth();
int height = img1.getHeight() - offsetY;
BufferedImage newImage = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = newImage.createGraphics();
Color oldColor = g2.getColor();
g2.setColor(Color.BLACK);
g2.fillRect(0, 0, width, height);
g2.setColor(oldColor);
g2.drawImage(img1, null, 0, -offsetY);
g2.drawImage(img2, null, offsetX, 0);
g2.dispose();
return newImage;
}

Rotating Images and keeping track of point [duplicate]

I need to be able to rotate images individually(in java). The only thing I have found so far is g2d.drawImage(image, affinetransform, ImageObserver ). Unfortunately, I need to draw the image at a specific point, and there is no method with an argument that 1.rotates the image separately and 2. allows me to set the x and y. any help is appreciated
This is how you can do it. This code assumes the existance of a buffered image called 'image' (like your comment says)
// The required drawing location
int drawLocationX = 300;
int drawLocationY = 300;
// Rotation information
double rotationRequired = Math.toRadians (45);
double locationX = image.getWidth() / 2;
double locationY = image.getHeight() / 2;
AffineTransform tx = AffineTransform.getRotateInstance(rotationRequired, locationX, locationY);
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
// Drawing the rotated image at the required drawing locations
g2d.drawImage(op.filter(image, null), drawLocationX, drawLocationY, null);
AffineTransform instances can be concatenated (added together). Therefore you can have a transform that combines 'shift to origin', 'rotate' and 'shift back to desired position'.
A simple way to do it without the use of such a complicated draw statement:
//Make a backup so that we can reset our graphics object after using it.
AffineTransform backup = g2d.getTransform();
//rx is the x coordinate for rotation, ry is the y coordinate for rotation, and angle
//is the angle to rotate the image. If you want to rotate around the center of an image,
//use the image's center x and y coordinates for rx and ry.
AffineTransform a = AffineTransform.getRotateInstance(angle, rx, ry);
//Set our Graphics2D object to the transform
g2d.setTransform(a);
//Draw our image like normal
g2d.drawImage(image, x, y, null);
//Reset our graphics object so we can draw with it again.
g2d.setTransform(backup);
I struggled a little with the existing answers because my image to be rotated is not always a square, furthermore the accepted answer has a comment asking "Any info on how to circumvent the cutoff problem" that is not answered.
So for those who had the issue of image being croped when rotated here is the code that worked for me :
public static BufferedImage rotate(BufferedImage bimg, Double angle) {
double sin = Math.abs(Math.sin(Math.toRadians(angle))),
cos = Math.abs(Math.cos(Math.toRadians(angle)));
int w = bimg.getWidth();
int h = bimg.getHeight();
int neww = (int) Math.floor(w*cos + h*sin),
newh = (int) Math.floor(h*cos + w*sin);
BufferedImage rotated = new BufferedImage(neww, newh, bimg.getType());
Graphics2D graphic = rotated.createGraphics();
graphic.translate((neww-w)/2, (newh-h)/2);
graphic.rotate(Math.toRadians(angle), w/2, h/2);
graphic.drawRenderedImage(bimg, null);
graphic.dispose();
return rotated;
}
public static BufferedImage rotateCw( BufferedImage img )
{
int width = img.getWidth();
int height = img.getHeight();
BufferedImage newImage = new BufferedImage( height, width, img.getType() );
for( int i=0 ; i < width ; i++ )
for( int j=0 ; j < height ; j++ )
newImage.setRGB( height-1-j, i, img.getRGB(i,j) );
return newImage;
}
from https://coderanch.com/t/485958/java/Rotating-buffered-image
Here is a solution for rotations of 90, 180 & 270 degrees.
For these cases, the AffineTransform can introduce some loss/interpolation.
This solution is lossless & can also handle (esoteric?) Colour Models with more than 8 Bits/Pixel which BufferedImage.getRGB(int x, int y) cannot.
The solution proceeds on a pixel-by-pixel basis, which has the advantage of being simple to code.
It is possible to read the original Image row-by-row to gain performance, but its more complex, so I've left that out.
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
/**
* N.B. this example uses the new switch/case/Arrow notation, which requires Java 14.
*/
public enum Rotation {
CLOCKWISE_90,
CLOCKWISE_180,
CLOCKWISE_270;
public BufferedImage rotate(final BufferedImage original) {
final int oW = original.getWidth();
final int oH = original.getHeight();
final BufferedImage rotated =
switch (this) {
case CLOCKWISE_180 -> new BufferedImage(oW, oH, original.getType());
default -> new BufferedImage(oH, oW, original.getType());
};
final WritableRaster rasterOriginal = original.copyData(null);
final WritableRaster rasterRotated = rotated .copyData(null);
/*
* The Data for 1 Pixel...
*/
final int[] onePixel = new int[original.getSampleModel().getNumBands()];
/*
* Copy the Pixels one-by-one into the result...
*/
for (int x = 0; x < oW; x++) {
for (int y = 0; y < oH; y++) {
; rasterOriginal.getPixel( x, y, onePixel);
switch (this) {
case CLOCKWISE_90 -> rasterRotated .setPixel(oH - 1 - y, x, onePixel);
case CLOCKWISE_270 -> rasterRotated .setPixel( y, oW - 1 - x, onePixel);
default -> rasterRotated .setPixel(oW - 1 - x, oH - 1 - y, onePixel);
};
}
}
rotated.setData(rasterRotated);
return rotated;
}
}
Sorry, but all the answers are difficult to understand for me as a beginner in graphics...
After some fiddling, this is working for me and it is easy to reason about.
#Override
public void draw(Graphics2D g) {
AffineTransform tr = new AffineTransform();
// X and Y are the coordinates of the image
tr.translate((int)getX(), (int)getY());
tr.rotate(
Math.toRadians(this.rotationAngle),
img.getWidth() / 2,
img.getHeight() / 2
);
// img is a BufferedImage instance
g.drawImage(img, tr, null);
}
I suppose that if you want to rotate a rectangular image this method wont work and will cut the image, but I thing you should create square png images and rotate that.
I think the sturdiest and easiest approach is to not only rotate the image, but also the coordinates you're working with. This code will turn a bufferedImage around the topleft corner and draw it accordingly without cropping, and the returned image will be drawn in the right place. If you know what a vector and matrix are and look at the wikipedia article for rotation matrix you will understand this code easily. I also added a little hand drawing to it. In the 2nd part of the algorithm we determine the position and dimension of the bigger rectangle, which contains our rotated bufferedImage. The first 4 points we define are technically not used, but they still help you understand where the points are initially.
public void drawRotated(final Graphics g, final BufferedImage bufferedImage, final int x, final int y, final int width, final int height, final double angle) {
final Rectangle collision = new Rectangle(x, y, width, height);
final BufferedImage resize = resize(bufferedImage, collision.width, collision.height);
final BufferedImage rotate = rotate(resize, angle, collision);
g.drawImage(rotate, collision.x, collision.y, collision.width, collision.height, null);
}
public static BufferedImage resize(final BufferedImage bufferedImage, final int newWidth, final int newHeight) {
final BufferedImage resized = new BufferedImage(newWidth, newHeight, bufferedImage.getType());
final Graphics g = resized.createGraphics();
g.drawImage(bufferedImage, 0, 0, newWidth, newHeight, null);
return resized;
}
public static BufferedImage rotate(final BufferedImage bufferedImage, final double angle, final Rectangle collision) {
final double sin = Math.sin(Math.toRadians(angle));
final double cos = Math.cos(Math.toRadians(angle));
final int x1 = collision.x;
final int y1 = collision.y;
final int x2 = collision.x+collision.width;
final int y2 = collision.y;
final int x3 = collision.x;
final int y3 = collision.y+collision.height;
final int x4 = collision.x+collision.width;
final int y4 = collision.y+collision.height;
//turn all 4 points around the top left point
final int newx1 = collision.x;
final int newy1 = collision.y;
//the y component is 0
final int newx2 = (int) (collision.x+collision.width*cos);
final int newy2 = (int) (collision.y+collision.width*sin);
//the x component is 0
final int newx3 = (int) (collision.x-collision.height*sin);
final int newy3 = (int) (collision.y+collision.height*cos);
final int newx4 = (int) (collision.x+collision.width*cos-collision.height*sin);
final int newy4 = (int) (collision.y+collision.width*sin+collision.height*cos);
//determine the new position of our bigger rectangle containing our image
collision.x = Math.min(Math.min(newx1, newx2), Math.min(newx3, newx4));
collision.y = Math.min(Math.min(newy1, newy2), Math.min(newy3, newy4));
//determine the new dimensions of our bigger rectangle containing our image
collision.width = Math.max(Math.max(newx1, newx2), Math.max(newx3, newx4))-collision.x;
collision.height = Math.max(Math.max(newy1, newy2), Math.max(newy3, newy4))-collision.y;
final BufferedImage rotated = new BufferedImage(collision.width, collision.height, bufferedImage.getType());
final Graphics2D g2d = rotated.createGraphics();
g2d.translate(newx1- collision.x, newy1- collision.y);
g2d.rotate(Math.toRadians(angle), 0, 0);
g2d.drawRenderedImage(bufferedImage, null);
g2d.dispose();
return rotated;
}

Java: Rotating Images

I need to be able to rotate images individually(in java). The only thing I have found so far is g2d.drawImage(image, affinetransform, ImageObserver ). Unfortunately, I need to draw the image at a specific point, and there is no method with an argument that 1.rotates the image separately and 2. allows me to set the x and y. any help is appreciated
This is how you can do it. This code assumes the existance of a buffered image called 'image' (like your comment says)
// The required drawing location
int drawLocationX = 300;
int drawLocationY = 300;
// Rotation information
double rotationRequired = Math.toRadians (45);
double locationX = image.getWidth() / 2;
double locationY = image.getHeight() / 2;
AffineTransform tx = AffineTransform.getRotateInstance(rotationRequired, locationX, locationY);
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
// Drawing the rotated image at the required drawing locations
g2d.drawImage(op.filter(image, null), drawLocationX, drawLocationY, null);
AffineTransform instances can be concatenated (added together). Therefore you can have a transform that combines 'shift to origin', 'rotate' and 'shift back to desired position'.
A simple way to do it without the use of such a complicated draw statement:
//Make a backup so that we can reset our graphics object after using it.
AffineTransform backup = g2d.getTransform();
//rx is the x coordinate for rotation, ry is the y coordinate for rotation, and angle
//is the angle to rotate the image. If you want to rotate around the center of an image,
//use the image's center x and y coordinates for rx and ry.
AffineTransform a = AffineTransform.getRotateInstance(angle, rx, ry);
//Set our Graphics2D object to the transform
g2d.setTransform(a);
//Draw our image like normal
g2d.drawImage(image, x, y, null);
//Reset our graphics object so we can draw with it again.
g2d.setTransform(backup);
I struggled a little with the existing answers because my image to be rotated is not always a square, furthermore the accepted answer has a comment asking "Any info on how to circumvent the cutoff problem" that is not answered.
So for those who had the issue of image being croped when rotated here is the code that worked for me :
public static BufferedImage rotate(BufferedImage bimg, Double angle) {
double sin = Math.abs(Math.sin(Math.toRadians(angle))),
cos = Math.abs(Math.cos(Math.toRadians(angle)));
int w = bimg.getWidth();
int h = bimg.getHeight();
int neww = (int) Math.floor(w*cos + h*sin),
newh = (int) Math.floor(h*cos + w*sin);
BufferedImage rotated = new BufferedImage(neww, newh, bimg.getType());
Graphics2D graphic = rotated.createGraphics();
graphic.translate((neww-w)/2, (newh-h)/2);
graphic.rotate(Math.toRadians(angle), w/2, h/2);
graphic.drawRenderedImage(bimg, null);
graphic.dispose();
return rotated;
}
public static BufferedImage rotateCw( BufferedImage img )
{
int width = img.getWidth();
int height = img.getHeight();
BufferedImage newImage = new BufferedImage( height, width, img.getType() );
for( int i=0 ; i < width ; i++ )
for( int j=0 ; j < height ; j++ )
newImage.setRGB( height-1-j, i, img.getRGB(i,j) );
return newImage;
}
from https://coderanch.com/t/485958/java/Rotating-buffered-image
Here is a solution for rotations of 90, 180 & 270 degrees.
For these cases, the AffineTransform can introduce some loss/interpolation.
This solution is lossless & can also handle (esoteric?) Colour Models with more than 8 Bits/Pixel which BufferedImage.getRGB(int x, int y) cannot.
The solution proceeds on a pixel-by-pixel basis, which has the advantage of being simple to code.
It is possible to read the original Image row-by-row to gain performance, but its more complex, so I've left that out.
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
/**
* N.B. this example uses the new switch/case/Arrow notation, which requires Java 14.
*/
public enum Rotation {
CLOCKWISE_90,
CLOCKWISE_180,
CLOCKWISE_270;
public BufferedImage rotate(final BufferedImage original) {
final int oW = original.getWidth();
final int oH = original.getHeight();
final BufferedImage rotated =
switch (this) {
case CLOCKWISE_180 -> new BufferedImage(oW, oH, original.getType());
default -> new BufferedImage(oH, oW, original.getType());
};
final WritableRaster rasterOriginal = original.copyData(null);
final WritableRaster rasterRotated = rotated .copyData(null);
/*
* The Data for 1 Pixel...
*/
final int[] onePixel = new int[original.getSampleModel().getNumBands()];
/*
* Copy the Pixels one-by-one into the result...
*/
for (int x = 0; x < oW; x++) {
for (int y = 0; y < oH; y++) {
; rasterOriginal.getPixel( x, y, onePixel);
switch (this) {
case CLOCKWISE_90 -> rasterRotated .setPixel(oH - 1 - y, x, onePixel);
case CLOCKWISE_270 -> rasterRotated .setPixel( y, oW - 1 - x, onePixel);
default -> rasterRotated .setPixel(oW - 1 - x, oH - 1 - y, onePixel);
};
}
}
rotated.setData(rasterRotated);
return rotated;
}
}
Sorry, but all the answers are difficult to understand for me as a beginner in graphics...
After some fiddling, this is working for me and it is easy to reason about.
#Override
public void draw(Graphics2D g) {
AffineTransform tr = new AffineTransform();
// X and Y are the coordinates of the image
tr.translate((int)getX(), (int)getY());
tr.rotate(
Math.toRadians(this.rotationAngle),
img.getWidth() / 2,
img.getHeight() / 2
);
// img is a BufferedImage instance
g.drawImage(img, tr, null);
}
I suppose that if you want to rotate a rectangular image this method wont work and will cut the image, but I thing you should create square png images and rotate that.
I think the sturdiest and easiest approach is to not only rotate the image, but also the coordinates you're working with. This code will turn a bufferedImage around the topleft corner and draw it accordingly without cropping, and the returned image will be drawn in the right place. If you know what a vector and matrix are and look at the wikipedia article for rotation matrix you will understand this code easily. I also added a little hand drawing to it. In the 2nd part of the algorithm we determine the position and dimension of the bigger rectangle, which contains our rotated bufferedImage. The first 4 points we define are technically not used, but they still help you understand where the points are initially.
public void drawRotated(final Graphics g, final BufferedImage bufferedImage, final int x, final int y, final int width, final int height, final double angle) {
final Rectangle collision = new Rectangle(x, y, width, height);
final BufferedImage resize = resize(bufferedImage, collision.width, collision.height);
final BufferedImage rotate = rotate(resize, angle, collision);
g.drawImage(rotate, collision.x, collision.y, collision.width, collision.height, null);
}
public static BufferedImage resize(final BufferedImage bufferedImage, final int newWidth, final int newHeight) {
final BufferedImage resized = new BufferedImage(newWidth, newHeight, bufferedImage.getType());
final Graphics g = resized.createGraphics();
g.drawImage(bufferedImage, 0, 0, newWidth, newHeight, null);
return resized;
}
public static BufferedImage rotate(final BufferedImage bufferedImage, final double angle, final Rectangle collision) {
final double sin = Math.sin(Math.toRadians(angle));
final double cos = Math.cos(Math.toRadians(angle));
final int x1 = collision.x;
final int y1 = collision.y;
final int x2 = collision.x+collision.width;
final int y2 = collision.y;
final int x3 = collision.x;
final int y3 = collision.y+collision.height;
final int x4 = collision.x+collision.width;
final int y4 = collision.y+collision.height;
//turn all 4 points around the top left point
final int newx1 = collision.x;
final int newy1 = collision.y;
//the y component is 0
final int newx2 = (int) (collision.x+collision.width*cos);
final int newy2 = (int) (collision.y+collision.width*sin);
//the x component is 0
final int newx3 = (int) (collision.x-collision.height*sin);
final int newy3 = (int) (collision.y+collision.height*cos);
final int newx4 = (int) (collision.x+collision.width*cos-collision.height*sin);
final int newy4 = (int) (collision.y+collision.width*sin+collision.height*cos);
//determine the new position of our bigger rectangle containing our image
collision.x = Math.min(Math.min(newx1, newx2), Math.min(newx3, newx4));
collision.y = Math.min(Math.min(newy1, newy2), Math.min(newy3, newy4));
//determine the new dimensions of our bigger rectangle containing our image
collision.width = Math.max(Math.max(newx1, newx2), Math.max(newx3, newx4))-collision.x;
collision.height = Math.max(Math.max(newy1, newy2), Math.max(newy3, newy4))-collision.y;
final BufferedImage rotated = new BufferedImage(collision.width, collision.height, bufferedImage.getType());
final Graphics2D g2d = rotated.createGraphics();
g2d.translate(newx1- collision.x, newy1- collision.y);
g2d.rotate(Math.toRadians(angle), 0, 0);
g2d.drawRenderedImage(bufferedImage, null);
g2d.dispose();
return rotated;
}

Categories

Resources