Related
So I am using this library https://github.com/uptechteam/MotionViews-Android/ for creating bitmaps, which can be resized and dragged around just like snapchat/instagram stickers. But the problem is that the bitmaps that are generated are not smooth and have rough edges like this:
As you can see the text are somewhat distorted and the background has rough edges. Here is the code for generating the bitmap:
/**
* If reuseBmp is not null, and size of the new bitmap matches the size of the reuseBmp,
* new bitmap won't be created, reuseBmp it will be reused instead
*
* #param textLayer text to draw
* #param reuseBmp the bitmap that will be reused
* #return bitmap with the text
*/
#NonNull
private Bitmap createBitmap(#NonNull TextLayer textLayer, #Nullable Bitmap reuseBmp) {
int boundsWidth = canvasWidth;
int alignment = textLayer.getFont().getAlignment();
int style = textLayer.getFont().getStyle();
// init params - size, color, typeface
if (style == STYLE_UNDERLINE) {
textPaint.setFlags(Paint.UNDERLINE_TEXT_FLAG);
} else {
textPaint.setFlags(0);
}
textPaint.setAntiAlias(true);
textPaint.setDither(false);
textPaint.setElegantTextHeight(true);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setTextSize(textLayer.getFont().getSize() * canvasWidth);
textPaint.setColor(textLayer.getFont().getColor());
textPaint.setTypeface(fontProvider.getTypeface(textLayer.getFont().getTypeface()));
textBackgroundColor = textLayer.getFont().getBackgroundColor();
// drawing text guide : http://ivankocijan.xyz/android-drawing-multiline-text-on-canvas/
// Static layout which will be drawn on canvas
Layout.Alignment layoutAlignment;
if (alignment == ALIGNMENT_LEFT) {
layoutAlignment = Layout.Alignment.ALIGN_NORMAL;
} else if (alignment == ALIGNMENT_RIGHT) {
layoutAlignment = Layout.Alignment.ALIGN_OPPOSITE;
} else {
layoutAlignment = Layout.Alignment.ALIGN_CENTER;
}
StaticLayout sl;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
sl = new StaticLayout(
textLayer.getText(), // - text which will be drawn
textPaint,
boundsWidth, // - width of the layout
layoutAlignment, // - layout alignment
1, // 1 - text spacing multiply
1, // 1 - text spacing add
true); // true - include padding
} else {
StaticLayout.Builder builder = StaticLayout.Builder.obtain(textLayer.getText(), 0, textLayer.getText().length(), textPaint, boundsWidth)
.setAlignment(layoutAlignment)
.setLineSpacing(1, 1)
.setIncludePad(true);
sl = builder.build();
}
// calculate height for the entity, min - Limits.MIN_BITMAP_HEIGHT
int boundsHeight = sl.getHeight();
// create bitmap not smaller than TextLayer.Limits.MIN_BITMAP_HEIGHT
int bmpHeight = (int) (canvasHeight * Math.max(TextLayer.Limits.MIN_BITMAP_HEIGHT,
1.0F * boundsHeight / canvasHeight));
// create bitmap where text will be drawn
Bitmap bmp;
if (reuseBmp != null && reuseBmp.getWidth() == boundsWidth
&& reuseBmp.getHeight() == bmpHeight) {
// if previous bitmap exists, and it's width/height is the same - reuse it
bmp = reuseBmp;
bmp.eraseColor(Color.TRANSPARENT); // erase color when reusing
} else {
bmp = Bitmap.createBitmap(boundsWidth, bmpHeight, Bitmap.Config.ARGB_8888);
}
Canvas canvas = new Canvas(bmp);
final Rect rect = new Rect(0, 0, bmp.getWidth(), bmp.getHeight());
final RectF rectF = new RectF(rect);
final Paint textBackgroundPaint = new Paint();
textBackgroundPaint.setFlags(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
textBackgroundPaint.setDither(false);
textBackgroundPaint.setColor(textBackgroundColor);
textBackgroundPaint.setStyle(Paint.Style.FILL_AND_STROKE);
textBackgroundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
canvas.drawRoundRect(rectF, textBackgroundRadius, textBackgroundRadius, textBackgroundPaint);
canvas.drawBitmap(bmp, rect, rect, textBackgroundPaint);
canvas.save();
// move text to center if bitmap is bigger that text
if (boundsHeight < bmpHeight) {
//calculate Y coordinate - In this case we want to draw the text in the
//center of the canvas so we move Y coordinate to center.
float textYCoordinate = (float) (bmpHeight - boundsHeight) / 2;
canvas.translate(0, textYCoordinate);
}
//draws static layout on canvas
sl.draw(canvas);
canvas.restore();
return bmp;
}
I tried using antialias, filterbitmap on the paint but it does nothing. Also tried setting the view's layer type to layer_type_software but the result is the same. How to fix this? Any help would be appreciated.
Please use below code for removing rough edges from bitmap
canvas.drawBitmap(bitmap, x, y, new Paint(Paint.ANTI_ALIAS_FLAG));
use the Paint.ANTI_ALIAS_FLAG flag, this flag is used for removing the jagged edges and smooth the edges
I have bitmaps which are squares or rectangles. I take the shortest side and do something like this:
int value = 0;
if (bitmap.getHeight() <= bitmap.getWidth()) {
value = bitmap.getHeight();
} else {
value = bitmap.getWidth();
}
Bitmap finalBitmap = null;
finalBitmap = Bitmap.createBitmap(bitmap, 0, 0, value, value);
Then I scale it to a 144 x 144 Bitmap using this:
Bitmap lastBitmap = null;
lastBitmap = Bitmap.createScaledBitmap(finalBitmap, 144, 144, true);
Problem is that it crops the top left corner of the original bitmap, Anyone has the code to crop the center of the bitmap?
This can be achieved with: Bitmap.createBitmap(source, x, y, width, height)
if (srcBmp.getWidth() >= srcBmp.getHeight()){
dstBmp = Bitmap.createBitmap(
srcBmp,
srcBmp.getWidth()/2 - srcBmp.getHeight()/2,
0,
srcBmp.getHeight(),
srcBmp.getHeight()
);
}else{
dstBmp = Bitmap.createBitmap(
srcBmp,
0,
srcBmp.getHeight()/2 - srcBmp.getWidth()/2,
srcBmp.getWidth(),
srcBmp.getWidth()
);
}
While most of the above answers provide a way to do this, there is already a built-in way to accomplish this and it's 1 line of code (ThumbnailUtils.extractThumbnail())
int dimension = getSquareCropDimensionForBitmap(bitmap);
bitmap = ThumbnailUtils.extractThumbnail(bitmap, dimension, dimension);
...
//I added this method because people keep asking how
//to calculate the dimensions of the bitmap...see comments below
public int getSquareCropDimensionForBitmap(Bitmap bitmap)
{
//use the smallest dimension of the image to crop to
return Math.min(bitmap.getWidth(), bitmap.getHeight());
}
If you want the bitmap object to be recycled, you can pass options that make it so:
bitmap = ThumbnailUtils.extractThumbnail(bitmap, dimension, dimension, ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
From: ThumbnailUtils Documentation
public static Bitmap extractThumbnail (Bitmap source, int width, int
height)
Added in API level 8 Creates a centered bitmap of the desired size.
Parameters source original bitmap source width targeted width
height targeted height
I was getting out of memory errors sometimes when using the accepted answer, and using ThumbnailUtils resolved those issues for me. Plus, this is much cleaner and more reusable.
Have you considered doing this from the layout.xml ? You could set for your ImageView the ScaleType to android:scaleType="centerCrop" and set the dimensions of the image in the ImageView inside the layout.xml.
You can used following code that can solve your problem.
Matrix matrix = new Matrix();
matrix.postScale(0.5f, 0.5f);
Bitmap croppedBitmap = Bitmap.createBitmap(bitmapOriginal, 100, 100,100, 100, matrix, true);
Above method do postScalling of image before cropping, so you can get best result with cropped image without getting OOM error.
For more detail you can refer this blog
Here a more complete snippet that crops out the center of an [bitmap] of arbitrary dimensions and scales the result to your desired [IMAGE_SIZE]. So you will always get a [croppedBitmap] scaled square of the image center with a fixed size. ideal for thumbnailing and such.
Its a more complete combination of the other solutions.
final int IMAGE_SIZE = 255;
boolean landscape = bitmap.getWidth() > bitmap.getHeight();
float scale_factor;
if (landscape) scale_factor = (float)IMAGE_SIZE / bitmap.getHeight();
else scale_factor = (float)IMAGE_SIZE / bitmap.getWidth();
Matrix matrix = new Matrix();
matrix.postScale(scale_factor, scale_factor);
Bitmap croppedBitmap;
if (landscape){
int start = (tempBitmap.getWidth() - tempBitmap.getHeight()) / 2;
croppedBitmap = Bitmap.createBitmap(tempBitmap, start, 0, tempBitmap.getHeight(), tempBitmap.getHeight(), matrix, true);
} else {
int start = (tempBitmap.getHeight() - tempBitmap.getWidth()) / 2;
croppedBitmap = Bitmap.createBitmap(tempBitmap, 0, start, tempBitmap.getWidth(), tempBitmap.getWidth(), matrix, true);
}
Probably the easiest solution so far:
public static Bitmap cropCenter(Bitmap bmp) {
int dimension = Math.min(bmp.getWidth(), bmp.getHeight());
return ThumbnailUtils.extractThumbnail(bmp, dimension, dimension);
}
imports:
import android.media.ThumbnailUtils;
import java.lang.Math;
import android.graphics.Bitmap;
To correct #willsteel solution:
if (landscape){
int start = (tempBitmap.getWidth() - tempBitmap.getHeight()) / 2;
croppedBitmap = Bitmap.createBitmap(tempBitmap, start, 0, tempBitmap.getHeight(), tempBitmap.getHeight(), matrix, true);
} else {
int start = (tempBitmap.getHeight() - tempBitmap.getWidth()) / 2;
croppedBitmap = Bitmap.createBitmap(tempBitmap, 0, start, tempBitmap.getWidth(), tempBitmap.getWidth(), matrix, true);
}
public Bitmap getResizedBitmap(Bitmap bm) {
int width = bm.getWidth();
int height = bm.getHeight();
int narrowSize = Math.min(width, height);
int differ = (int)Math.abs((bm.getHeight() - bm.getWidth())/2.0f);
width = (width == narrowSize) ? 0 : differ;
height = (width == 0) ? differ : 0;
Bitmap resizedBitmap = Bitmap.createBitmap(bm, width, height, narrowSize, narrowSize);
bm.recycle();
return resizedBitmap;
}
public static Bitmap resizeAndCropCenter(Bitmap bitmap, int size, boolean recycle) {
int w = bitmap.getWidth();
int h = bitmap.getHeight();
if (w == size && h == size) return bitmap;
// scale the image so that the shorter side equals to the target;
// the longer side will be center-cropped.
float scale = (float) size / Math.min(w, h);
Bitmap target = Bitmap.createBitmap(size, size, getConfig(bitmap));
int width = Math.round(scale * bitmap.getWidth());
int height = Math.round(scale * bitmap.getHeight());
Canvas canvas = new Canvas(target);
canvas.translate((size - width) / 2f, (size - height) / 2f);
canvas.scale(scale, scale);
Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
canvas.drawBitmap(bitmap, 0, 0, paint);
if (recycle) bitmap.recycle();
return target;
}
private static Bitmap.Config getConfig(Bitmap bitmap) {
Bitmap.Config config = bitmap.getConfig();
if (config == null) {
config = Bitmap.Config.ARGB_8888;
}
return config;
}
val sourceWidth = source.width
val sourceHeight = source.height
val xScale = newWidth.toFloat() / sourceWidth
val yScale = newHeight.toFloat() / sourceHeight
val scale = xScale.coerceAtLeast(yScale)
val scaledWidth = scale * sourceWidth
val scaledHeight = scale * sourceHeight
val left = (newWidth - scaledWidth) / 2
val top = (newHeight - scaledHeight) / 2
val targetRect = RectF(
left, top, left + scaledWidth, top
+ scaledHeight
)
val dest = Bitmap.createBitmap(
newWidth, newHeight,
source.config
)
val mutableDest = dest.copy(source.config, true)
val canvas = Canvas(mutableDest)
canvas.drawBitmap(source, null, targetRect, null)
binding.imgView.setImageBitmap(mutableDest)
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;
}
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;
}
I have a motion JPEG stream that is put on a canvas in Android. My problem is that at the bottom right of the canvas there is a little, gray, pixelated bar. It kinda of disappears and re-appears. I don't have this issue on other platforms that use the same stream, so i'm guessing its an Android problem. Here is my code:
Canvas canvas = null;
try
{
Bitmap bmp = BitmapFactory.decodeByteArray(notifi.imgData, 0, notifi.imgData.length);
if (bmp == null)
System.out.println("Skipping invalid MJpeg frame");
else
{
canvas = holder.lockCanvas(null);
if (canvas == null)
{
System.out.println("Cannot lock canvas, skipping MJpeg frame");
return;
}
canvas.drawColor(Color.BLACK);
Rect dst = null;
int viewWidth = mPreview.getWidth();
int viewHeight = mPreview.getHeight();
float ratio = bmp.getWidth() / (float)bmp.getHeight();
int desiredHeight = (int)(viewWidth / ratio);
if (desiredHeight > viewHeight)
{ // Letterbox
int maxWidth = (int)(viewHeight * ratio);
int pad = (viewWidth - maxWidth) / 2;
dst = new Rect(pad, 0, maxWidth + pad, viewHeight);
}
else
{
int pad = (viewHeight - desiredHeight) / 2;
dst = new Rect(0, pad, viewWidth, desiredHeight + pad);
}
canvas.drawBitmap(bmp, null, dst, null);
}
} finally {
if (canvas != null)
holder.unlockCanvasAndPost(canvas);
}
}
});
canvas = holder.lockCanvas(null); looks suspect.
Try canvas = holder.lockCanvas(); instead.
I notice that you are adding a "Pad" to the destination rectangle to change it's height.
If you do that, I wonder if the destination rectangle size will match the size of the image you are drawing and thus "over draw" some gray bar as you are seeing. Just a guess - maybe if you remove the height pad it will go away? Just a guess.
dst = new Rect(0, pad, viewWidth, desiredHeight + pad);
canvas.drawBitmap(bmp, null, dst, null);