Search code examples
javagraphics2d

How to handle huge data/images in RAM in Java?


Summary

  1. I am reading a large binary file which contains image data.
  2. Cumulative Count Cut analysis is performed on data [It requires another array with same size as the image].
  3. The data is stretched between 0 to 255 stored in BufferedImage pixel by pixel, to draw the image on JPanel.
  4. On this image, zooming is performed using AffineTransform.

Problems

  1. Small Image(<.5GB)

    1.1 When I am increasing the scale factor for performing zooming, after a
    point exception is thrown:-

java.lang.OutOfMemoryError: Java heap space.

Below is the code used for zooming-

    scaled = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
    Graphics2D g2d = (Graphics2D)scaled.createGraphics();
    AffineTransform transformer = new AffineTransform();
    transformer.scale(scaleFactor, scaleFactor); 
    g2d.setTransform(transformer);
  1. Large Image(>1.5GB)
    • While loading a huge image(>1.5GB), same exception occurs as appeared in 1.1, even is the image is small enough to be loaded, sometimes, I get the same error.

Solutions Tried

  1. I tried using BigBufferedImage in place of BufferedImage to store the stretched data. BigBufferedImage image = BigBufferedImage.create(newCol,newRow, BufferedImage.TYPE_INT_ARGB);
  2. But it couldn't be passed to g2d.drawImage(image, 0, 0, this); because the repaint method of JPanel just stops for some reason.

  3. I tried loading image in low resolution where pixel is read and few columns and rows are jumped/skipped. But the problem is how to decide what number of pixels to skip as image size varies therefore I am unable to decide how to decide the jump parameter.

    MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY,0, inChannel.size());
    buffer.order(ByteOrder.LITTLE_ENDIAN);
    FloatBuffer floatBuffer = buffer.asFloatBuffer();
    for(int i=0,k=0;i<nrow;i=i+jump)  /*jump is the value to be skipped, nrow is height of image*/
    {
        for(int j=0,l=0;j<ncol1;j=j+jump)   //ncol is width of image
        {
                index=(i*ncol)+j;
                oneDimArray[(k*ncolLessRes)+l] = floatBuffer.get(index);//oneDimArray is initialised to size of Low Resolution image.
                l++;
        }
        k++;
    }

The problem is to decide how many column and row to skip i.e what value of jump should be set.

  1. I tried setting Xmx but image size varies and we cannot dynamically set the Xmx values. Here are some values -

table, th, td {
  border: 1px solid black;
}
<table style="width:100%">
  <tr>
    <th>Image Size</th>
    <th>Xmx</th>
    <th>Xms</th>
    <th>Problem</th>
  </tr>
  <tr>
    <td>83Mb</td>
    <td>512m</td>
    <td>256m</td>
    <td>working</td>
  </tr>
  <tr>
    <td>83Mb</td>
    <td>3096m</td>
    <td>2048m</td>
    <td>System hanged</td>
  </tr>
   <tr>
    <td>3.84Gb</td>
    <td>512m</td>
    <td>256m</td>
    <td>java.lang.OutOfMemoryError: Java heap space
  </tr>
  <tr>
    <td>3.84Gb</td>
    <td>3096m</td>
    <td>512m</td>
    <td>java.lang.OutOfMemoryError: Java heap space
  </tr>
</table>

  1. For this I tried finding memory allocated to program:-
 try(BufferedWriter bw= new BufferedWriter(new FileWriter(dtaFile,true))){
    Runtime runtime=Runtime.getRuntime();
    runtime.gc();
    double oneMB=Math.pow(2,20);
    long[] arr= Instream.range(0,(int)(10.432*long.BYTES*Math.pow(2,20))).asLongStream().toArray();
    runtime.gc();
    long freeMemory= runtime.freeMemory();
    long totalMemory= runtime.totalMemory();
    long usedMemory= totalMemory-freeMemory;
    long maxMemory= runtime.maxMemory();
    String fileLine= String.format(" %9.3f  %9.3f   %9.3f " , usedMemory/oneMb, freeMemory/oneMB, totalMemory/oneMb, maxMemory/oneMB);
    bw.write();
}

Following results were obtained
Memory Allocation
This approach failed because the available memory increases as per usage of my code. As a result it will not be useful for me to make a decision for jump.

Result Expected

A way to access the amount of available memory before the loading of the image so that I could use it to make decision on value of the jump. Is there any other alternative to decide jump value (i.e., how much I can lower the resolution?).


Solution

  • You can read the specific portion of an image, then scale it with reduced resolution for display purpose.

    So in your case you can read the image in chunk (read image portions just like we read the data from db row by row)

    For example:

    // Define the portion / row size 50px or 100px
    int rowHeight = 50;
    int rowsToScan = imageHeight / rowHeight;
    if(imageHeight % rowHeight > 0) rowsToScan++;
    
    int x = 0;
    int y = 0;
    int w = imageWidth;
    int h = rowHeight;
    
    ArrayList<BufferedImage> scaledImagePortions = new ArrayList<>();
    
    for(int i = 1; i <= rowsToScan; i++) {
        // Read the portion of an image scale it
        // and push the scaled version in lets say array
        BufferedImage scalledPortionOfImage = this.getScaledPortionOfImage(img, x, y, w, h);
        scaledImagePortions.add(scalledPortionOfImage);
    
        y = (rowHeight * i);
    }
    
    // Create single image out of scaled images portions
    

    Thread which can help you to get portion of an image Read region from very large image file in Java

    Thread which can help you to scale the image (my quick search result :) ) how to resize Image in java?

    Thread which can help you in merging the buffered images: Merging two images

    You can always tweak the snippets :)