Java Image BufferedImage loosing aspect ratio of upright image - java

I am storing an Image in the filesystem like:
FileImageOutputStream fos = new FileImageOutputStream(newFile);
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
fos.close();
And all images are stored correctly in the filesystem. (Also with correct aspect ratio with width and height!)
However, later, I am loading my image like:
File imgFile ...
FileImageInputStream stream = new FileImageInputStream(imgFile);
BufferedImage srcImage = ImageIO.read(stream);
And I want to get the width and height for my images like:
int actualHeight = srcImage.getHeight();
int actualWidth = srcImage.getWidth();
This works totally fine for images in landscape format.
However, for images in upfront format, the width and height is always swapped, so that the width is the height of the orginial image. So, e.g. I have an image that is 200x500 (width x height) and the two integers above are actualHeight = 200 and actualWidth = 500.
Am I missing something?

Most likely, your images are Exif images ("Exif-in-JPEG") from a digital camera/phone. For such images, the pixel data is often stored in the "natural" orientation of the sensor, which is always the same (usually landscape). For portrait images, the orientation is only stored in the Exif metadata, and the ImageIO JPEG plugin doesn't take this orientation into account.
Some cameras, like my Canon DSLR, has an option to do in-camera rotation to match the orientation, but this feature is typically disabled by default. This is obviously only a possible fix if you control the input images.
To fix this in the Java side, you hava some options. You already mentioned using Thumbnailator:
BufferedImage srcImage = Thumbnails.of(new FileInputStream(imgFile))
.scale(1)
.asBufferedImage();
Another option is to use EXIFUtilities from TwelveMonkeys (I'm the author of that library):
IIOImage image = EXIFUtilities.readWithOrientation(imgFile);
BufferedImage srcImage = (BufferedImage) image.getRenderedImage();
Or, if you don't need the other metadata for anything:
BufferedImage srcImage = (BufferedImage) EXIFUtilities.readWithOrientation(imgFile)
.getRenderedImage();

Related

Get image resolution ( theBufferedImage )

how do I get the resolution of an image.
BufferedImage theBufferedImage = ImageIO.read(new File("/tmp/foo.jpg"));
int width = theBufferedImage.getWidth();
int height = theBufferedImage.getHeight();
int resolution = ???
Regards,
saromba
You can't get DPI or any other such information out of BufferedImage, you'll have to examine the original JPEG file for it. It's just a raster image without any metadata. If you're writing a BufferedImage to a file, you can set the DPI. But BufferedImage itself has no concept of DPI, it's just pixels.

android better save image option?

Currently I am using the compress method to save an image taken with the camera hardware on the android phone to the SD card.
try {
BitmapFactory.Options options=new BitmapFactory.Options();
options.inSampleSize = 10;
Bitmap myImage = BitmapFactory.decodeByteArray(imageData, 0,
imageData.length,options);
fileOutputStream = new FileOutputStream(
sdImageMainDirectory.toString() +"/"+fileName+".png");
BufferedOutputStream bos = new BufferedOutputStream(
fileOutputStream);
myImage.compress(CompressFormat.PNG, 100, bos);
bos.flush();
bos.close();
Now this works perfectly fine, however, the quality of image it saves hardly makes it worth taking the picture in the first place. I'm looking for a better way to save the picture at a higher quality.
options.inSampleSize = 10;
here is your loss of quality: This will create an image of 1/10 in heigth and width
From the doc:
If set to a value > 1, requests the decoder to subsample the original
image, returning a smaller image to save memory. The sample size is
the number of pixels in either dimension that correspond to a single
pixel in the decoded bitmap. For example, inSampleSize == 4 returns an
image that is 1/4 the width/height of the original, and 1/16 the
number of pixels. Any value <= 1 is treated the same as 1. Note: the
decoder will try to fulfill this request, but the resulting bitmap may
have different dimensions that precisely what has been requested.
Also, powers of 2 are often faster/easier for the decoder to honor.

Retrieve the dimensions of an image in the filesystem

How can I retrieve the dimensions of an image (typically jpeg, png, jpg and gif), in the local filesystem with Java?
You can use java's image class to get image attributes. Here is the sample -
BufferedImage img = ImageIO.read(new File("imageFilePath"));
int height = img.getHeight();
int width = img.getWidth();
how about this: getting image metadata

Write to 16 bit BufferedImage TYPE_USHORT_GRAY

I'm trying to write 16 bit grayscale imagedata to a png using BufferedImage.TYPE_USHORT_GRAY. Normally I write to an image like so:
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
then:
image.setRGB(x,y,Color.getRGB);
to set the pixels, and finally:
ImageIO.write(image, "png", new File(path + ".png"));
to write to a png image.
But now I have this as image:
BufferedImage imageGray = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY);
How do I go about saving pixels to that format? Using setRGB() with a 16 bit integer doesn't seem to work, when I open the saved png file I see a lot of banding happening.
I tried saving a simple gradient from 0 to 65535 and then using the setRGB() on the grayscale image, and checked the results in Photoshop. I can see the image consists of smaller gradients every 256 rows. I'm guessing either setRGB() or imageIO doesn't work as I would like it to.
Are there workarounds for this? Does imageIO even support the BufferedImage.TYPE_USHORT_GRAY format? Or can it only save 8 bit data? And if it can save 16bit data, how would I go about saving pixel data, preferably in a way like setRGB() works (so for a certain x,y coordinate)?
As pst already commented below my question:
Try using the Raster directly?
Accessing the Raster directly solved the problem :
BufferedImage bi = BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY)
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
Short s = shortData[x][y]
bi.getRaster().setDataElements(x, y, Short[] {s})
}
}
From BufferedImage you can read
public static final int TYPE_USHORT_GRAY
Represents an unsigned short grayscale image, non-indexed). This image has a ComponentColorModel with a CS_GRAY ColorSpace.
So try instantiating your own ColorSpace with the CS_GRAY type (ColorSpace.getInstance(ColorSpace.CS_GRAY) should do it I suppose). This object has a method called fromRGB which you should be able to use...
You probably need to widen the signed 16bit shorts to ints and remove the sign:
int ushort = (int)(shortData[x][y]) & 0xFFFF;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY);
short[] dataArray = ((DataBufferUShort)image.getRaster().getDataBuffer()).getData();
dataArray[y*width+x] = color;
ImageIO.write(image, "png", new File(path + ".png"));

