I have a lot of operations involving generating a BufferedImage object using a third-party library and saving it to a jpg file. Sometimes one processing can have over 10,000 such savings. Currently I'm using ImageIO.write(image, "jpg", file) directly but the performance is not very satisfactory.
I'm wondering if I can use direct ByteBuffer to make disk writing faster? I'm thinking about putting the BufferedImage into a direct ByteBuffer and finally save to disk using a FileChannel. I didn't find a way to do so. I wonder how can I put a BufferedImage to direct ByteBuffer? Some example code would help a lot.
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
public byte[] toByteArray(BufferedImage image) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(baos);
encoder.encode(image);
return baos.toByteArray();
}
From this answer try this. Should be faster.
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 a tiff image stored as Base64 encoded String in a file. My aim is to create a tiff file out of it. This is what I am doing:
String base64encodedTiff = IOUtils.toString(new FileInputStream("C:/tiff-attachment.txt"));
byte[] imgBytes = DatatypeConverter.parseBase64Binary(base64encodedTiff);
BufferedImage bufImg = ImageIO.read(new ByteArrayInputStream(imgBytes));
ImageIO.write(bufImg, "tiff", new File("c:/new-darksouls-imageIO-tiff.tiff"));
ImageIO.write() is throwing IllegalArgumentException because bufImg is null. I don't understand what am I doing wrong here.
On the contrary if I use IOUtils to write, it works fine:
IOUtils.write(imgBytes, new FileOutputStream("c:/new-darksouls-io-tiff.tiff"));
Please help me understand
Why ImageIO is throwing exception
What is the right API and way for what I am trying to achieve.
ImageIO would be useful if, for example, you wanted to convert a PNG to a JPEG. Since you don't need to manipulate the image or convert to another format, don't bother with ImageIO. Just use IOUtils.write() to save the TIFF data verbatim.
ImageIO.read() is returning a null image because it can't read the TIFF file, probably because TIFF isn't one of the standard ImageIO plugin formats. The standard supported image formats are listed here:
http://docs.oracle.com/javase/6/docs/api/javax/imageio/package-summary.html
An additional note -- the code you posted buffers the entire image in memory. If you're concerned about using memory efficiently, consider using some kind of Base64 decoding input stream to perform the decoding on the fly. That might look like this:
try (FileOutputStream out = new FileOutputStream("c:/new-darksouls-io-tiff.tiff");
FileInputStream in = new FileInputStream("C:/tiff-attachment.txt");
Base64InputStream decodedIn = new Base64InputStream(in)) {
IOUtils.copy(decodedIn, out);
}
I need to convert a BufferedImage to a byte[], but It is too slow. The byte is eventually base64 encoded and sent to an android client. The Method I have been using is like this:
public static byte[] ImageToBytes(BufferedImage im) throws IOException
{
//make sure its NN
if(im!=null)
{
//create a ByteArrayOutputStream
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//write our image to it
ImageIO.write( im, "png", baos );
//flush the stream
baos.flush();
//get the image in byte form
byte[] imageInByte = baos.toByteArray();
//close the stream
baos.close();
//return our value encoded in base64
return imageInByte;
}
return null;
}
This is far too slow for my program. Changing the png to jpeg makes it fail on the mobile side. The JpegCodec version also fails on the mobile side. By fail I mean the Android method BitmapFactory.decodeByteArray() returns null.
You don't say why it's too slow and there isn't much that can be done to make this code faster because it's the only way to convert a BufferedImage to a PNG byte stream. But here are some pointers:
The class ByteArrayOutputStream is synchronized which costs a lot of performance. You can find a faster implementation in Commons IO (org.apache.commons.io.output.ByteArrayOutputStream)
Depending on the size of your image, the allocation algorithm of ByteArrayOutputStream might be a problem. Start with an initial size of 1 or 10 KiB.
toByteArray() gives you a copy of the existing buffer. If you don't need that (and usually, you don't), writing your own OutputStream might give you an additional speed boost (not to mention avoiding GC runs)
Would not converting to a byte[] be an option? You say "[it] is eventually base64 encoded and sent to an android client". What about wrapping the OutputStream to write to the Android client in something like Base64OutputStream and avoiding the issue entirely?
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)
BufferedImage image = ImageIO.read( new ByteArrayInputStream( byteArray ) );
ImageIO.write(image, "BMP", new File("filename.bmp"));
When i tried this thing [ImageIO.read( new ByteArrayInputStream( byteArray ))] returns null value so i cant create new bmp file.
But this works to convert jpg to bmp files.I have raw files and i need to convert to image.
Please help me in this.
The support for readers and writers in the javax.imageio is limited, and if you dont know the format of the byte[] your are trying to convert, then propably it is not included in the list of valid image readers. Then, what you might need is the Java Advanced Imaging API, which you can find here.
These are the readers a
Readers: Writers:
bmp bmp
jpg jpg
jpeg jpeg
wbmp Wbmp
png png
gif gif
This few lines shall help you to get this to work using JAI API
// read byte[]
SeekableStream stream = new ByteArraySeekableStream(image);
// render stream of bytes to valid image format
RenderedImage renderedImage = JAI.create("stream", stream);
// persist image to file
JAI.create("filestore", renderedImage, filename, targetFormat);
// dont't forget to close the stream!
stream.close();
Take a look here: Java Advanced Imaging I/O Tools. The Java Image I/O Reader seems to be able to read "raw"-Files. I think that you can write them with the Java Image I/O Writer as BMP.
regards
Macs