This question already has answers here:
Rotate a buffered image in Java
(4 answers)
Closed 1 year ago.
I'm trying to rotate the image around a reference point in Java using BufferedImage and AffineTransform, at first it seemed exactly what I needed, but it turns out it doesn't behave as expected. I need to do some rudimentary rotations, in multiples of 90, so I tried to do getQuadrantRotateInstance, but, if the reference point is at 0,0 then I get a RasterFormatException: Transformed height (0) is less than or equal to 0.
var rotation = switch (transform) {
case TRANS_NONE -> 0;
case TRANS_ROT90 -> 1;
case TRANS_ROT180 -> 2;
case TRANS_ROT270 -> 3;
default -> throw new NotImplementedException();
};
var transform = AffineTransform.getQuadrantRotateInstance(rotation, referenceX, referenceY);
var operation = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);
var rotated = operation.filter(source, null);
By the looks of it the image gets rotated out of the canvas (into negative coordinates), resulting in exception above.
What would be the proper solution to create a rotated variant of the image without cropping or rotating around a center point like existing solutions do?
Rotating an image by an angle around the center point:
private BufferedImage rotateImage(BufferedImage buffImage, double angle) {
double radian = Math.toRadians(angle);
double sin = Math.abs(Math.sin(radian));
double cos = Math.abs(Math.cos(radian));
int width = buffImage.getWidth();
int height = buffImage.getHeight();
int nWidth = (int) Math.floor((double) width * cos + (double) height * sin);
int nHeight = (int) Math.floor((double) height * cos + (double) width * sin);
BufferedImage rotatedImage = new BufferedImage(nWidth, nHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = rotatedImage.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
graphics.translate((nWidth - width) / 2, (nHeight - height) / 2);
// This is the rotation around the center point - change this line
graphics.rotate(radian, (double) (width / 2), (double) (height / 2));
graphics.drawImage(buffImage, 0, 0, null);
graphics.dispose();
return rotatedImage;
}
To change the origin point of the rotation see javadoc of the method rotate.
Source: Creating simple captcha.
Related
I try to find the way to rotate the LinearGradient object nested into e.g. Rectangle object, say:
Rectangle rect = new Rectangle(0, 0, 200, 200);
LinearGradient lg = new LinearGradient(0, 0, 100, 0, false, CycleMethod.REPEAT, new Stop[] {
new Stop(0, Color.BLACK);
new Stop(0.5, Color.WHITE);
new Stop(1, Color.BLACK);
});
rect.setFill(lg);
Now, I try to rotate this lg object, for example for 45 degrees to the left, but without rotating the whole rect. Is there any way to achieve that?
The first parameters that are given to the LinearGradient constructor are the coordinates of the start- and end point of the gradient axis, respectively. This means that you can achieve a "rotated" gradient simply by passing in an appropriately rotated axis.
In the simplest form, for the example that you described, you can use the following pattern:
double angleInRadians = Math.toRadians(45);
double length = 100;
double endX = Math.cos(angleInRadians) * length;
double endY = Math.sin(angleInRadians) * length;
LinearGradient lg = new LinearGradient(0, 0, endX, endY, ...);
This will result in a gradient rotated by 45 degrees.
The fixed values here will affect the final appearance of the gradient, together with the other parameters. Referring to your example, this gradient with the same "wave length" as before (namely 100), and start with the same color at the upper left corner (i.e. Color.BLACK will be at coordinates (0,0)).
Trig ratios can be used for a more flexible gradient angle. Please note: It does not implement repeat, hence add more stops in the gradient object.
private fun createGradient(width: Float, height: Float): LinearGradient {
val mode = TileMode.CLAMP
val angleInRadians = Math.toRadians(mAngle.toDouble())
val halfWidth = width / 2
val halfHeight = height / 2
val sinAngle = sin(angleInRadians)
val cosAngle = cos(angleInRadians)
val x0 = (halfWidth * (1 + sinAngle)).toFloat()
val y0 = (halfHeight * (1 - cosAngle)).toFloat()
val x1 = (halfWidth * (1 - sinAngle)).toFloat()
val y1 = (halfHeight * (1 + cosAngle)).toFloat()
return LinearGradient(x0, y0, x1, y1, mGradient, null, mode)
}
i have a use case like after adding JLabelComponent to pallet which i have to resize to custom level(re sizing bcz data is very large ) the added label component.Once i am done with re-sizing setting the component.setBounds all the coordinates. when i try to rotate the re sized label component to 90 degrees i am not getting proper shape. its head are cut off. please suggest
Here is my code:
if (selectedComponent instanceof LabelComponent) {
LabelComponent lbls = (LabelComponent) selectedComponent;
lbls.setAngle(Integer.parseInt(value));
lbls.repaint();
lbls.setSize(lbls.getPreferredSize());
and my paint method is
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
AffineTransform aT = g2.getTransform();
double sin = Math.abs(Math.sin(getAngle()));
double cos = Math.abs(Math.cos(getAngle()));
int originalWidth = getWidth();
int originalHeight = getHeight();
int newWidth = (int) Math.floor(originalWidth * cos + riginalHeight * sin);
int newHeight = (int) Math.floor(originalHeight * cos + originalWidth * sin);
if(getAngle() == Integer.parseInt("90"))
{
g2.translate((newWidth-originalWidth)/2, (newHeight-orginalHeight)/2);
}
g2.rotate(Math.toRadians(getAngle()), originalWidth/2, originalHeight/2);
super.paint(g);
}
when i try to rotate the re sized label component to 90 degrees i am not getting proper shape.
One problem is that you need to reset the preferred size of the label. That is the width and height change because of the rotation.
Instead of doing custom painting you could try to use the Rotated Icon.
I am trying to rotate a 32 by 64 png image with 90 degree increments by using Affinetransform.rotate() and AffinetransformOp.filter. It works fine for 0, 90 and 180 degrees but at 270 degrees only half of my image is shown. After some research i found out that this is due to the way filter() handles image coordinates but i have no idea how to solve this. I have already tried using other methods to rotate images but i'd rather use this one.
The method for rotating images:
public BufferedImage rotateImage(BufferedImage img, double angle) {
AffineTransform tx = new AffineTransform();
tx.rotate(angle, img.getWidth() - 16, img.getHeight() - 16);
AffineTransformOp op = new AffineTransformOp(tx,
AffineTransformOp.TYPE_BILINEAR);
return op.filter(img, null);
}
I also have a screenshot for clarifying my problem:
EDIT: I have tried manually creating a destination image for filter() by adding the following code, to no avail.
int w, h;
if (angle == Math.PI / 2 || angle == 3 * Math.PI / 2) {
w = img.getHeight();
h = img.getWidth();
} else {
w = img.getWidth();
h = img.getHeight();
}
BufferedImage dest = new BufferedImage(w, h, img.getType());
return op.filter(img, dest);
I think it has something to do with the fact that you are passing
tx.rotate(angle, img.getHeight() / 2 - 16, img.getHeight() - 16);
when it should be
tx.rotate(angle, img.getWidth() - 16, img.getHeight() / 2 - 16);
(you passed getHeight() twice and I'm pretty sure they were in the wrong order)
Let me know if this changes things for you.
I keep in memory a list of java.Awt Images and have the need to rotate them. I've read some solutions but they deal with changing the way the image is shown, not really rotating the image itself.
I need to rotate an image itself, not to draw in in a rotated way. How can this be attained?
The following code will rotate an image by an arbitrary angle in degrees.
Positive values for degrees will rotate the image clockwise, negative values counterclockwise.
The resulting image will be adjusted in size, so that the rotated image fits exactly into it.
I have tested it with jpg and png image files as input.
public static BufferedImage rotateImage(BufferedImage src, double degrees) {
double radians = Math.toRadians(degrees);
int srcWidth = src.getWidth();
int srcHeight = src.getHeight();
/*
* Calculate new image dimensions
*/
double sin = Math.abs(Math.sin(radians));
double cos = Math.abs(Math.cos(radians));
int newWidth = (int) Math.floor(srcWidth * cos + srcHeight * sin);
int newHeight = (int) Math.floor(srcHeight * cos + srcWidth * sin);
/*
* Create new image and rotate it
*/
BufferedImage result = new BufferedImage(newWidth, newHeight,
src.getType());
Graphics2D g = result.createGraphics();
g.translate((newWidth - srcWidth) / 2, (newHeight - srcHeight) / 2);
g.rotate(radians, srcWidth / 2, srcHeight / 2);
g.drawRenderedImage(src, null);
return result;
}
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