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);
Related
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. :)
In the script it is going from around the 300x300 mark down to 60x60. Need to improve the overall image quality as it is coming out very poorly at the moment.
public static Boolean resizeImage(String sourceImg, String destImg, Integer Width, Integer Height, Integer whiteSpaceAmount)
{
BufferedImage origImage;
try
{
origImage = ImageIO.read(new File(sourceImg));
int type = origImage.getType() == 0? BufferedImage.TYPE_INT_ARGB : origImage.getType();
int fHeight = Height;
int fWidth = Width;
int whiteSpace = Height + whiteSpaceAmount; //Formatting all to squares so don't need two whiteSpace calcs..
double aspectRatio;
//Work out the resized dimensions
if (origImage.getHeight() > origImage.getWidth()) //If the pictures height is greater than the width then scale appropriately.
{
fHeight = Height; //Set the height to 60 as it is the biggest side.
aspectRatio = (double)origImage.getWidth() / (double)origImage.getHeight(); //Get the aspect ratio of the picture.
fWidth = (int)Math.round(Width * aspectRatio); //Sets the width as created via the aspect ratio.
}
else if (origImage.getHeight() < origImage.getWidth()) //If the pictures width is greater than the height scale appropriately.
{
fWidth = Width; //Set the height to 60 as it is the biggest side.
aspectRatio = (double)origImage.getHeight() / (double)origImage.getWidth(); //Get the aspect ratio of the picture.
fHeight = (int)Math.round(Height * aspectRatio); //Sets the height as created via the aspect ratio.
}
int extraHeight = whiteSpace - fHeight;
int extraWidth = whiteSpace - fWidth;
BufferedImage resizedImage = new BufferedImage(whiteSpace, whiteSpace, type);
Graphics2D g = resizedImage.createGraphics();
g.setColor(Color.white);
g.fillRect(0, 0, whiteSpace, whiteSpace);
g.setComposite(AlphaComposite.Src);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.drawImage(origImage, extraWidth/2, extraHeight/2, fWidth, fHeight, null);
g.dispose();
ImageIO.write(resizedImage, "jpg", new File(destImg));
}
catch (IOException ex)
{
return false;
}
return true;
}
Really just need to know if their is something I can plug in that will bump up the quality or if I need to look at something else entirely.
EDIT: Picture comparison.
Source, just picked a random washing machine from google.
http://www.essexappliances.co.uk/images/categories/washing-machine.jpg
The same picture converted in Photoshop to what I need it to be.
http://imgur.com/78B1p
What it looks like being converted like this.
http://imgur.com/8WlXD
Scaling an image down over a large range is inherently dangerous (from the point of view of quality), especially using a single step.
The recommended method is to use a divide and conquer method. Basically, you scale the image down in steps of 50% until you reach your desired size.
So, I took the original image of 650x748 and scaled it down to fit within a 60x60 region (52x60).
Divide and conquer compared to one step...
public class TestImageResize {
public static void main(String[] args) {
new TestImageResize();
}
public TestImageResize() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new ScalePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class ScalePane extends JPanel {
private BufferedImage original;
private BufferedImage scaled;
public ScalePane() {
try {
original = ImageIO.read(new File("path/to/master.jpg"));
scaled = getScaledInstanceToFit(original, new Dimension(60, 60));
ImageIO.write(scaled, "jpg", new File("scaled.jpg"));
BufferedImage image = new BufferedImage(52, 60, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = image.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.drawImage(original, 0, 0, 52, 60, this);
g2d.dispose();
ImageIO.write(image, "jpg", new File("test.jpg"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
if (original != null) {
if (scaled != null) {
size.width = original.getWidth() + scaled.getWidth();
size.height = original.getHeight();
} else {
size.width = original.getWidth();
size.height = original.getHeight();
}
}
return size;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if (original != null) {
int x = 0;
int y = (getHeight() - original.getHeight()) / 2;;
if (scaled != null) {
x = (getWidth() - (original.getWidth() + scaled.getWidth())) / 2;
} else {
x = (getWidth() - original.getWidth()) / 2;
}
g2d.drawImage(original, x, y, this);
if (scaled != null) {
x += original.getWidth();
y = (getHeight() - scaled.getHeight()) / 2;
g2d.drawImage(scaled, x, y, this);
}
}
g2d.dispose();
}
public BufferedImage getScaledInstanceToFit(BufferedImage img, Dimension size) {
float scaleFactor = getScaleFactorToFit(img, size);
return getScaledInstance(img, scaleFactor);
}
public float getScaleFactorToFit(BufferedImage img, Dimension size) {
float scale = 1f;
if (img != null) {
int imageWidth = img.getWidth();
int imageHeight = img.getHeight();
scale = getScaleFactorToFit(new Dimension(imageWidth, imageHeight), size);
}
return scale;
}
public float getScaleFactorToFit(Dimension original, Dimension toFit) {
float scale = 1f;
if (original != null && toFit != null) {
float dScaleWidth = getScaleFactor(original.width, toFit.width);
float dScaleHeight = getScaleFactor(original.height, toFit.height);
scale = Math.min(dScaleHeight, dScaleWidth);
}
return scale;
}
public float getScaleFactor(int iMasterSize, int iTargetSize) {
float scale = 1;
if (iMasterSize > iTargetSize) {
scale = (float) iTargetSize / (float) iMasterSize;
} else {
scale = (float) iTargetSize / (float) iMasterSize;
}
return scale;
}
public BufferedImage getScaledInstance(BufferedImage img, double dScaleFactor) {
BufferedImage imgBuffer = null;
imgBuffer = getScaledInstance(img, dScaleFactor, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true);
return imgBuffer;
}
protected BufferedImage getScaledInstance(BufferedImage img, double dScaleFactor, Object hint, boolean higherQuality) {
int targetWidth = (int) Math.round(img.getWidth() * dScaleFactor);
int targetHeight = (int) Math.round(img.getHeight() * dScaleFactor);
int type = (img.getTransparency() == Transparency.OPAQUE)
? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = (BufferedImage) img;
if (targetHeight > 0 || targetWidth > 0) {
int w, h;
if (higherQuality) {
w = img.getWidth();
h = img.getHeight();
} else {
w = targetWidth;
h = targetHeight;
}
do {
if (higherQuality && w > targetWidth) {
w /= 2;
if (w < targetWidth) {
w = targetWidth;
}
}
if (higherQuality && h > targetHeight) {
h /= 2;
if (h < targetHeight) {
h = targetHeight;
}
}
BufferedImage tmp = new BufferedImage(Math.max(w, 1), Math.max(h, 1), type);
Graphics2D g2 = tmp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(ret, 0, 0, w, h, null);
g2.dispose();
ret = tmp;
} while (w != targetWidth || h != targetHeight);
} else {
ret = new BufferedImage(1, 1, type);
}
return ret;
}
}
}
You may, also, find The Perils of Image.getScaledInstance() of interest.
The issue you are seeing is actually related to the resampling filter used for downscaling. Obviously, the one used by your library is a bad one for the situation. Nearest neighbor, bilinear and bicubic are typical bad examples to be used when downscaling. I don't know the exact resampling filter Photoshop uses, but I used 3-lobed lanczos and got the following result:
So, to solve your problem, you need to use a smarter resampling filter.
dutchman, this is why I maintain the imgscalr library -- to make this kind of stuff painfully easy.
In your example, a single method call would do the trick, right after your first ImageIO.read line:
origImage = ImageIO.read(new File(sourceImg));
you can do the following to get what you want (javadoc for this method):
origImage = Scalr.resize(origImage, Method.ULTRA_QUALITY, 60);
and if that still looked a little jagged (because you are removing so much information from the image, you can add the following OP to the command to apply a light anti-aliasing filter to the image so it looks smoother):
origImage = Scalr.resize(origImage, Method.ULTRA_QUALITY, 60, Scalr.OP_ANTIALIAS);
That will replace all the remainder of the code logic you have. The only other thing I would recommend is saving out your really small samples as PNG's so there is no more compression/lossy conversion done on the image OR make sure you use little to none compression on the JPG if you really want it in JPG format. (Here is an article on how to do it; it utilizes the ImageWriteParam class)
imgscalr is licensed under an Apache 2 license and hosted on GitHub so you can do what you want with it; it also includes asynchronous scaling support if you are using the library in a server-side app and queuing up huge numbers of scaling operations and don't want to kill the server.
As already stated, Java's Graphics2D does not provide a very good algorithm for down-scaling. If you don't want to implement a sophisticated algorithm yourself you could try out the current open source libs specialized for this: Thumbnailator, imgscalr and a Java interface for ImageMagick.
While researching for a private project I tried them out (except ImageMagick) and here are the visual results with Photoshop as reference:
A. Thumbnailator 0.4.8 with default settings (no additional internal resizing)
B. imgscalr 4.2 with ULTRA_QUALTY setting
C. Photoshop CS5 bicubic filter (save for web)
D. Graphics2d with all HQ render hints
Here is the used code
Thumbnailator and PS create similar results, while imgscalr seems to be softer. It is subjective which one of the libs creates the preferable results. Another point to consider though is the performance. While Thumbnailator and Graphics2d have similar runtime, imgscalr is considerably slower (with ULTRA_QUALITY) in my benchmarks.
For more info, read this post providing more detail on this matter.
How do I flip an Screenshot image? I can't find my problem anywhere else.Example code:
/*
*#param fileLoc //Location of fileoutput destination
*#param format //"png"
*#param WIDTH //Display.width();
*#param HEIGHT //Display.height();
*/
private void getScreenImage(){
int[] pixels = new int[WIDTH * HEIGHT];
int bindex;
// allocate space for RBG pixels
ByteBuffer fb = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 3);//.order(ByteOrder.nativeOrder());
// grab a copy of the current frame contents as RGB
glReadPixels(0, 0, WIDTH, HEIGHT, GL_RGB, GL_UNSIGNED_BYTE, fb);
BufferedImage image = new BufferedImage(WIDTH, HEIGHT,BufferedImage.TYPE_INT_RGB);
// convert RGB data in ByteBuffer to integer array
for (int i=0; i < pixels.length; i++) {
bindex = i * 3;
pixels[i] =
((fb.get(bindex) << 16)) +
((fb.get(bindex+1) << 8)) +
((fb.get(bindex+2) << 0));
}
try {
//Create a BufferedImage with the RGB pixels then save as PNG
image.setRGB(0, 0, WIDTH, HEIGHT, pixels, 0 , WIDTH);
ImageIO.write(image, format , fileLoc);
}
catch (Exception e) {
System.out.println("ScreenShot() exception: " +e);
}
}
Basically the code works for capturing the screen and storing at as "png" format.
But it output's the image horizontally flipped, because glReadPixels();,
read from bottom-left to top-right.
So how do I flip the image horizontally before I ImageIO.write();?
Thanks in-front,
Rose.
E.G. of flipping an image horizontally using an AffineTransform.
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class Test001 {
public static BufferedImage getFlippedImage(BufferedImage bi) {
BufferedImage flipped = new BufferedImage(
bi.getWidth(),
bi.getHeight(),
bi.getType());
AffineTransform tran = AffineTransform.getTranslateInstance(bi.getWidth(), 0);
AffineTransform flip = AffineTransform.getScaleInstance(-1d, 1d);
tran.concatenate(flip);
Graphics2D g = flipped.createGraphics();
g.setTransform(tran);
g.drawImage(bi, 0, 0, null);
g.dispose();
return flipped;
}
Test001(BufferedImage bi) {
JPanel gui = new JPanel(new GridLayout(1,2,2,2));
gui.add(new JLabel(new ImageIcon(bi)));
gui.add(new JLabel(new ImageIcon(getFlippedImage(bi))));
JOptionPane.showMessageDialog(null, gui);
}
public static void main(String[] args) throws AWTException {
final Robot robot = new Robot();
Runnable r = new Runnable() {
#Override
public void run() {
final BufferedImage bi = robot.createScreenCapture(
new Rectangle(0, 360, 200, 100));
new Test001(bi);
}
};
SwingUtilities.invokeLater(r);
}
}
It's worth noting that it might be faster to simply read the pixels out of the buffer in the order you want them, rather than read them backwards and do a costly transform operation. Additionally, since you know for sure that the BufferedImage is TYPE_INT_RGB it should be safe to write directly into its raster.
ByteBuffer fb = BufferUtils.createByteBuffer(WIDTH * HEIGHT * 3);
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
glReadPixels(0, 0, WIDTH, HEIGHT, GL_RGB, GL_UNSIGNED_BYTE, fb);
int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
for (int i = pixels.length - 1; i >= 0; i--) {
int x = i % WIDTH, y = i / WIDTH * WIDTH;
pixels[y + WIDTH - 1 - x] = (fb.get() & 0xff) << 16 | (fb.get() & 0xff) << 8 | fb.get() & 0xff;
}
I have some problems with rotating images in Java using the AffineTransform class.
I have the following method for creating a rotated (90 degrees) copy of an image:
private BufferedImage createRotatedCopy(BufferedImage img, Rotation rotation) {
int w = img.getWidth();
int h = img.getHeight();
BufferedImage rot = new BufferedImage(h, w, BufferedImage.TYPE_INT_RGB);
double theta;
switch (rotation) {
case CLOCKWISE:
theta = Math.PI / 2;
break;
case COUNTERCLOCKWISE:
theta = -Math.PI / 2;
break;
default:
throw new AssertionError();
}
AffineTransform xform = AffineTransform.getRotateInstance(theta, w / 2, h / 2);
Graphics2D g = (Graphics2D) rot.createGraphics();
g.drawImage(img, xform, null);
g.dispose();
return rot;
}
Rotation is a simple enum with the values NONE, CLOCKWISE and COUNTERCLOCKWISE.
The symptoms of my problems are displayed here:
http://perp.se/so/rotate_problems.html
So, the rotation works OK, but the resulting images aren't anchored to the correct coordinates (or how one should put it). And since I don't really know what the heck I'm doing in the first place (my linear algebra is weak), I don't know how to solve this on my own.
I've tried with some random fiddling with the AffineTransform instance, but it hasn't helped me (of course). I've tried googling (and searching SO), but all examples I've seen basically use the same approach as I do... which doesn't work for me.
Thankful for advice.
If you must express the transform as a single rotation, the anchor point depends on the direction of rotation: Either (w/2, w/2) or (h/2, h/2).
But it's probably simpler to express as translate; rotate; translate, e.g.
AffineTransform xform = new AffineTransform();
xform.translate(0.5*h, 0.5*w);
xform.rotate(theta);
xform.translate(-0.5*w, -0.5*h);
Also consider using getQuadrantRotateInstance instead of getRotateInstance.
Since you only need 90 degree rotation you can avoid using the AffineTransform stuff:
public BufferedImage rotate90DX(BufferedImage bi) {
int width = bi.getWidth();
int height = bi.getHeight();
BufferedImage biFlip = new BufferedImage(height, width, bi.getType());
for(int i=0; i<width; i++)
for(int j=0; j<height; j++)
biFlip.setRGB(height-1-j, width-1-i, bi.getRGB(i, j));
return biFlip;
}
This also avoids cutting off edges of rectangular images.
From: http://snippets.dzone.com/posts/show/2936
You could try an alternative appoach and create an Icon from the image and then use a Rotated Icon.
Or you can try this old code I found in the Sun forums:
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import java.net.*;
import javax.imageio.*;
import javax.swing.*;
public class RotateImage {
public static void main(String[] args) throws IOException {
URL url = new URL("https://blogs.oracle.com/jag/resource/JagHeadshot-small.jpg");
BufferedImage original = ImageIO.read(url);
GraphicsConfiguration gc = getDefaultConfiguration();
BufferedImage rotated1 = tilt(original, -Math.PI/2, gc);
BufferedImage rotated2 = tilt(original, +Math.PI/4, gc);
BufferedImage rotated3 = tilt(original, Math.PI, gc);
display(original, rotated1, rotated2, rotated3);
}
public static BufferedImage tilt(BufferedImage image, double angle, GraphicsConfiguration gc) {
double sin = Math.abs(Math.sin(angle)), cos = Math.abs(Math.cos(angle));
int w = image.getWidth(), h = image.getHeight();
int neww = (int)Math.floor(w*cos+h*sin), newh = (int)Math.floor(h*cos+w*sin);
int transparency = image.getColorModel().getTransparency();
BufferedImage result = gc.createCompatibleImage(neww, newh, transparency);
Graphics2D g = result.createGraphics();
g.translate((neww-w)/2, (newh-h)/2);
g.rotate(angle, w/2, h/2);
g.drawRenderedImage(image, null);
return result;
}
public static GraphicsConfiguration getDefaultConfiguration() {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
return gd.getDefaultConfiguration();
}
public static void display(BufferedImage im1, BufferedImage im2, BufferedImage im3, BufferedImage im4) {
JPanel cp = new JPanel(new GridLayout(2,2));
addImage(cp, im1, "original");
addImage(cp, im2, "rotate -PI/2");
addImage(cp, im3, "rotate +PI/4");
addImage(cp, im4, "rotate PI");
JFrame f = new JFrame("RotateImage");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setContentPane(cp);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
static void addImage(Container cp, BufferedImage im, String title) {
JLabel lbl = new JLabel(new ImageIcon(im));
lbl.setBorder(BorderFactory.createTitledBorder(title));
cp.add(lbl);
}
}
I don't know if this might be your issue.
AffineTransform xform = AffineTransform.getRotateInstance(theta, w / 2, h / 2);
Why not try?
AffineTransform xform = AffineTransform.getRotateInstance(theta);
OR
g.transform(AffineTransform.getRotateInstance(theta));
g.drawImage(img, 0, 0, w/2, h/2, null, null);