Any reason this code would changing the resolution of the original JPEG? I can understand if the file size were different because the JPEG quality settings are probably different but I don't understand why this would be resizing an image.
File newfile=new File(mydestinationfolder.concat(imagename));
Files.move(file.toPath(),newfile.toPath(), REPLACE_EXISTING);
Rotation Orientation;
if ((Orientation=Exif_data.get_Exif_Orientation(newfile)) != null) {
System.out.println(Orientation.toString());
BufferedImage oldimage = ImageIO.read(newfile);
BufferedImage tmp = Scalr.rotate(oldimage, Orientation);
oldimage.flush();
oldimage=tmp;
ImageIO.write(oldimage, "JPEG", newfile);
}
Well I am not sure why but the default settings for ImageIO.write() are changing the resolution. If I define a custom writer with JPEG quality set to 100%, the image resolution stays the same.
NOTE: output.close() at the end is important because as long as the stream is open the file is locked.
BufferedImage oldimage = ImageIO.read(newfile);
BufferedImage tmp = Scalr.rotate(oldimage, Orientation);
oldimage.flush();
oldimage=tmp;
Iterator iter = ImageIO.getImageWritersByFormatName("jpeg");
ImageWriter writer = (ImageWriter)iter.next();
ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
float quality=1.0f;
iwp.setCompressionQuality(quality);
FileImageOutputStream output = new FileImageOutputStream(newfile);
writer.setOutput(output);
IIOImage image = new IIOImage(oldimage, null, null);
writer.write(null, image, iwp);
writer.dispose();
output.close();
Late answer, but anyway..
As a JPEG read/manipulate/write cycle using ImageIO is always going to be lossy, and all you are doing is rotating, you should have a look at LLJTran from mediautil, as mentioned in this thread.
With that package, you should be able to benefit from a special feature of JPEG block compression to do a lossless JPEG transformation.
Still don't understand why your original code would change the image resolution, nor why your proposed solution would fix it though... Sounds like a bug to me, and should be reported to Oracle. What I do know though, is that setting the JPEG quality to 1.0 (100%) isn't what JPEG was meant for, and is going to cause huge files with no gain in quality (it may possibly be worse than storing at the quality of the original) given the input is already a compressed JPEG .
Related
I'm working with ImageIO and JAI and want to read a byte array into a BufferedImage. The byte[] contains data for a JP2000 encoded image, and it's fairly large, around 100MB. I'm currently doing something like:
byte[] imageDataBytes = ...
InputStream imageStream = new ByteArrayInputStream(imageDataBytes);
BufferedImage imageData = ImageIO.read(imageStream);
It seems that ImageIO is creating a new BufferedImage each time read() is called.
Question:
Is there a way to tell ImageIO to read and decode the image byte data into a pre-allocated mutable BufferedImage?
I did some searching through the Javadocs and found that the BufferedImage stores its data in a Raster object, which stores its data in a DataBuffer object. So I'm aware any solution that exists will technically not be writing to the BufferedImage, but instead will be directly writing to the DataBuffer.
It may help to know that all images are the same size: roughly 10,000 x 10,000, so there shouldn't be any problems with the read image not aligning with the buffered image. Ultimately, I would like to have an object pool of buffered images, or rasters, or data buffers, and borrow from the pool every time I read using ImageIO. Something like this pseudocode:
InputStream imageStream = new ByteArrayInputStream(imageDataBytes);
WritableRaster raster = ObjectPool.getAvailableRaster();
ImageIO.readToRaster(imageStream, raster);
BufferedImage imageData = new BufferedImage(raster);
I'm sure there's a simple solution out there. Any help would be appreciated!
Yes, you can set the destination image of an ImageReadParam object. However, there is a caveat: the BufferedImage must have a ColorModel and SampleModel that match the image being loaded.
I’m not sure about JPEG2000 images, but regular JPEGs are usually RGB images, so an image of TYPE_INT_RGB should suffice:
BufferedImage image = new BufferedImage(10000, 10000,
BufferedImage.TYPE_INT_RGB);
while (bytesAvailable) {
byte[] imageDataBytes = getImageBytes();
try (InputStream in = new ByteArrayInputStream(imageDataBytes);
ImageInputStream stream = ImageIO.createImageInputStream(in)) {
ImageReader reader = ImageIO.getImageReaders(stream).next();
reader.setInput(stream);
ImageReadParam param = reader.getDefaultReadParam();
param.setDestination(image);
reader.read(0, param);
}
}
For those who find themselves in this situation, the answer by VGR works well. I like to add that specifically for JPEG-2000 images that contain metadata, use
reader.setInput(stream, true, true);
instead of
reader.setInput(stream);
This avoids a NullPointer exception. you can read more about it here:
https://issues.apache.org/jira/browse/PDFBOX-2103
I have JPG image files, which I want to load into a BufferedImage and later write the BufferedImage back into a JPG file. Here is what I am currently doing.
Is there a better way not to lose quality and make read/write faster?
Read:
BufferedImage image = ImageIO.read(new File(storagePath + fileName + extension));
Write:
BufferedImage image = // some jpg image
Iterator iter = ImageIO.getImageWritersByFormatName("JPG");
if (iter.hasNext()) {
ImageWriter writer = (ImageWriter) iter.next();
ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwp.setCompressionQuality(quality);
File outFile = new File(storagePath + fileName + extension);
FileImageOutputStream output = new FileImageOutputStream(outFile);
writer.setOutput(output);
IIOImage iioImage = new IIOImage(image, null, null);
writer.write(null, iioImage, iwp);
}
You can minimize the loss in quality in recompressing JPEG by using the same quantization tables used to correct the original image. It is still possible to get single bit errors from rounding but you can get it pretty close.
The problem is how to get the quantization tables. If your encoded will allow you to specify them, you can pull the values out of the source image. Otherwise, you have to hope that the images were originally encoded using the same encoder.
"Quality Values" are not part of JPEG. They are a method for selecting quantization tables used by some encoder. The LIBJPEG is the most common encoder but there are others out there that do things differently.
PNG encoding is generally slower than JPEG.
I am receiving large size CCITT Group 4 compressed TIFF files that need to be written elsewhere as uncompressed TIFF files. I am using the jai_imageio TIFF reader and writer to do that and it works well as long as the product _width * height_ of the image fits in an integer.
Here is the code I am using:
TIFFImageReaderSpi readerSpi= new TIFFImageReaderSpi();
ImageReader imageReader = readerSpi.createReaderInstance();
byte[] data = blobManager.getObjectForIdAndVersion(id, version);
ImageInputStream imageInputStream = ImageIO.createImageInputStream(data);
imageReader.setInput(imageInputStream);
TIFFImageWriterSpi writerSpi = new TIFFImageWriterSpi();
ImageWriter imageWriter = writerSpi.createWriterInstance();
ImageWriteParam imageWriteParam = imageWriter.getDefaultWriteParam();
imageWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED);
//bufferFile is created in the constructor
ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(bufferFile);
imageWriter.setOutput(imageOutputStream);
//Now read the bitmap
BufferedImage bufferedImage = imageReader.read(0);
IIOImage iIOImage = new IIOImage(bufferedImage, null, null);
//and write it
imageWriter.write(null, iIOImage, imageWriteParam);
Unfortunately, the files that I receive are often very large and the BufferedImage cannot be created.
I have been trying to find a way to stream from the ImageReader directly to the ImageWriter but I cannot find out how to do that.
Anybody with a suggestion?
I've had the some issues, and the end result might surprise you :
I ended up calling IrfanView with some command-line options using the Runtime.exec() method. That way, I am not worried about compression or size, it just works and outputs the correct files in the correct folder for me.
If you are on Linux, you can use ImageMagik or something similar.
You can use TIFF tiles to segment a TIFF into smaller portions ("tiles"). If you control the code creating the big images, JAI allows you to retrieve image content tile-by-tile.
Here is an example on how to create tiled image with JAI:
ColorModel cm = source.createColorModel();
// SampleModel with the tilesize
SampleModel sm = cm.createCompatibleSampleModel(tileWidth, tileHeight);
TiledImage image = new TiledImage(0, 0, imageWidth, imageHeight, 0, 0, sm, cm);
TIFFEncodeParam tep = new TIFFEncodeParam();
tep.setTileSize(tileWidth, tileHeight); // Set tile size to avoid OOM
tep.setWriteTiled(true);
JAI.create("filestore", image, filepath, "TIFF", tep);
If you can't control the TIFF production, my knowledge of JAI is too limited to be of much help.
Give your Java VM more memory.
If that doesn't work, look at the source code of the TIFF plugin in the JAI source code. You might be able to write your own processor which just decompresses the data structures using a streaming approach (so you'll never have to keep the whole image in memory at any time).
If that also doesn't work, look at JNA which allows you to call code from a DLL from Java (no C code required; everything is done from pure Java, unlike with Sun's JNI API).
I am trying to create a high quality thumbnail of this image, with Java and Scalr 3.2
This is the relevant source code, where THUMB_WIDTH = 77 and THUMB_HEIGHT = 57
BufferedImage srcImg = ImageIO.read(new File(sourceFile));
BufferedImage dstImg = Scalr.resize(srcImg, Scalr.Method.QUALITY,
THUMB_WIDTH, THUMB_HEIGHT);
ImageIO.write(dstImg, format, new File(destFile));
If I use format = "png", here is the result:
If I use format = "jpg", here is the result:
With imagemagick identify I've found out that the JPEG is saved with a quality of 75 that is totally insufficient to create a good looking thumbnail. The PNG doesn't look good either to me.
Here is the output of identify of the original file and the two thumbnails:
$ identify 42486_1.jpg 42486_s1.jpg 42486_s1.png
42486_1.jpg JPEG 580x435 580x435+0+0 8-bit DirectClass 50.6KB 0.000u 0:00.000
42486_s1.jpg[1] JPEG 77x58 77x58+0+0 8-bit DirectClass 2.22KB 0.000u 0:00.000
42486_s1.png[2] PNG 77x58 77x58+0+0 8-bit DirectClass 12.2KB 0.000u 0:00.000
Questions
How to improve the quality of the generated thumbnail?
How to save a JPEG with a higher quality? I'd like to try with higher quality and compare the results. I couldn't find anything in the JavaDoc for ImageIO.write.
Why I tell Scalr that my maximum dimensions are 77x57 and it output an image 77x58? I think that is to maintain the proportion, but those are my maximum width and maximum height. Width or height could be less but not more.
UPDATE: With a web search I found an article about how to adjust JPEG image compression quality. I wrote my own method to save a BufferedImage setting the quality:
/**
* Write a JPEG file setting the compression quality.
*
* #param image
* a BufferedImage to be saved
* #param destFile
* destination file (absolute or relative path)
* #param quality
* a float between 0 and 1, where 1 means uncompressed.
* #throws IOException
* in case of problems writing the file
*/
private void writeJpeg(BufferedImage image, String destFile, float quality)
throws IOException {
ImageWriter writer = null;
FileImageOutputStream output = null;
try {
writer = ImageIO.getImageWritersByFormatName("jpeg").next();
ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(quality);
output = new FileImageOutputStream(new File(destFile));
writer.setOutput(output);
IIOImage iioImage = new IIOImage(image, null, null);
writer.write(null, iioImage, param);
} catch (IOException ex) {
throw ex;
} finally {
if (writer != null) writer.dispose();
if (output != null) output.close();
}
}
Here are the results. PNG:
JPEG quality 75:
JPEG quality 90 (the gravatars on stackoverflow are saved as JPEG quality 90):
and the filesize:
thumb90.jpg JPEG 77x58 77x58+0+0 8-bit DirectClass 6.89KB 0.000u 0:00.000
UPDATE 2: test to compare Scalr with java-image-scaling.
private void scaleAndSaveImageWithScalr(String sourceFile, String destFile, int width, int height)
throws IOException {
BufferedImage sourceImage = ImageIO.read(new File(sourceFile));
BufferedImage destImage = Scalr.resize(sourceImage, Scalr.Method.QUALITY, width, height);
writeJpeg(destImage, destFile, JPEG_QUALITY);
}
private void scaleAndSaveImageWithJImage(String sourceFile, String destFile, int width, int height)
throws IOException {
BufferedImage sourceImage = ImageIO.read(new File(sourceFile));
ResampleOp resampleOp = new ResampleOp(width, height);
resampleOp.setFilter(ResampleFilters.getLanczos3Filter());
resampleOp.setUnsharpenMask(AdvancedResizeOp.UnsharpenMask.Normal);
BufferedImage destImage = resampleOp.filter(sourceImage, null);
writeJpeg(destImage, destFile, JPEG_QUALITY);
}
JPEG quality 90 generated with Scalr:
JPEG quality 90 generated with java-image-scaling:
I didn't receive any further feedback, so my personal conclusion is that java-image-scaling provides superior quality, and so it's the library that I choose.
#Stivlo, I am sorry for not replying to this, I never got any notification from SO about the question.
java-image-scaling does have some nice filters to help with fine-tuning if you need it. That said, in v4.2 of imgscalr I added the new ULTRA_QUALITY that might get you closer to what you want.
I hope that helps, but realize this is being replied to almost a year after the fact unfortunately. Sorry about that.
This is not a complete answer to your question, but:
Regarding JPEG quality:
Compression quality can be set using a ImageWriteParam as described here. They suggest using an int value of 0|1 but I believe that you should actually specify a float value between 0.0 and 1.0.
Regarding your scaling dimension issues:
From the Scalr homepage:
NOTE: If a width and height are provided that violate the image’s
proportions (e.g. attempt to resize an 800×600 image to a 150×150
square) the library will first look at the orientation of the image
(landscape/square or portrait) and then
select the primary dimension
(landscape or square uses width, portrait uses height) to recalculate
a correct secondary dimension; ignoring what was passed in by the user
that was violating the proportions.
In your case the primary dimension will be a width of 77 and thus your height limit of 57 will be ignored.
I have run the same tests and java-image-scaling definitively have better results for thumbnails smaller than 250px. It also support sharp filtering, which make the results better.
I keep both libraries since the syntax of Scalr is often easier, with only one line.
Note that if your images have an alpha channel, both libraries are problematic. I'm only talking about shrinking images, I haven't tested enlarging them.
java-image-scaling may create an ugly border around the transparent edges depending on the image, and this looks very bad. I found no way to avoid this.
Scalr is only problematic using the (ultra) quality modes. It can easily be used in a way that works fine, though: bicubic interpolation leaves artifacts in transparent images, so you may want to avoid it. Since it's the default for (ultra) quality images, and scaleImageIncrementally() is protected you'd have to subclass it for this, though, if you want the quality (a fraction higher than 2 looks very blurry with bilinear filtering, though).
If you want high quality result, so use [RapidDecoder][1] library. It is simple as follow:
import rapid.decoder.BitmapDecoder;
...
Bitmap bitmap = BitmapDecoder.from(getResources(), R.drawable.image)
.scale(width, height)
.useBuiltInDecoder(true)
.decode();
Don't forget to use builtin decoder if you want to scale down less than 50% and a HQ result.
As can be seen below I have 1st image an original JPEG image .Second one was taken to buffer image and than save using http://www.lac.inpe.br/JIPCookbook/6040-howto-compressimages.jsp with 1.0 quality . Still image became smaller in size and a really small destortion. Is it possible to save image to its quality as it is ? Pleas not that saving image as it is was just a sample test. After adding text I save it with highest quality which looses information too.
Do not redraw the image and save it. Just copy the raw bytes instead!
I suspect your current code is something like this:
BufferedImage image = ImageIO.read(new File("my.jpg");
ImageIO.write(image, "jpg", new File("copy.jpg"));
Every time you repeat this the image will change a little (as you saw you always loose some quality). If you only want to copy the JPEG/file without changing anything you can do something like this (from this page http://www.exampledepot.com/egs/java.io/CopyFile.html):
void copy(File src, File dst) throws IOException {
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst);
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
}
JPEG, even with highest quality settings, is always lossy, even if the original image data came from a JPEG.
There are some operations like rotation/mirroring/crop that can be done lossless on a JPEG (using tools like jpegtran), but these are rare exceptions.
Anyway, it seems you have access to the original JPG image and you don't change it, so I don't understand why you compress it again.
If you really have to store such images lossless, best choice would be using the lossless mode of JPEG2000, this gives a smaller filesize than other alternatives like PNG for image data that has been compressed using JPG (although it is still much larger than the original JPG). For example, for the first of your example pictures:
hAw2d.jpg -> 268,678 bytes (Original)
hAw2d.jp2 -> 1,021,007 bytes (JPEG 2000, lossless)
hAw2d.png -> 1,213,392 bytes (PNG)