JDK-8041465 : BMPImageReader read error using ImageReadParam with set sourceRectangle
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.imageio
  • Affected Version: 7u45,8,9
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_7
  • CPU: x86_64
  • Submitted: 2014-03-11
  • Updated: 2015-06-04
  • Resolved: 2014-09-12
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 9
9 b33Fixed
Description
FULL PRODUCT VERSION :
jdk1.7.0_45.zip sources. Runtime used when bag occures:
java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Windows 7 professional, SP1

EXTRA RELEVANT SYSTEM CONFIGURATION :
doesnt' matter any hard or soft specifications, pure logical bug due to lack of debugging depth

A DESCRIPTION OF THE PROBLEM :
When trying to read some BufferedImage from arbitrarily rectangle from image through using javax.imageio.ImageReader with setting ImageReadParam  source read rectangle only 1st call is always sucessfull, all other subsequent ones return invalid colored BufferedImage or even throw EOFException. This bug only occures on BMP rasters reading. JPG and PNG works correctly, GIF format was not tested. ImageReader instance (for BMP) is get by call to  javax.imageio.ImageIO.getImageReadersBySuffix( "BMP" ).getNext(). 

The problem cause is due to logical bug when in subsequent calls source stream is not positioned to the 1st byte after BMP header as expected by class code logic. To solve this bug, you can insert
follow code line just before comment line at source file line #780 (in BMPImageReader.java)

        ...
	iis.seek( bitmapOffset ); //++ BUG fix (insert before source line #780)
        // There should only be one tile (line #780).
        switch(imageType) {

        case VERSION_2_1_BIT:
            // no compression
            read1Bit(bdata);
            break;
        ...


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Get ImageReader  for BMP raster format using some image stream
2. Set rectangle for ImageReadParam (param)
3. Use param to read part of image at least twice time

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Each read returns requested rectangle from image. So it works for all valid image types except BMP.
ACTUAL -
1st read returns requested image rectangle as BufferedImage.
All subsequent reads returns some strange image or even throw EOFException. It depends on color model of input image. 32 bit BMP usually throw EOFException on 2nd read and follow ones.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
java.io.EOFException
	at javax.imageio.stream.ImageInputStreamImpl.readFully(ImageInputStreamImpl.java:353)
	at javax.imageio.stream.ImageInputStreamImpl.readFully(ImageInputStreamImpl.java:405)
	at com.sun.imageio.plugins.bmp.BMPImageReader.read32Bit(BMPImageReader.java:1345)
	at com.sun.imageio.plugins.bmp.BMPImageReader.read(BMPImageReader.java:851)
	at ru.ts.tests.TestImageRectangleRead.main(TestImageRectangleRead.java:67)

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
package tests;

import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.stream.FileImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Iterator;

public class TestImageRectangleRead
{
	public static void main( String[] args )
	{
		System.out.println( "+++ TEST rectangle sub-image API +++" );
		if ( args.length == 0 )
			System.out.println( "Expected file paths not found" );

		javax.imageio.ImageReader reader;

		for ( String path : args )
		{
			System.out.println( "Processing file \"" + path + "\"" );
			try
			{
				File file = new File( path );
				FileImageInputStream iis = new FileImageInputStream( file );

				int pntIndex = path.lastIndexOf( '.' );
				if ( pntIndex < 0  || pntIndex >= path.length() )
				{
					System.err.println( "Input path doesn't have known extension, skipping" );
					continue;
				}
				String ext = path.substring( pntIndex + 1 );
				System.out.println( "Extension is \"" +  ext  + "\"" );

				// Find corresponding reader
				Iterator iter = ImageIO.getImageReadersBySuffix( ext );
				reader = null;
				if ( iter.hasNext() )
					reader = (javax.imageio.ImageReader) iter.next();
				else
				{
					System.err.println( "Reader for \""+ext +"\" not found by call to ImageIO.getImageReadersBySuffix( \"" + ext + "\" ), skipping" );
					continue;
				}
				reader.setInput( iis );
				ImageReadParam param = reader.getDefaultReadParam();
				int w = reader.getWidth( 0 );
				int h = reader.getHeight( 0 );
				System.out.println( "Image successfully opened, w=" + w + ", h=" + h );
				// read UL corner
				System.out.println( "Read UL corner and write it to disk" );
				Rectangle ulRect = new Rectangle( 0, 0, w/2, h/2);
				param.setSourceRegion( ulRect );
				BufferedImage bi = reader.read( 0, param );
				final String ulpath = "C:/temp/ULCorner." + ext;
				if ( ImageIO.write( bi, ext, new File( ulpath ) ) )
					System.out.println( "New UL file successfully stored: \"" + ulpath + "\"" );
				else
					System.err.println( "No no appropriate writer was found, file not created" );
				// read LR corner
				System.out.println( "Read LR corner and write it to disk" );
				Rectangle lrRect = new Rectangle( w/2, h/2, w/2, h/2 );
				param.setSourceRegion( lrRect );
				bi = reader.read( 0, param );
				final String lrpath = "C:/temp/LRCorner." + ext;
				if ( ImageIO.write( bi, ext, new File( lrpath ) ) )
					System.out.println( "New LR file successfully stored: \"" + lrpath + "\"" );
				else
					System.err.println( "No no appropriate writer was found, file not created" );
			}
			catch ( Exception e )
			{
				e.printStackTrace();
			}
		}
	}
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Creates MyBMPImageReader.java as copy of BMPImageReader.java and fix bug by insertion new code line before original source line 780 (sources of SDK version 1.45)

  iis.seek( bitmapOffset ); //++ BUG fix, insert before line #780

Use in any way this new class except of reader returned by call to 
ImageIO.getImageReadersBySuffix( "BMP" )


Comments
The problem happens if we perform multiple read operations from a source which is set only once. For instance, we can try to read image in several tiles. The root case of the problem is that we set stream position to the start of raster data only once: when we complete the processing of BMP header. A peculiarity of BMP format is that raster data can be stored in reverse order, so if we request few leading scanlines first, then we skip rest of raster data, and subsequent read command does not copy anything to the destination buffer, because we never re-set the stream position to the start of bitmap. Suggested fix just saves the bitmap's start postilion and seeks the stream to to it each time when we read image. This change makes the BMP reader behavior exactly same as other standard plugin demonstrate. Fix review: http://mail.openjdk.java.net/pipermail/2d-dev/2014-September/004787.html
03-09-2014

Failed to reproduce the problem. Requested additional info from the bug submitter.
02-09-2014

- this is an issue reported against 7(7u), - there are now affected version 9 filed for this issue - 7u issues are transferred to Sustaining Nevertheless if someone have a report against 9 - please reopen and add affectedVersion 9 or 7u specific escalations might be reopen to Sustaining
10-08-2014

- this is an issue reported against 7(7u), - there are now affected version 9 filed for this issue - 7u issues are transferred to Sustaining Nevertheless if someone have a report against 9 - please reopen and add affectedVersion 9 or 7u specific escalations might be reopen to Sustaining
10-08-2014