Converting transparent gif / png to jpeg using java

I'd like to convert gif images to jpeg using Java. It works great for most images, but I have a simple transparent gif image:
Input gif image http://img292.imageshack.us/img292/2103/indexedtestal7.gif
[In case the image is missing: it's a blue circle with transparent pixels around it]
When I convert this image using the following code:
File file = new File("indexed_test.gif");
BufferedImage image = ImageIO.read(file);
File f = new File("indexed_test.jpg");
ImageIO.write(image, "jpg", f);
This code works without throwing an Exception, but results an invalid jpeg image:
[In case the image is missing: IE cannot show the jpeg, Firefox shows the image with invalid colors.]
I'm using Java 1.5.
I also tried converting the sample gif to png with gimp and using the png as an input for the Java code. The result is the same.
Is it a bug in the JDK? How can I convert images correctly preferably without 3rd party libraries?
UPDATE:
Answers indicate that jpeg conversion cannot handle transparency correctly (I still think that this is a bug) and suggest a workaround for replacing transparent pixels with predefined color. Both of the suggested methods are quite complex, so I've implemented a simpler one (will post as an answer). I accept the first published answer with this workaround (by Markus). I don't know which implementation is the better. I go for the simplest one still I found a gif where it's not working.
For Java 6 (and 5 too, I think):
BufferedImage bufferedImage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB);
g = bufferedImage.createGraphics();
//Color.WHITE estes the background to white. You can use any other color
g.drawImage(image, 0, 0, bufferedImage.getWidth(), bufferedImage.getHeight(), Color.WHITE, null);
As already mentioned in the UPDATE of the question I've implemented a simpler way of replacing transparent pixels with predefined color:
public static BufferedImage fillTransparentPixels( BufferedImage image,
Color fillColor ) {
int w = image.getWidth();
int h = image.getHeight();
BufferedImage image2 = new BufferedImage(w, h,
BufferedImage.TYPE_INT_RGB);
Graphics2D g = image2.createGraphics();
g.setColor(fillColor);
g.fillRect(0,0,w,h);
g.drawRenderedImage(image, null);
g.dispose();
return image2;
}
and I call this method before jpeg conversion in this way:
if( inputImage.getColorModel().getTransparency() != Transparency.OPAQUE) {
inputImage = fillTransparentPixels(inputImage, Color.WHITE);
}
The problem (at least with png to jpg conversion) is that the color scheme isn't the same, because jpg doesn't support transparency.
What we've done successfully is something along these lines (this is pulled from various bits of code - so please forgive the crudeness of the formatting):
File file = new File("indexed_test.gif");
BufferedImage image = ImageIO.read(file);
int width = image.getWidth();
int height = image.getHeight();
BufferedImage jpgImage;
//you can probably do this without the headless check if you just use the first block
if (GraphicsEnvironment.isHeadless()) {
if (image.getType() == BufferedImage.TYPE_CUSTOM) {
//coerce it to TYPE_INT_ARGB and cross fingers -- PNGs give a TYPE_CUSTOM and that doesn't work with
//trying to create a new BufferedImage
jpgImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
} else {
jpgImage = new BufferedImage(width, height, image.getType());
}
} else {
jgpImage = GraphicsEnvironment.getLocalGraphicsEnvironment().
getDefaultScreenDevice().getDefaultConfiguration().
createCompatibleImage(width, height, image.getTransparency());
}
//copy the original to the new image
Graphics2D g2 = null;
try {
g2 = jpg.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2.drawImage(image, 0, 0, width, height, null);
}
finally {
if (g2 != null) {
g2.dispose();
}
}
File f = new File("indexed_test.jpg");
ImageIO.write(jpgImage, "jpg", f);
This works for png to jpg and gif to jpg. And you will have a white background where the transparent bits were. You can change this by having g2 fill the image with another color before the drawImage call.
3 months late, but I am having a very similar problem (although not even loading a gif, but simply generating a transparent image - say, no background, a colored shape - where when saving to jpeg, all colors are messed up, not only the background)
Found this bit of code in this rather old thread of the java2d-interest list, thought I'd share, because after a quick test, it is much more performant than your solution:
final WritableRaster raster = img.getRaster();
final WritableRaster newRaster = raster.createWritableChild(0, 0, img.getWidth(), img.getHeight(), 0, 0, new int[]{0, 1, 2});
// create a ColorModel that represents the one of the ARGB except the alpha channel
final DirectColorModel cm = (DirectColorModel) img.getColorModel();
final DirectColorModel newCM = new DirectColorModel(cm.getPixelSize(), cm.getRedMask(), cm.getGreenMask(), cm.getBlueMask());
// now create the new buffer that we'll use to write the image
return new BufferedImage(newCM, newRaster, false, null);
Unfortunately, I can't say I understand exactly what it does ;)
If you create a BufferedImage of type BufferedImage.TYPE_INT_ARGB and save to JPEG weird things will result. In my case the colors are scewed into orange. In other cases the produced image might be invalid and other readers will refuse loading it.
But if you create an image of type BufferedImage.TYPE_INT_RGB then saving it to JPEG works fine.
I think this is therefore a bug in Java JPEG image writer - it should write only what it can without transparency (like what .NET GDI+ does). Or in the worst case thrown an exception with a meaningful message e.g. "cannot write an image that has transparency".
JPEG has no support for transparency. So even when you get the circle color correctly you will still have a black or white background, depending on your encoder and/or renderer.
BufferedImage originalImage = ImageIO.read(getContent());
BufferedImage newImage = new BufferedImage(originalImage.getWidth(), originalImage.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
for (int x = 0; x < originalImage.getWidth(); x++) {
for (int y = 0; y < originalImage.getHeight(); y++) {
newImage.setRGB(x, y, originalImage.getRGB(x, y));
}
}
ImageIO.write(newImage, "jpg", f);
7/9/2020 Edit: added imageIO.write

Categories