In my Java application i am trying to resize an image until it fits a required dimension but still keeping its original scale.
the following method is used for resizing the image.
public BufferedImage ImageScaler(BufferedImage image){
scaledWidth = (int) image.getWidth() / SCALE_FACTOR * 100;
scaledHeight = (int) image.getHeight() / SCALE_FACTOR * 100;
scaledImage = resize(image, scaledHeight, scaledWidth);
while(scaledHeight > 150){
scaledHeight = scaledImage.getHeight();
scaledWidth = scaledImage.getWidth();
scaledImage = resize(image, scaledHeight, scaledWidth);
scaledHeight = scaledImage.getHeight();
scaledWidth = scaledImage.getWidth();
}
return scaledImage;
}
the while loop goes into an infinte loop. please help.
resize method.
private static BufferedImage resize(BufferedImage img, int height, int width) {
java.awt.Image tmp = img.getScaledInstance(width, height, java.awt.Image.SCALE_SMOOTH);
BufferedImage resized = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = resized.createGraphics();
g2d.drawImage(tmp, 0, 0, null);
g2d.dispose();
return resized;
}
Your code doesn't actually change anything. Suppose that when it goes into the loop, the width and height are both 200; the first two lines merely set scaledHeight and scaledWidth to 200, the third edits the image to conform to said variables, and then the fourth and fifth line set it to.. Exactly the same dimensions again. If after the initial resizing before the loop scaledHeight doesn't reach 150, it never will.
Related
I cannot seem to figure out how to draw a transparent and rotated image. I need to be able to draw an image that is transparent and rotated to a certain degree.
I tried this code:
// draws an image that is rotated to a certain degree
public static void drawRotatedImage(BufferedImage image_, int x, int y, int degrees, float scale) {
// graphics used for the utilities of drawing the image (processing)
Graphics2D utilGraphics;
// make rectangular image
int radius = (int) Math.sqrt(image_.getWidth() * image_.getWidth() + image_.getHeight() * image_.getHeight());
BufferedImage image1 = new BufferedImage(radius, radius, BufferedImage.TYPE_INT_RGB);
utilGraphics = image1.createGraphics();
// centers image
utilGraphics.drawImage(image_, image1.getWidth() / 2 - image_.getWidth() / 2, image1.getHeight() / 2 - image_.getHeight() / 2, null);
// scale image
int nw = (int) (image1.getWidth() * scale);
int nh = (int) (image1.getHeight() * scale);
BufferedImage image = new BufferedImage(nw, nh, BufferedImage.TYPE_INT_RGB);
utilGraphics.drawImage(image1, 0, 0, nw, nh, null);
// Rotation information
double rotationRequired = Math.toRadians (degrees);
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);
ImageProducer filteredImgProd = new FilteredImageSource(op.filter(image, null).getSource(), filter);
Image transparentImg = Toolkit.getDefaultToolkit().createImage(filteredImgProd);
// Drawing the rotated image at the required drawing locations
g2d.drawImage(Toolkit.getDefaultToolkit().createImage(transparentImg.getSource()), x, y, null);
}
The filter variable is defined as:
private static final ImageFilter filter = new RGBImageFilter() {
int transparentColor = new Color(0, 0, 0, 0).getRGB() | 0x0000ffcc;
public final int filterRGB(int x, int y, int rgb) {
if ((rgb | 0x0000ffcc) == transparentColor) {
return 0x0000ffcc & rgb;
} else {
return rgb;
}
}
};
This ...
BufferedImage image = new BufferedImage(nw, nh, BufferedImage.TYPE_INT_RGB);
centeredGraphics.drawImage(image1, 0, 0, nw, nh, null);
You're creating a new BufferedImage (image), but you never actually paint anything to it, instead, you paint image1 to it's own Graphics context.
Now, if you wanted a transparent image, you should have used...
BufferedImage centeredImage = new BufferedImage(radius, radius, BufferedImage.TYPE_INT_ARGB);
instead of...
BufferedImage centeredImage = new BufferedImage(radius, radius, BufferedImage.TYPE_INT_RGB);
And I never used g2d.drawImage(Toolkit.getDefaultToolkit().createImage(transparentImg.getSource()), x, y, null); as it just doesn't make sense to me (transparentImg is already an Image 🤷♂️)
Now, having said all that, I would "suggest" you take each step individually, start by scaling the original image using something like Java: maintaining aspect ratio of JPanel background image and the rotate the image using something like Rotate a buffered image in Java (which will generate a image large enough to contain the rotated image)
Also, if you "create" a Graphics context, you should also dispose of it when you no longer need it, otherwise you could end up with a memory leak.
"Fixed" code...
Just to be clear, I would still recommend sing ARGB instead of RGB for centeredImage as your filter workflow never seemed to work for, but I started with a transparent image anyway
public Image rotateAndScaleImage(BufferedImage originalImage, int degrees, float scale) {
// make rectangular image
int radius = (int) Math.sqrt(originalImage.getWidth() * originalImage.getWidth() + originalImage.getHeight() * originalImage.getHeight());
BufferedImage centeredImage = new BufferedImage(radius, radius, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = centeredImage.createGraphics();
// centers image
int xPos = (centeredImage.getWidth() - originalImage.getWidth()) / 2;
int yPos = (centeredImage.getHeight() - originalImage.getHeight()) / 2;
graphics.drawImage(originalImage, xPos, yPos, null);
graphics.dispose();
// scale image
int nw = (int) (centeredImage.getWidth() * scale);
int nh = (int) (centeredImage.getHeight() * scale);
BufferedImage image = new BufferedImage(nw, nh, BufferedImage.TYPE_INT_RGB);
graphics = image.createGraphics();
// No scaling is done ???
graphics.drawImage(centeredImage, 0, 0, nw, nh, null);
// Rotation information
double rotationRequired = Math.toRadians(degrees);
double locationX = centeredImage.getWidth() / 2;
double locationY = centeredImage.getHeight() / 2;
AffineTransform tx = AffineTransform.getRotateInstance(rotationRequired, locationX, locationY);
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
ImageProducer filteredImgProd = new FilteredImageSource(op.filter(centeredImage, null).getSource(), filter);
Image transparentImg = Toolkit.getDefaultToolkit().createImage(filteredImgProd);
return transparentImg;
}
private static final ImageFilter filter = new RGBImageFilter() {
int transparentColor = new Color(0, 0, 0, 0).getRGB() | 0x0000ffcc;
public final int filterRGB(int x, int y, int rgb) {
if ((rgb | 0x0000ffcc) == transparentColor) {
return 0x0000ffcc & rgb;
} else {
return rgb;
}
}
};
Oh, and I'm returning an Image because I painted directly to a component for testing
I'm trying scale an image so it will always fit my JPanel. Unfortunately using this method I don't always receive an Image I wanted to receive. Mostly it is zoomed and I would rather have the whole image but scaled.
Thats the class that creates the image. 600 is the PanelWidth and 400 is the PanelHeight.
Any ideas what goes wrong?
public class Image extends Component{
private BufferedImage img;
protected int width;
protected int height;
private String path;
#Override
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
double scale = getScale(600,400,img.getWidth(),img.getHeight());
double xPos = (600 - scale * img.getWidth())/2;
double yPos = (400 - scale *img.getHeight())/2;
AffineTransform at = AffineTransform.getTranslateInstance(xPos, yPos);
at.scale(scale, scale);
g2.drawRenderedImage(img, at);
System.out.println(scale);
}
public Image(String path){
try{
img = ImageIO.read(new File(path));
} catch (IOException e) { }
this.width=img.getWidth();
this.height=img.getHeight();
this.path = path;
}
public double getScale(int panelWidth, int panelHeight, int imageWidth, int imageHeight){
double scale = 1;
double xScale, yScale;
if(imageWidth > panelWidth || imageHeight > panelHeight){
xScale = (double)imageWidth/panelWidth;
yScale = (double)imageHeight/panelHeight;
scale = Math.max(xScale, yScale);
}else if(imageWidth < panelWidth && imageHeight < panelHeight){
xScale = (double)panelWidth/imageWidth;
yScale = (double)panelHeight/imageHeight;
scale = Math.max(xScale, yScale);
}else{
scale = 1;
}
return scale;
}
A JPanel is a Swing component which implies you are using Swing.
For custom painting you should extend JPanel or JComponent. Most people use JPanel because it will clear the background of the component for you.
Custom painting of a Swing component is done by overriding paintComponent(...)
so it will always fit my JPanel
Define "fit"?
Assuming you are trying to scale the image to retain its original proportions you could to something like:
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
double imageWidth = image.getWidth(null);
double imageHeight = image.getHeight(null);
double factor = Math.min(getWidth() / imageWidth, getHeight() / imageHeight);
int width = (int)(image.getWidth(null) * factor);
int height = (int)(image.getHeight(null) * factor);
g.drawImage(image, 0, 0, width, height, this);
}
If you are just trying to fit the image on the panel then you do:
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(image, 0, 0, getWidth(), getHeight(), this);
}
You don't need to use float if you do your operations in the right order. Assuming imageWidth, imageHeight, panelWidth are all int:
// Calculate the width of the scaled image; if the image is wider than the
// panel, use the panel width, otherwise use the image width (i.e. don't upscale)
int scaledWidth = Math.min(imageWidth, panelWidth);
// Given the scaled width, calculate the scaled height
// Force it to be at least 1 pixel, since if you have an image that's wider than
// the panel and only 1 pixel tall, this will scale to zero height, which you
// don't want
int scaledHeight = Math.max(1, imageHeight * scaledWidth / imageWidth);
The above assumes you want to fit the width and will be providing a scrolling mechanism if the image height exceeds the panel height. If you want to fit height instead (and horizontal scroll for overflow) just make the necessary changes in variables.
I need to resize alot of images from the ratio aspect (2:3) to (3:4).
The images are 800px x 1200px currently. I need them to be 600px x 800px eventually without any cropping.
May I know what libraries are available for me to do padding and resizing without cropping in Java?
From your current Image (assuming a java.awt.Image) you can use :
Image.getScaledInstance(w,h,h) as method
Image.SCALE_SMOOTH as algorithm for resize
And these steps:
compute the ratios in width and in height
depending on their values (padding width or padding height)
compute the width and height to obtain the scaled image
compute the padding required
write the image at the good position
static BufferedImage pad(BufferedImage image, double width, double height, Color pad) {
double ratioW = image.getWidth() / width;
double ratioH = image.getHeight() / height;
double newWidth = width, newHeight = height;
int fitW = 0, fitH = 0;
BufferedImage resultImage;
Image resize;
//padding width
if (ratioW < ratioH) {
newWidth = image.getWidth() / ratioH;
newHeight = image.getHeight() / ratioH;
fitW = (int) ((width - newWidth) / 2.0);
}//padding height
else if (ratioH < ratioW) {
newWidth = image.getWidth() / ratioW;
newHeight = image.getHeight() / ratioW;
fitH = (int) ((height - newHeight) / 2.0);
}
resize = image.getScaledInstance((int) newWidth, (int) newHeight, Image.SCALE_SMOOTH);
resultImage = new BufferedImage((int) width, (int) height, image.getType());
Graphics g = resultImage.getGraphics();
g.setColor(pad);
g.fillRect(0, 0, (int) width, (int) height);
g.drawImage(resize, fitW, fitH, null);
g.dispose();
return resultImage;
}
To use as
BufferedImage image = ...;
BufferedImage result = pad(image, 600, 800, Color.white);
Managed to do it using below code:
'w' is the amount of padding you need on each side.
BufferedImage newImage = new BufferedImage(image.getWidth()+2*w, image.getHeight(),
image.getType());
Graphics g = newImage.getGraphics();
g.setColor(Color.white);
g.fillRect(0,0,image.getWidth()+2*w,image.getHeight());
g.drawImage(image, w, 0, null);
g.dispose();
I think ffmpeg can help you to do anything with image.
e.g. Use ffmpeg to resize image
You can keep ffmpeg binaries in some conf folder.
Create sh script for ffmpeg command.
Use CommandLine from (Apache Commons exec library) to run the script.
I'm trying to rotate an image, but it's getting slightly messed up when I'm rotating it, and it looks like it's not rotating it on center. So if I go around it looks like it's being truncated. Is there a better method to get the "center" of the image?
public void RotateImageLeft() {
try {
BufferedImage newImage = new BufferedImage(originalImage.getWidth(), originalImage.getHeight(), originalImage.getType());
AffineTransform tx = new AffineTransform();
tx.rotate(Math.toRadians(-90.0), originalImage.getWidth() / 2, originalImage.getHeight() / 2);
Graphics2D g2 = newImage.createGraphics();
g2.drawImage(originalImage, tx, null);
originalImage = newImage;
this.repaint();
g2.dispose();
} catch (Exception ex) {
ex.toString();
}
//g2d.drawImage(getResImage(), rescale, x, y);
}
For full code disclosure, here's more code. Here's my painComponent overridden method:
public void paintComponent(Graphics g) {
super.paintComponent(g);
resizeImage();
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(getResImage(), rescale, x, y);
}
Here's the resizeImage() method that gets called:
public void resizeImage() {
Graphics g = getResImage().getGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, getResImage().getWidth(), getResImage().getHeight());
int scaledWidth = (int) ((getOriginalImage().getWidth() * getHeight()
/ getOriginalImage().getHeight()));
if (scaledWidth < getWidth()) {
int leftOffset = getWidth() / 2 - scaledWidth / 2;
int rightOffset = getWidth() / 2 + scaledWidth / 2;
g.drawImage(getOriginalImage(),
leftOffset, 0, rightOffset, getHeight(),
0, 0, getOriginalImage().getWidth(), getOriginalImage().getHeight(),
null);
} else {
int scaledHeight = (getOriginalImage().getHeight() * getWidth())
/ getOriginalImage().getWidth();
int topOffset = getHeight() / 2 - scaledHeight / 2;
int bottomOffset = getHeight() / 2 + scaledHeight / 2;
g.drawImage(getOriginalImage(),
0, topOffset, getWidth(), bottomOffset,
0, 0, getOriginalImage().getWidth(), getOriginalImage().getHeight(),
null);
}
}
I'm using the ResizeImage method since I want any image to fit correctly on my 720/432 panel.
Here's some example pictures.
Pre-rotated
Post-rotated:
New code: (new image is the correct height/width of rotated image, still getting black bars. Screens below.
public void RotateImageLeft() {
try {
BufferedImage newImage = new BufferedImage( originalImage.getHeight(),originalImage.getWidth(), originalImage.getType());
AffineTransform tx = new AffineTransform();
tx.rotate(Math.toRadians(-90.0), newImage.getWidth() / 2, (newImage.getHeight() / 2));
Graphics2D g2 = newImage.createGraphics();
g2.drawImage(originalImage, tx, null);
originalImage = newImage;
this.repaint();
g2.dispose();
} catch (Exception ex) {
ex.toString();
}
}
Post rotate:
From my answer to another similar question
If you're rotating then this will work for 90 degrees.
move image so centered "around" the origin
plain rotate() call with no extra parameters
Move image back into the center remembering that now width = old height and height = old width.
Also remember the affine transform steps work in reverse order.
AffineTransform tx = new AffineTransform();
// last, width = height and height = width
tx.translate(originalImage.getHeight() / 2,originalImage.getWidth() / 2);
tx.rotate(Math.PI / 2);
// first - center image at the origin so rotate works OK
tx.translate(-originalImage.getWidth() / 2,-originalImage.getHeight() / 2);
If you want to rotate an image without cropping you need to add black bars around it first, since a rotated rectangle will always have a bigger bounding box than an axis-aligned one (exception: rotating a square by multiples of 90 degrees).
So what you want to do is do some trigonometric calculations beforehand to decide the maximum Width/Height of the rotated image, and combine that with the original Width/Height. Resize your image (centering it) using those dimensions, rotate it, and then crop it back to the Width/Height of the rotated image.
Is there any faster way to achieve padding of pixels to a BufferedImage than drawing it centered on larger BufferedImage?
BufferedImage has a constructor where you get to specify a WriteableRaster.
Picking at the a default buffered image, storing each pixel in an int, it uses an IntegerInterleavedRaster.
The ColorModel you can use ColorModel.getRGBDefault().
int imageWidth = 638, imageHeight = 480;
int dataImageWidth = 640;
SampleModel sm = new SinglePixelPackedSampleModel(TYPE_INT, imageWidth, imageHeight, dataImageWidth, new int[] { 0xff0000, 0xff00, 0xff });
DataBuffer db = new DataBufferInt(dataImageWidth * imageHeight);
WritableRaster r = Raster.createWritableRaster(sm, db, new Point());
BufferedImage image = new BufferedImage(ColorModel.getRGBDefault(), r, false, null);
Notice the scanlineStride in SinglePixelPackedSampleModel (second last parameter).
Another much simpler approach is to use BufferedImage's getSubimage method.
BufferedImage fullImage = new BufferedImage(dataImageWidth, imageHeight);
BufferedImage subImage = fullImage.getSubimage(0, 0, imageWidth, imageHeight);
Create an ImageIcon using the BufferedImage and add the Icon to a JLabel. Then you can just add a Border to the label to get your desired padding.
To defer centering until rendering, I like this approach due to finnw, where this is a suitable component:
private BufferedImage image;
....
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.translate(this.getWidth() / 2, this.getHeight() / 2);
g2d.translate(-image.getWidth() / 2, -image.getHeight() / 2);
g2d.drawImage(image, 0, 0, null);
}