How to read image from stream?

We can assume that BufferedImage is the best option for image processing in Java. Although this is convenient, when reading huge images it often ends:

 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 

Increasing the size of the virtual machine is not a solution, as some input files are really huge in my case.

So, I'm looking for a way (s) how an image can be read gradually, from a stream.

I suspect that ImageIO.createImageInputStream() from ImageIO might match the score, but I'm not sure how to use it to read fragments gradually. In addition, there are the PNGMetadata and PNGImageReader classes available on the JDK rt.jar that seem useful, but I have not found simple examples of their use.

Is this the way, or are there better alternatives?

+7
java stream image png
source share
2 answers

The Java APIs for reading and processing images are not really thread-based the way you think. ImageInputStream is just a convenient wrapper that allows you to read byte and other primitive types from different inputs ( RandomAccessFile s, InputStream , etc.).

I was thinking of creating an API for reading "pixel streams" to provide a whole chain of processing filters without using a lot of memory. But this need was never serious enough to be worth the effort. Feel free to hire me if you like to hear more ideas or have a business implementation .; -)

However, as I see it, you have several options for achieving the ultimate goal in order to process large images:

  • Use BufferedImage as is, and the ImageIO API to read images in smaller parts to save memory. This will be quite effective for some formats, less effective for other formats due to the implementation (i.e., by default, JPEGImageReader will read the entire image in the source memory before transferring a smaller area to the Java heap, but PNGImageReader may do well).

    Something along the lines of:

     ImageInputStream stream = ImageIO.createImageInputStream(input); ImageReader reader = ImageIO.getImageReaders(stream).next(); // TODO: Test hasNext() reader.setInput(stream); int width = reader.getWidth(0); int height = reader.getHeight(0); ImageReadParam param = reader.getDefaultReadParam(); for (int y = 0; y < height; y += 100) { for (int x = 0; x < width; x += 100) { param.setSourceRegion(new Rectangle(x, y, 100, 100)); // TODO: Bounds check // Read a 100 x 100 tile from the image BufferedImage region = reader.read(0, param); // ...process region as needed... } } 
  • Read the entire image at once, into the buffer with memory mapping. Feel free to try the experimental classes . I made for this purpose (using nio ). Reading will be slower than reading an image of pure memory, and processing will also be slower. But if you perform calculations on small areas of the image at a time, it can be about as fast as in memory with some optimizations. I read> 1 GB of image in a 32 MB JVM using these classes (the consumption of real memory, of course, is a lot more).

    Again, here is an example:

     ImageInputStream stream = ImageIO.createImageInputStream(input); ImageReader reader = ImageIO.getImageReaders(stream).next(); // TODO: Test hasNext() reader.setInput(stream); int width = reader.getWidth(0); int height = reader.getHeight(0); ImageTypeSpecifier spec = reader.getImageTypes(0).next(); // TODO: Test hasNext(); BufferedImage image = MappedImageFactory.createCompatibleMappedImage(width, height, spec) ImageReadParam param = reader.getDefaultReadParam(); param.setDestination(image); image = reader.read(0, param); // Will return same image as created above // ...process image as needed... 
  • Some formats, such as uncompressed TIFF, BMP, PPM, etc., save the pixels in a file so that they can be directly displayed on the map to manipulate them. Some work is required, but should be possible. TIFF also supports tiles that can help. I will leave this option as an exercise, feel free to use the classes I linked above as a starting point or inspiration .; -)

  • JAI may have something that can help you. I am not a big fan, because of this, many unresolved errors and the lack of love and development of Oracle. But worth checking out. I think they have support for tileable and disk-based RenderedImage . Again, I will leave this opportunity for further study.

+11
source share

The memory problem is not necessarily associated with the decoding process, but with saving the entire image in memory as a BufferedImage . You can view the PNG image gradually, but:

  • This is only slightly related to the organization in "pieces", especially since PNG files are encoded in turn, and therefore they can, in principle, be read in turn.

  • The above assumption breaks down in the case of interlaced PNGs - but you cannot expect huge PNG images to be stored in interlaced format

  • Although some PNG libraries allow for progressive (phased) decoding (for example: libpng ), the Java API standard does not give you this.

I ran into this problem and I ended up coding my own Java library: PNGJ . It is quite mature, it allows you to read PNG images in turn, minimizing memory consumption and writing them the same way (even interlaced PNGs can be read, but in this case the memory problem will not disappear.) If you only need to perform some β€œlocal” image processing ( change each pixel value depending on the current value and neighbors and write it back), this should help.

+4
source share

All Articles