-
Bug
-
Resolution: Unresolved
-
P4
-
8, 11, 17, 21, 23, 24, 25
-
generic
-
generic
A DESCRIPTION OF THE PROBLEM :
Saving an indexed-color image in "bmp" format produces invalid data when the used colors are less than the full color table necessary to address N-bit image data. The output always has 'biClrUsed: 0' [1]:
> Specifies the number of color indices in the color table that are actually
> used by the bitmap. See Remarks for more information.
"Remarks – Color Tables" [2]:
> The BITMAPINFOHEADER structure may be followed by an array of palette
> entries or color masks. The rules depend on the value of biCompression.
>
> * If biCompression equals BI_RGB and the bitmap uses 8 bpp or less, the
> bitmap has a color table immediately following the BITMAPINFOHEADER
> structure. The color table consists of an array of RGBQUAD values.
> The size of the array is given by the biClrUsed member. If biClrUsed
> is zero, the array contains the maximum number of colors for the given
> bitdepth; that is, 2^biBitCount colors.
It seems the BMPImageWriter writes the number of used colors to the following 'biClrImportant' field, which however should be irrelevant for computing the size of the stored color table. BMPImageReader seems capable of reading thus invalid images back possibly using the info from the 'biClrImportant' field, but other software fails with the invalid data.
[1] https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader#:~:text=biclrused,-Specifies
[2] https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader#color-tables
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Run the attached test case: "java IndexedBMPWriteTest.java"
2. Observe the saved "image-4bit-jiio.bmp"; compare with the source "image-4bit.bmp"
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The saved "image-4bit-jiio.bmp" should have BITMAPINFOHEADER.biClrUsed=4.
ACTUAL -
The saved "image-4bit-jiio.bmp" has BITMAPINFOHEADER.biClrUsed=0.
---------- BEGIN SOURCE ----------
-----"IndexedBMPWriteTest.java"
//package ;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.util.Base64;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriter;
public class IndexedBMPWriteTest {
public static void main(String[] args) throws Exception {
try (FileOutputStream fout = new FileOutputStream("image-4bit.bmp")) {
fout.write(IMAGE_4BIT);
}
BufferedImage fourBitImage = ImageIO.read(new ByteArrayInputStream(IMAGE_4BIT));
IndexColorModel cm = (IndexColorModel) fourBitImage.getColorModel();
System.out.printf("pixelSize=%d, mapSize=%d%n", cm.getPixelSize(), cm.getMapSize());
ImageWriter bmpWriter = ImageIO.getImageWritersByFormatName("bmp").next();
System.out.println(bmpWriter.getClass().getName());
ImageIO.write(fourBitImage, "bmp", new File("image-4bit-jiio.bmp"));
}
static final byte[] IMAGE_4BIT = Base64.getDecoder().decode(
"Qk1GAgAAAAAAAEYAAAAoAAAAIAAAACAAAAABAAQAAAAAAAACAAATCwAAEwsA"
+ "AAQAAAAEAAAAAAAAAADAAADgoEAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAiIhERAAAAAAAAAAAAAAIiIiIREREQAAAAAAAAAAAiIiIiEREREQAAAAAA"
+ "AAAiIiIiIhERERERAAAAAAACIiIiIiIRERERERAAAAAAIiIiIiIiERERERER"
+ "AAAAACIiIiIiIhEREREREQAAAAIiIiIiIiIREREREREQAAAiIiIiIiIiERER"
+ "EREREQAAIiIiIiIiIhEREREREREAACIiIiIiIiIRERERERERAAIiIiIiIiIi"
+ "ERERERERERACIiIiIiIiIhEREREREREQAiIiIiIiIiIREREREREREAIiIiIi"
+ "IiIiERERERERERACIiIiIiIiIhEREREREREQAiIiIiIiIiIREREREREREAIi"
+ "IiIiIiIiERERERERERACIiIiIiIiIhEREREREREQACIiIiIiIiIRERERERER"
+ "AAAiIiIiIiIiEREREREREQAAIiIiIiIiIhEREREREREAAAIiIiIiIiIRERER"
+ "EREQAAAAIiIiIiIiERERERERAAAAACIiIiIiIhEREREREQAAAAACIiIiIiIR"
+ "ERERERAAAAAAACIiIiIiEREREREAAAAAAAAAIiIiIhEREREAAAAAAAAAAAIi"
+ "IiIREREQAAAAAAAAAAAAACIiEREAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
}
-----"IndexedBMPWriteTest.java"--
---------- END SOURCE ----------
Saving an indexed-color image in "bmp" format produces invalid data when the used colors are less than the full color table necessary to address N-bit image data. The output always has 'biClrUsed: 0' [1]:
> Specifies the number of color indices in the color table that are actually
> used by the bitmap. See Remarks for more information.
"Remarks – Color Tables" [2]:
> The BITMAPINFOHEADER structure may be followed by an array of palette
> entries or color masks. The rules depend on the value of biCompression.
>
> * If biCompression equals BI_RGB and the bitmap uses 8 bpp or less, the
> bitmap has a color table immediately following the BITMAPINFOHEADER
> structure. The color table consists of an array of RGBQUAD values.
> The size of the array is given by the biClrUsed member. If biClrUsed
> is zero, the array contains the maximum number of colors for the given
> bitdepth; that is, 2^biBitCount colors.
It seems the BMPImageWriter writes the number of used colors to the following 'biClrImportant' field, which however should be irrelevant for computing the size of the stored color table. BMPImageReader seems capable of reading thus invalid images back possibly using the info from the 'biClrImportant' field, but other software fails with the invalid data.
[1] https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader#:~:text=biclrused,-Specifies
[2] https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader#color-tables
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Run the attached test case: "java IndexedBMPWriteTest.java"
2. Observe the saved "image-4bit-jiio.bmp"; compare with the source "image-4bit.bmp"
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The saved "image-4bit-jiio.bmp" should have BITMAPINFOHEADER.biClrUsed=4.
ACTUAL -
The saved "image-4bit-jiio.bmp" has BITMAPINFOHEADER.biClrUsed=0.
---------- BEGIN SOURCE ----------
-----"IndexedBMPWriteTest.java"
//package ;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.util.Base64;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriter;
public class IndexedBMPWriteTest {
public static void main(String[] args) throws Exception {
try (FileOutputStream fout = new FileOutputStream("image-4bit.bmp")) {
fout.write(IMAGE_4BIT);
}
BufferedImage fourBitImage = ImageIO.read(new ByteArrayInputStream(IMAGE_4BIT));
IndexColorModel cm = (IndexColorModel) fourBitImage.getColorModel();
System.out.printf("pixelSize=%d, mapSize=%d%n", cm.getPixelSize(), cm.getMapSize());
ImageWriter bmpWriter = ImageIO.getImageWritersByFormatName("bmp").next();
System.out.println(bmpWriter.getClass().getName());
ImageIO.write(fourBitImage, "bmp", new File("image-4bit-jiio.bmp"));
}
static final byte[] IMAGE_4BIT = Base64.getDecoder().decode(
"Qk1GAgAAAAAAAEYAAAAoAAAAIAAAACAAAAABAAQAAAAAAAACAAATCwAAEwsA"
+ "AAQAAAAEAAAAAAAAAADAAADgoEAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAiIhERAAAAAAAAAAAAAAIiIiIREREQAAAAAAAAAAAiIiIiEREREQAAAAAA"
+ "AAAiIiIiIhERERERAAAAAAACIiIiIiIRERERERAAAAAAIiIiIiIiERERERER"
+ "AAAAACIiIiIiIhEREREREQAAAAIiIiIiIiIREREREREQAAAiIiIiIiIiERER"
+ "EREREQAAIiIiIiIiIhEREREREREAACIiIiIiIiIRERERERERAAIiIiIiIiIi"
+ "ERERERERERACIiIiIiIiIhEREREREREQAiIiIiIiIiIREREREREREAIiIiIi"
+ "IiIiERERERERERACIiIiIiIiIhEREREREREQAiIiIiIiIiIREREREREREAIi"
+ "IiIiIiIiERERERERERACIiIiIiIiIhEREREREREQACIiIiIiIiIRERERERER"
+ "AAAiIiIiIiIiEREREREREQAAIiIiIiIiIhEREREREREAAAIiIiIiIiIRERER"
+ "EREQAAAAIiIiIiIiERERERERAAAAACIiIiIiIhEREREREQAAAAACIiIiIiIR"
+ "ERERERAAAAAAACIiIiIiEREREREAAAAAAAAAIiIiIhEREREAAAAAAAAAAAIi"
+ "IiIREREQAAAAAAAAAAAAACIiEREAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
}
-----"IndexedBMPWriteTest.java"--
---------- END SOURCE ----------