Related
I am trying to reverse the perspective shift from a rectangle seen in 3D such that it appears as a quadrilateral. Here is an example image that I would like to process:
I know the coordinates of the 4 corners of the quadrilateral in the image.
I have been playing around with AffineTransform, specifically the shear method. However I can not find any good information on how to properly determine the shx and shy values for an arbitrary quadrilateral.
The final image also needs to be a rectangle that does not include any of the black background, just the internal image. So I need some way of selecting only the quadrilateral for the transformation. I tried using java.awt Shapes like Polygon and Area to describe the quadrilateral, however it only seemed to account for the outline and not the pixels contained in the Shape.
I was able to solve this with projective transformations. It doesn't run as fast I would have liked but still works. It takes about 24 seconds to perform 1000 iterations, on my computer; I was aiming for 60 fps at least. I thought maybe Java would have a built-in way of dealing with these image transformations.
Here is the output image:
Here is my code:
/*
* File: ImageUtility.java
* Package: utility
* Author: Zachary Gill
*/
package utility;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import javax.imageio.ImageIO;
import math.matrix.Matrix3;
import math.vector.Vector;
/**
* Handles image operations.
*/
public class ImageUtility {
public static void main(String[] args) throws Exception {
File image = new File("test2.jpg");
BufferedImage src = loadImage(image);
List<Vector> srcBounds = new ArrayList<>();
srcBounds.add(new Vector(439, 42));
srcBounds.add(new Vector(841, 3));
srcBounds.add(new Vector(816, 574));
srcBounds.add(new Vector(472, 683));
int width = (int) ((Math.abs(srcBounds.get(1).getX() - srcBounds.get(0).getX()) + Math.abs(srcBounds.get(3).getX() - srcBounds.get(2).getX())) / 2);
int height = (int) ((Math.abs(srcBounds.get(3).getY() - srcBounds.get(0).getY()) + Math.abs(srcBounds.get(2).getY() - srcBounds.get(1).getY())) / 2);
BufferedImage dest = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
List<Vector> destBounds = getBoundsForImage(dest);
transformImage(src, srcBounds, dest, destBounds);
ImageIO.write(dest, "jpg", new File("result.jpg"));
}
/**
* Performs a quad to quad image transformation.
*
* #param src The source image.
* #param srcBounds The bounds from the source image of the quad to transform.
* #param dest The destination image.
* #param destBounds The bounds from the destination image of the quad to place the result of the transformation.
*/
public static void transformImage(BufferedImage src, List<Vector> srcBounds, BufferedImage dest, List<Vector> destBounds) {
Graphics2D destGraphics = dest.createGraphics();
transformImage(src, srcBounds, destGraphics, dest.getWidth(), dest.getHeight(), destBounds);
destGraphics.dispose();
}
/**
* Performs a quad to quad image transformation.
*
* #param src The source image.
* #param srcBounds The bounds from the source image of the quad to transform.
* #param dest The destination graphics.
* #param destWidth The width of the destination graphics.
* #param destHeight The height of the destination graphics.
* #param destBounds The bounds from the destination graphics of the quad to place the result of the transformation.
*/
#SuppressWarnings("IntegerDivisionInFloatingPointContext")
public static void transformImage(BufferedImage src, List<Vector> srcBounds, Graphics2D dest, int destWidth, int destHeight, List<Vector> destBounds) {
if ((src == null) || (srcBounds == null) || (dest == null) || (destBounds == null) ||
(srcBounds.size() != 4) || (destBounds.size() != 4)) {
return;
}
Matrix3 projectiveMatrix = calculateProjectiveMatrix(srcBounds, destBounds);
if (projectiveMatrix == null) {
return;
}
final int filterColor = new Color(0, 255, 0).getRGB();
BufferedImage maskImage = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D maskGraphics = maskImage.createGraphics();
maskGraphics.setColor(new Color(filterColor));
maskGraphics.fillRect(0, 0, maskImage.getWidth(), maskImage.getHeight());
Polygon mask = new Polygon(
destBounds.stream().map(e -> (int) e.getX()).mapToInt(Integer::valueOf).toArray(),
destBounds.stream().map(e -> (int) e.getY()).mapToInt(Integer::valueOf).toArray(),
4
);
Vector maskCenter = Vector.averageVector(destBounds);
maskGraphics.setColor(new Color(0, 0, 0));
maskGraphics.fillPolygon(mask);
maskGraphics.dispose();
int srcWidth = src.getWidth();
int srcHeight = src.getHeight();
int maskWidth = maskImage.getWidth();
int maskHeight = maskImage.getHeight();
int[] srcData = ((DataBufferInt) src.getRaster().getDataBuffer()).getData();
int[] maskData = ((DataBufferInt) maskImage.getRaster().getDataBuffer()).getData();
Set<Integer> visited = new HashSet<>();
Stack<Point> stack = new Stack<>();
stack.push(new Point((int) maskCenter.getX(), (int) maskCenter.getY()));
while (!stack.isEmpty()) {
Point p = stack.pop();
int x = (int) p.getX();
int y = (int) p.getY();
int index = (y * maskImage.getWidth()) + x;
if ((x < 0) || (x >= maskWidth) || (y < 0) || (y >= maskHeight) ||
visited.contains(index) || (maskData[y * maskWidth + x] == filterColor)) {
continue;
}
visited.add(index);
stack.push(new Point(x + 1, y));
stack.push(new Point(x - 1, y));
stack.push(new Point(x, y + 1));
stack.push(new Point(x, y - 1));
}
visited.parallelStream().forEach(p -> {
Vector homogeneousSourcePoint = projectiveMatrix.multiply(new Vector(p % maskWidth, p / maskWidth, 1.0));
int sX = BoundUtility.truncateNum(homogeneousSourcePoint.getX() / homogeneousSourcePoint.getZ(), 0, srcWidth - 1).intValue();
int sY = BoundUtility.truncateNum(homogeneousSourcePoint.getY() / homogeneousSourcePoint.getZ(), 0, srcHeight - 1).intValue();
maskData[p] = srcData[sY * srcWidth + sX];
});
visited.clear();
Shape saveClip = dest.getClip();
dest.setClip(mask);
dest.drawImage(maskImage, 0, 0, maskWidth, maskHeight, null);
dest.setClip(saveClip);
}
/**
* Calculates the projective matrix for a quad to quad image transformation.
*
* #param src The bounds of the quad in the source.
* #param dest The bounds of the quad in the destination.
* #return The projective matrix.
*/
private static Matrix3 calculateProjectiveMatrix(List<Vector> src, List<Vector> dest) {
Matrix3 projectiveMatrixSrc = new Matrix3(new double[] {
src.get(0).getX(), src.get(1).getX(), src.get(3).getX(),
src.get(0).getY(), src.get(1).getY(), src.get(3).getY(),
1.0, 1.0, 1.0});
Vector solutionSrc = new Vector(src.get(2).getX(), src.get(2).getY(), 1.0);
Vector coordinateSystemSrc = projectiveMatrixSrc.solveSystem(solutionSrc);
Matrix3 coordinateMatrixSrc = new Matrix3(new double[] {
coordinateSystemSrc.getX(), coordinateSystemSrc.getY(), coordinateSystemSrc.getZ(),
coordinateSystemSrc.getX(), coordinateSystemSrc.getY(), coordinateSystemSrc.getZ(),
coordinateSystemSrc.getX(), coordinateSystemSrc.getY(), coordinateSystemSrc.getZ()
});
projectiveMatrixSrc = projectiveMatrixSrc.scale(coordinateMatrixSrc);
Matrix3 projectiveMatrixDest = new Matrix3(new double[] {
dest.get(0).getX(), dest.get(1).getX(), dest.get(3).getX(),
dest.get(0).getY(), dest.get(1).getY(), dest.get(3).getY(),
1.0, 1.0, 1.0});
Vector solutionDest = new Vector(dest.get(2).getX(), dest.get(2).getY(), 1.0);
Vector coordinateSystemDest = projectiveMatrixDest.solveSystem(solutionDest);
Matrix3 coordinateMatrixDest = new Matrix3(new double[] {
coordinateSystemDest.getX(), coordinateSystemDest.getY(), coordinateSystemDest.getZ(),
coordinateSystemDest.getX(), coordinateSystemDest.getY(), coordinateSystemDest.getZ(),
coordinateSystemDest.getX(), coordinateSystemDest.getY(), coordinateSystemDest.getZ()
});
projectiveMatrixDest = projectiveMatrixDest.scale(coordinateMatrixDest);
try {
projectiveMatrixDest = projectiveMatrixDest.inverse();
} catch (ArithmeticException ignored) {
return null;
}
return projectiveMatrixSrc.multiply(projectiveMatrixDest);
}
/**
* Loads an image.
*
* #param file The image file.
* #return The BufferedImage loaded from the file, or null if there was an error.
*/
public static BufferedImage loadImage(File file) {
try {
BufferedImage tmpImage = ImageIO.read(file);
BufferedImage image = new BufferedImage(tmpImage.getWidth(), tmpImage.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D imageGraphics = image.createGraphics();
imageGraphics.drawImage(tmpImage, 0, 0, tmpImage.getWidth(), tmpImage.getHeight(), null);
imageGraphics.dispose();
return image;
} catch (Exception ignored) {
return null;
}
}
/**
* Creates the default bounds for an image.
*
* #param image The image.
* #return The default bounds for the image.
*/
public static List<Vector> getBoundsForImage(BufferedImage image) {
List<Vector> bounds = new ArrayList<>();
bounds.add(new Vector(0, 0));
bounds.add(new Vector(image.getWidth() - 1, 0));
bounds.add(new Vector(image.getWidth() - 1, image.getHeight() - 1));
bounds.add(new Vector(0, image.getHeight() - 1));
return bounds;
}
}
If you would like to run this yourself, the Matrix3 and Vector operations can be found here:
https://github.com/ZGorlock/Graphy/blob/master/src/math/matrix/Matrix3.java
https://github.com/ZGorlock/Graphy/blob/master/src/math/vector/Vector.java
Also, here is some good reference material for projective transformations:
http://graphics.cs.cmu.edu/courses/15-463/2006_fall/www/Papers/proj.pdf
https://mc.ai/part-ii-projective-transformations-in-2d/
I have an image with transparent background. I'd like to rotate this image to a specific angle and keep the transparent background for the resulting image. For this purpose I use the following method:
public static BufferedImage rotateImage(BufferedImage image, double angle, Color backgroundColor) {
System.out.println(image.getType());
double theta = Math.toRadians(angle);
double sin = Math.abs(Math.sin(theta));
double cos = Math.abs(Math.cos(theta));
int w = image.getWidth();
int h = image.getHeight();
int newW = (int) Math.floor(w * cos + h * sin);
int newH = (int) Math.floor(h * cos + w * sin);
BufferedImage tmp = new BufferedImage(newW, newH, image.getType());
Graphics2D g2d = tmp.createGraphics();
if (backgroundColor != null) {
g2d.setColor(backgroundColor);
g2d.fillRect(0, 0, newW, newH);
}
g2d.fillRect(0, 0, newW, newH);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2d.translate((newW - w) / 2, (newH - h) / 2);
g2d.rotate(theta, w / 2, h / 2);
g2d.drawImage(image, 0, 0, null);
g2d.dispose();
return tmp;
}
I invoke it with background=null:
BufferedImage image = ImageIO.read(file);
rotateImage(image, 4, null);
ImageIO.write(bi, "PNG", new File("image.png"));
but the background of the resulting image.png is WHITE. What am I doing wrong and how to properly keep the transparent background for image.png?
I'm a bit puzzled about the behavior of Graphics.drawImage(). Maybe somebody else can comment about it.
However, Graphics2D.drawRenderedImage() works a treat. It takes an AffineTransform to control the rotation. The below example nicely works. You probably have additional requirement about the final image size and the location of the rotated image.
import javax.imageio.ImageIO;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
public class ImageRotation {
public static void main(String[] args) {
ImageRotation rotation = new ImageRotation();
rotation.rotate("input.png", 45, "output.png");
}
public void rotate(String inputImageFilename, double angle, String outputImageFilename) {
try {
BufferedImage inputImage = ImageIO.read(new File(inputImageFilename));
BufferedImage outputImage = rotateImage(inputImage, angle);
ImageIO.write(outputImage, "PNG", new File(outputImageFilename));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private BufferedImage rotateImage(BufferedImage sourceImage, double angle) {
int width = sourceImage.getWidth();
int height = sourceImage.getHeight();
BufferedImage destImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = destImage.createGraphics();
AffineTransform transform = new AffineTransform();
transform.rotate(angle / 180 * Math.PI, width / 2 , height / 2);
g2d.drawRenderedImage(sourceImage, transform);
g2d.dispose();
return destImage;
}
}
Update
While the above code works for most PNGs, it does not work for the image that alexanoid is using. I've analyzed the image:
It's a grayscale image without a color palette (PNG color type 0) .
It uses simple transparency with a 2 byte long tRNS chunk.
As far as I can tell that's perfectly legal. However, ImageIO does not implement this combination. If the image has no palette, it simply ignores the tRNS chunk and therefore ignores the transparency information. That's most likely a bug.
You basically have two options now:
Look for an alternative library to read PNG files.
Fix the transparency after you have read the PNG file. This only works if know that the image used the particular problematic format.
Input and output for working PNG files
Input image:
Ouptput Image:
I'm having troubles scaling font to fit in background width. I have a 1000 height and 350 width background, and I'm trying to scale font when it's bigger than background.
I've done several test with different font and results are the same, some letters missed or blank spaces at the end of text.
This is the code:
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class PruebaStackoverflow {
public static void main(String[] args) {
String titleText = null;
Graphics2D g2D = null;
Font testFont = null;
File imageGrayBackgroundFile = new File(
"resources/pruebaAltaResolucionGris.png");
File destinationImageGray = new File("resources/outputTextGray.png");
BufferedImage background = readImage(imageGrayBackgroundFile);
titleText = "Lorem ipsum dolor sit amet asdkf sdm";
testFont = new Font("Lucida Console", Font.PLAIN, 50);
g2D = background.createGraphics();
g2D.setColor(Color.BLACK);
g2D.setFont(testFont);
g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2D = scaleFontFromFontMetrics(g2D, background, titleText);
g2D.drawString(titleText, 0, 150);
g2D.dispose();
writeImage(destinationImageGray, background);
}
private static Graphics2D scaleFontFromFontMetrics(Graphics2D g2D,
BufferedImage backgroundImage, String text) {
double xScale;
double yScale;
double scale;
Integer backgroundWidth = null;
Integer backgroundHeight = null;
Integer textWidth = null;
Integer textHeigth = null;
backgroundWidth = backgroundImage.getWidth();
backgroundHeight = backgroundImage.getHeight();
Font f = g2D.getFont();
FontMetrics fm = g2D.getFontMetrics(f);
textWidth = fm.stringWidth(text);
textHeigth = fm.getHeight();
xScale = backgroundWidth / (double) textWidth;
yScale = backgroundHeight / (double) textHeigth;
if (xScale > yScale) {
scale = yScale;
} else {
scale = xScale;
}
g2D.setFont(f.deriveFont(AffineTransform.getScaleInstance(scale, scale)));
return g2D;
}
private static BufferedImage readImage(File sourceImage) {
BufferedImage bufferedImage = null;
try {
bufferedImage = ImageIO.read(sourceImage);
} catch (IOException e1) {
e1.printStackTrace();
}
return bufferedImage;
}
private static void writeImage(File destinationImage,
BufferedImage bufferedImage) {
try {
ImageIO.write(bufferedImage, "png", destinationImage);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("Image Saved");
}
}
this is the text to scale "Lorem ipsum dolor sit amet asdkf sdm"
and this is text scaled with affine transformation.
output image with font scaled and 'm' letter missed
I hope that you may help me, thanks
you can measure the length of a string and verify if it fits in your content.
int lengthInPixel = graphics.getFontMetrics().stringWidth("Lorem ipsum dolor sit amet asdkf sdm")
Following on from my comment earlier, here's a solution where the closest font size to the image width is used. The text is drawn to a separate image, resized, then drawn to the final image. This is all done in the createTextImage() method. Note, I have created a background image rather than using a file.
The outcome may not be as crisp as desired, but you could experiment with different algorithms for resizing. Hopefully it'll give you a starting point.
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class Main {
public static void main(String[] args) {
String titleText = "Lorem ipsum dolor sit amet asdkf sdm";
Font initialFont = new Font("Lucida Console", Font.PLAIN, 50);
BufferedImage textImg = createTextImage(titleText, 350, 1000, 150,
initialFont, Color.BLACK, Color.GRAY);
writeImage(new File("outputTextGray.png"), textImg);
}
private static BufferedImage createTextImage(String text, int targetWidth,
int targetHeight, int textYOffset, Font font, Color textColor, Color bgColor) {
// The final image
BufferedImage finalImg = createBackgroundImg(targetWidth, targetHeight, bgColor);
Graphics2D finalImgG = finalImg.createGraphics();
Font closestFont = scaleFont(finalImg, font, text);
finalImgG.setFont(closestFont);
// Create new image to fit text
int textWidth = finalImgG.getFontMetrics().stringWidth(text);
int textHeight = finalImgG.getFontMetrics().getHeight();
BufferedImage textImg = createBackgroundImg(textWidth, textHeight * 2, bgColor);
// Draw text
Graphics2D textImgG = textImg.createGraphics();
textImgG.setFont(closestFont);
textImgG.setColor(textColor);
textImgG.drawString(text, 0, textHeight);
// Scale text image
double scale = getScale(textImg.getWidth(), textImg.getHeight(),
targetWidth, targetHeight);
Image resized = textImg.getScaledInstance((int) (textImg.getWidth() * scale),
(int) (textImg.getHeight() * scale), Image.SCALE_SMOOTH);
// Draw text image onto final image
finalImgG.drawImage(resized, 0, textYOffset, null);
return finalImg;
}
private static Font scaleFont(BufferedImage img, Font font, String text) {
Graphics2D g2D = img.createGraphics();
g2D.setFont(font);
double scale = getScale(g2D.getFontMetrics().stringWidth(text),
g2D.getFontMetrics().getHeight(), img.getWidth(),
img.getHeight());
return g2D.getFont().deriveFont(AffineTransform.getScaleInstance(scale, scale));
}
private static double getScale(int width, int height, int targetWidth, int targetHeight) {
assert width > 0 && height > 0 : "width and height must be > 0";
double scaleX = (double) targetWidth / width;
double scaleY = (double) targetHeight / height;
return scaleX > scaleY ? scaleY : scaleX;
}
private static BufferedImage createBackgroundImg(int width, int height, Color color) {
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < bufferedImage.getWidth(); x++) {
for (int y = 0; y < bufferedImage.getHeight(); y++) {
bufferedImage.setRGB(x, y, color.getRGB());
}
}
return bufferedImage;
}
private static void writeImage(File destinationImage,
BufferedImage bufferedImage) {
try {
ImageIO.write(bufferedImage, "png", destinationImage);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("Image Saved");
}
}
Something I do to fit a game-screen to a certain resolution, is to pre-calculate the ratio, after testing its maximum bound. Something like this (I changed my actual code to your case):
double calculateScaling( BufferedImage image, String text, Font font ){
//These are final to avoid accidental mending, since they are
//the base for our calculations.
//Belive me, it took me a while to debug the
//scale calculation when I made this for my games :P
/**
* imageWidth and imageHeight are the bounds
*/
final int imageWidth = image.getWidth();
final int imageHeight = image.getHeight();
Graphics2D g2 = image.createGraphics();
FontMetrics fm = g2.getFontMetrics( font );
/**
* requestedStringWidthSize e requestedStringHeightSize are the measures needed
* to draw the text WITHOUT resizing.
*/
final int requestedStringWidthSize = fm.stringWidth( text );
final int requestedStringHeightSize = fm.getHeight();
double stringHeightSizeToUse = imageHeight;
double stringWidthSizeToUse;
double scale = stringHeightSizeToUse/requestedStringHeightSize;
stringWidthSizeToUse = scale*requestedStringWidthSize;
/**
* Checking if fill in height makes the text go out of bound in width,
* if it does, it rescalates it to size it to maximum width.
*/
if( imageWidth < ((int)(Math.rint(stringWidthSizeToUse))) ) {
stringWidthSizeToUse = imageWidth;
scale = stringWidthSizeToUse/requestedStringWidthSize;
//stringHeightSizeToUse = scale*requestedStringHeightSize;
}
g2.dispose(); //we created this to use fontmetrics, now we don't need it.
return scale;
}
In my game, I would store the scale as float instead of double to avoid heavy calculations on the run, but for you it's just a simple scaling, right?
All you have to do now is to study the code and implement it to yours.
I hope I have helped.
Have a nice day. :)
I'm loading an image using C++ and feeding the pixels to JNI via a ByteBuffer. I know the pixels are being fed just fine because if the images are square, they render perfectly fine. If they are rectangular, they get distorted. I've also saved the Image back successfully in the DLL and it works. Java unfortunately gave up on me (unless it's square-like). I cannot figure out why! What am I doing wrong?
package library;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class Frame extends JFrame {
public Frame(int Width, int Height, String FrameName, BufferedImage Buffer) {
setName(FrameName);
setSize(Width, Height);
getContentPane().add(new JLabel(new ImageIcon(Buffer)));
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setVisible(true);
}
}
All the loading:
package library;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.nio.ByteBuffer;
class SharedLibrary {
static{System.loadLibrary("TestDLL");}
private static native void GetGLBuffer(ByteBuffer Buffer);
private ByteBuffer Buffer = null;
private int ByteSize = 0, Width = 0, Height = 0, BitsPerPixel = 32;
public SharedLibrary(int ImageWidth, int ImageHeight) throws IOException {
Width = ImageWidth;
Height = ImageHeight;
ByteSize = ((Width * BitsPerPixel + 31) / 32) * 4 * Height; //Compute Image Size in Bytes.
Buffer = ByteBuffer.allocateDirect(ByteSize); //Allocate Space for the image data.
GetGLBuffer(Buffer); //Fill the buffer with Image data from the DLL.
byte[] Bytes = new byte[ByteSize];
Buffer.get(Bytes);
BufferedImage Image = new BufferedImage(Width, Height, BufferedImage.TYPE_3BYTE_BGR);
WritableRaster raster = (WritableRaster) Image.getData();
raster.setPixels(0, 0, Width, Height, ByteBufferToIntBuffer(Bytes));
Image.setData(raster);
Frame F = new Frame(Width, Height, "", Image);
}
private int[] ByteBufferToIntBuffer(byte[] Data) {
int IntBuffer[] = new int[Data.length];
for (int I = 0; I < Data.length; I++) {
IntBuffer[I] = (int)Data[I] & 0xFF;
}
return IntBuffer;
}
}
The above Image Gets drawn perfectly because it is almost square. If I resize it to a rectangle, it gets distorted. Example:
Gets distorted and looks like:
I am trying to resized a bufferedimage. I am able to store it and show up on a jframe no problems but I can't seem to resize it. Any tips on how I can change this to make it work and show the image as a 200*200 file would be great
private void profPic(){
String path = factory.getString("bottle");
BufferedImage img = ImageIO.read(new File(path));
}
public static BufferedImage resize(BufferedImage img, int newW, int newH) {
int w = img.getWidth();
int h = img.getHeight();
BufferedImage dimg = new BufferedImage(newW, newH, img.getType());
Graphics2D g = dimg.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(img, 0, 0, newW, newH, 0, 0, w, h, null);
g.dispose();
return dimg;
}
Updated answer
I cannot recall why my original answer worked but having tested it in a separate environment, I agree, the original accepted answer doesn't work (why I said it did I cannot remember either). This, on the other hand, did work:
public static BufferedImage resize(BufferedImage img, int newW, int newH) {
Image tmp = img.getScaledInstance(newW, newH, Image.SCALE_SMOOTH);
BufferedImage dimg = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = dimg.createGraphics();
g2d.drawImage(tmp, 0, 0, null);
g2d.dispose();
return dimg;
}
If all that is required is to resize a BufferedImage in the resize method, then the Thumbnailator library can do that fairly easily:
public static BufferedImage resize(BufferedImage img, int newW, int newH) {
return Thumbnails.of(img).size(newW, newH).asBufferedImage();
}
The above code will resize the img to fit the dimensions of newW and newH while maintaining the aspect ratio of the original image.
If maintaining the aspect ratio is not required and resizing to exactly the given dimensions is required, then the forceSize method can be used in place of the size method:
public static BufferedImage resize(BufferedImage img, int newW, int newH) {
return Thumbnails.of(img).forceSize(newW, newH).asBufferedImage();
}
Using the Image.getScaledInstance method will not guarantee that the aspect ratio of the original image will be maintained for the resized image, and furthermore, it is in general very slow.
Thumbnailator uses a technique to progressively resize the image which can be several times faster than Image.getScaledInstance while achieving an image quality which generally is comparable.
Disclaimer: I am the maintainer of this library.
Here's some code that I have used to resize bufferedimages, no frills, pretty quick:
public static BufferedImage scale(BufferedImage src, int w, int h)
{
BufferedImage img =
new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
int x, y;
int ww = src.getWidth();
int hh = src.getHeight();
int[] ys = new int[h];
for (y = 0; y < h; y++)
ys[y] = y * hh / h;
for (x = 0; x < w; x++) {
int newX = x * ww / w;
for (y = 0; y < h; y++) {
int col = src.getRGB(newX, ys[y]);
img.setRGB(x, y, col);
}
}
return img;
}
This class resize from a file and get the format name:
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageInputStream;
import org.apache.commons.io.IOUtils;
public class ImageResizer {
public static void main(String as[]) throws IOException{
File f = new File("C:/Users/samsungrob/Desktop/shuttle.jpg");
byte[] ba = resize(f, 600, 600);
IOUtils.write(ba, new FileOutputStream( new File("C:/Users/samsungrob/Desktop/shuttle_resized.jpg") ) );
}
public static byte[] resize(File file,
int maxWidth, int maxHeight) throws IOException{
int scaledWidth = 0, scaledHeight = 0;
BufferedImage img = ImageIO.read((ImageInputStream) file );
scaledWidth = maxWidth;
scaledHeight = (int) (img.getHeight() * ( (double) scaledWidth / img.getWidth() ));
if (scaledHeight> maxHeight) {
scaledHeight = maxHeight;
scaledWidth= (int) (img.getWidth() * ( (double) scaledHeight/ img.getHeight() ));
if (scaledWidth > maxWidth) {
scaledWidth = maxWidth;
scaledHeight = maxHeight;
}
}
Image resized = img.getScaledInstance( scaledWidth, scaledHeight, Image.SCALE_SMOOTH);
BufferedImage buffered = new BufferedImage(scaledWidth, scaledHeight, Image.SCALE_REPLICATE);
buffered.getGraphics().drawImage(resized, 0, 0 , null);
String formatName = getFormatName( file ) ;
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageIO.write(buffered,
formatName,
out);
return out.toByteArray();
}
private static String getFormatName(ImageInputStream iis) {
try {
// Find all image readers that recognize the image format
Iterator iter = ImageIO.getImageReaders(iis);
if (!iter.hasNext()) {
// No readers found
return null;
}
// Use the first reader
ImageReader reader = (ImageReader)iter.next();
// Close stream
iis.close();
// Return the format name
return reader.getFormatName();
} catch (IOException e) {
}
return null;
}
private static String getFormatName(File file) throws IOException {
return getFormatName( ImageIO.createImageInputStream(file) );
}
private static String getFormatName(InputStream is) throws IOException {
return getFormatName( ImageIO.createImageInputStream(is) );
}
}
This is a shortened version of what is actually happening in imgscalr, if you just want to use the "balanced" smoothing:
/**
* Takes a BufferedImage and resizes it according to the provided targetSize
*
* #param src the source BufferedImage
* #param targetSize maximum height (if portrait) or width (if landscape)
* #return a resized version of the provided BufferedImage
*/
private BufferedImage resize(BufferedImage src, int targetSize) {
if (targetSize <= 0) {
return src; //this can't be resized
}
int targetWidth = targetSize;
int targetHeight = targetSize;
float ratio = ((float) src.getHeight() / (float) src.getWidth());
if (ratio <= 1) { //square or landscape-oriented image
targetHeight = (int) Math.ceil((float) targetWidth * ratio);
} else { //portrait image
targetWidth = Math.round((float) targetHeight / ratio);
}
BufferedImage bi = new BufferedImage(targetWidth, targetHeight, src.getTransparency() == Transparency.OPAQUE ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = bi.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); //produces a balanced resizing (fast and decent quality)
g2d.drawImage(src, 0, 0, targetWidth, targetHeight, null);
g2d.dispose();
return bi;
}
try the imgscalr library. Best lib i found- very fast, good quality and simple to use
BufferedImage thumbnail = Scalr.resize(image, 150);
deprecated link: http://www.thebuzzmedia.com/software/imgscalr-java-image-scaling-library/
Apache 2 License
Check this out, it helps:
BufferedImage bImage = ImageIO.read(new File(C:\image.jpg);
BufferedImage thumbnail = Scalr.resize(bImage, Scalr.Method.SPEED, Scalr.Mode.FIT_TO_WIDTH,
750, 150, Scalr.OP_ANTIALIAS);