Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8350543

BMPImageReader fails with indexed-color table followed by a gap

XMLWordPrintable

      A DESCRIPTION OF THE PROBLEM :
      The BITMAPFILEHEADER.bfOffBits [1] field specifies the starting offset of the pixel data in a BMP file. Thus it is possible to have a gap after the BITMAPINFOHEADER + the following color table [2] before the pixel data (see the visualization [3] from Wikipedia, for example). BMPImageReader fails to read such files:

          javax.imageio.IIOException: java.lang.IllegalArgumentException: Raster BytePackedRaster: width = 32 height = 32 #channels 1 xOff = 0 yOff = 0 is incompatible with ColorModel IndexColorModel: #pixelBits = 4 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@4b6995df transparency = 1 transIndex = -1 has alpha = false isAlphaPre = false
           at java.desktop/java.awt.image.BufferedImage.<init>(BufferedImage.java:622)
           at java.desktop/com.sun.imageio.plugins.bmp.BMPImageReader.read(BMPImageReader.java:909)
           at java.desktop/javax.imageio.ImageIO.read(ImageIO.java:1470)
           ...

      When the color table contains fewer colors than necessary to address full N-bits, and the gap fits into the remainder of a full color table, BMPImageReader doesn't fail but incorrectly includes the gap data as part of the color table.

      [1] https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapfileheader#:~:text=bfoffbits,-The%20offset
      [2] https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader#color-tables
      [3] https://en.wikipedia.org/wiki/BMP_file_format#/media/File:BMPfileFormat.svg

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1. Run the attached test case: "java IndexedBMPReadTest.java"
      2. Observe the console output

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      ```
      image-2bit.bmp
      -> pixelSize=1, mapSize=2
      image-2bit-gap1.bmp
      -> pixelSize=1, mapSize=2
      image-4bit.bmp
      -> pixelSize=4, mapSize=4
      image-4bit-gap1a.bmp
      -> pixelSize=4, mapSize=4
      image-4bit-gap1b.bmp
      -> pixelSize=4, mapSize=4
      ```

      ACTUAL -
      ```
      image-2bit.bmp
      -> pixelSize=1, mapSize=2
      image-2bit-gap1.bmp
      javax.imageio.IIOException: java.lang.IllegalArgumentException: Raster BytePackedRaster: width = 32 height = 32 #channels 1 xOff = 0 yOff = 0 is incompatible with ColorModel IndexColorModel: #pixelBits = 1 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@2cfb4a64 transparency = 1 transIndex = -1 has alpha = false isAlphaPre = false
      at java.desktop/java.awt.image.BufferedImage.<init>(BufferedImage.java:622)
      at java.desktop/com.sun.imageio.plugins.bmp.BMPImageReader.read(BMPImageReader.java:909)
      at java.desktop/javax.imageio.ImageIO.read(ImageIO.java:1470)
      ...
      image-4bit.bmp
      -> pixelSize=4, mapSize=4
      image-4bit-gap1a.bmp
      -> pixelSize=4, mapSize=14
      image-4bit-gap1b.bmp
      javax.imageio.IIOException: java.lang.IllegalArgumentException: Raster BytePackedRaster: width = 32 height = 32 #channels 1 xOff = 0 yOff = 0 is incompatible with ColorModel IndexColorModel: #pixelBits = 4 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@2cfb4a64 transparency = 1 transIndex = -1 has alpha = false isAlphaPre = false
      at java.desktop/java.awt.image.BufferedImage.<init>(BufferedImage.java:622)
      at java.desktop/com.sun.imageio.plugins.bmp.BMPImageReader.read(BMPImageReader.java:909)
      at java.desktop/javax.imageio.ImageIO.read(ImageIO.java:1470)
      ...
      ```


      ---------- BEGIN SOURCE ----------
      ---"IndexedBMPReadTest.java"
      import java.io.ByteArrayInputStream;
      import java.io.FileOutputStream;
      import java.io.IOException;
      import java.util.Base64;

      import java.awt.image.BufferedImage;
      import java.awt.image.IndexColorModel;
      import javax.imageio.ImageIO;

      public class IndexedBMPReadTest {

          public static void main(String[] args) throws Exception {
              Object[][] files = {
                  { "image-2bit.bmp", IMAGE_2BIT },
                  { "image-2bit-gap1.bmp", IMAGE_2BIT_GAP1 },
                  { "image-4bit.bmp", IMAGE_4BIT },
                  { "image-4bit-gap1a.bmp", IMAGE_4BIT_GAP1A },
                  { "image-4bit-gap1b.bmp", IMAGE_4BIT_GAP1B }
              };
              for (Object[] params : files) {
                  readImage((String) params[0], (byte[]) params[1]);
              }
          }

          private static void readImage(String name, byte[] data) throws IOException {
              System.out.println(name);
              try (FileOutputStream fout = new FileOutputStream(name)) {
                  fout.write(data);
              }
              try {
                  BufferedImage image = ImageIO.read(new ByteArrayInputStream(data));
                  IndexColorModel cm = (IndexColorModel) image.getColorModel();
                  System.out.printf("\t-> pixelSize=%d, mapSize=%d%n", cm.getPixelSize(), cm.getMapSize());
              } catch (IOException e) {
                  e.printStackTrace(System.err);
              }
          }

          static final byte[] IMAGE_2BIT = Base64.getMimeDecoder().decode(
                  "Qk2+AAAAAAAAAD4AAAAoAAAAIAAAACAAAAABAAEAAAAAAIAAAAATCwAAEwsA"
                  + "AAIAAAACAAAAAAAAAP///wD///////AP//+AAf//AAD//AAAP/gAAB/wAAAP"
                  + "8AAAD+AAAAfAAAADwAAAA8AAAAOAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGA"
                  + "AAABgAAAAcAAAAPAAAADwAAAA+AAAAfwAAAP8AAAD/gAAB/8AAA//wAA//+A"
                  + "Af//8A///////w==");

          static final byte[] IMAGE_2BIT_GAP1 = Base64.getMimeDecoder().decode(
                  "Qk3CAAAAAAAAAEIAAAAoAAAAIAAAACAAAAABAAEAAAAAAIAAAAATCwAAEwsA"
                  + "AAIAAAACAAAAAAAAAP///wCqqqqq///////wD///gAH//wAA//wAAD/4AAAf"
                  + "8AAAD/AAAA/gAAAHwAAAA8AAAAPAAAADgAAAAYAAAAGAAAABgAAAAYAAAAGA"
                  + "AAABgAAAAYAAAAHAAAADwAAAA8AAAAPgAAAH8AAAD/AAAA/4AAAf/AAAP/8A"
                  + "AP//gAH///AP//////8=");

          static final byte[] IMAGE_4BIT = Base64.getMimeDecoder().decode(
                  "Qk1GAgAAAAAAAEYAAAAoAAAAIAAAACAAAAABAAQAAAAAAAACAAATCwAAEwsA"
                  + "AAQAAAAEAAAAAAAAAADAAADgoEAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAA"
                  + "AAAiIhERAAAAAAAAAAAAAAIiIiIREREQAAAAAAAAAAAiIiIiEREREQAAAAAA"
                  + "AAAiIiIiIhERERERAAAAAAACIiIiIiIRERERERAAAAAAIiIiIiIiERERERER"
                  + "AAAAACIiIiIiIhEREREREQAAAAIiIiIiIiIREREREREQAAAiIiIiIiIiERER"
                  + "EREREQAAIiIiIiIiIhEREREREREAACIiIiIiIiIRERERERERAAIiIiIiIiIi"
                  + "ERERERERERACIiIiIiIiIhEREREREREQAiIiIiIiIiIREREREREREAIiIiIi"
                  + "IiIiERERERERERACIiIiIiIiIhEREREREREQAiIiIiIiIiIREREREREREAIi"
                  + "IiIiIiIiERERERERERACIiIiIiIiIhEREREREREQACIiIiIiIiIRERERERER"
                  + "AAAiIiIiIiIiEREREREREQAAIiIiIiIiIhEREREREREAAAIiIiIiIiIRERER"
                  + "EREQAAAAIiIiIiIiERERERERAAAAACIiIiIiIhEREREREQAAAAACIiIiIiIR"
                  + "ERERERAAAAAAACIiIiIiEREREREAAAAAAAAAIiIiIhEREREAAAAAAAAAAAIi"
                  + "IiIREREQAAAAAAAAAAAAACIiEREAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");

          static final byte[] IMAGE_4BIT_GAP1A = Base64.getMimeDecoder().decode(
                  "Qk1uAgAAAAAAAG4AAAAoAAAAIAAAACAAAAABAAQAAAAAAAACAAATCwAAEwsA"
                  + "AAQAAAAEAAAAAAAAAADAAADgoEAA////AKqqqqqqqqqqqqqqqqqqqqqqqqqq"
                  + "qqqqqqqqqqqqqqqqqqqqqqqqqqoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIiIR"
                  + "EQAAAAAAAAAAAAACIiIiEREREAAAAAAAAAAAIiIiIhEREREAAAAAAAAAIiIi"
                  + "IiIREREREQAAAAAAAiIiIiIiEREREREQAAAAACIiIiIiIhEREREREQAAAAAi"
                  + "IiIiIiIREREREREAAAACIiIiIiIiEREREREREAAAIiIiIiIiIhEREREREREA"
                  + "ACIiIiIiIiIRERERERERAAAiIiIiIiIiEREREREREQACIiIiIiIiIhERERER"
                  + "EREQAiIiIiIiIiIREREREREREAIiIiIiIiIiERERERERERACIiIiIiIiIhER"
                  + "EREREREQAiIiIiIiIiIREREREREREAIiIiIiIiIiERERERERERACIiIiIiIi"
                  + "IhEREREREREQAiIiIiIiIiIREREREREREAAiIiIiIiIiEREREREREQAAIiIi"
                  + "IiIiIhEREREREREAACIiIiIiIiIRERERERERAAACIiIiIiIiEREREREREAAA"
                  + "ACIiIiIiIhEREREREQAAAAAiIiIiIiIREREREREAAAAAAiIiIiIiEREREREQ"
                  + "AAAAAAAiIiIiIhERERERAAAAAAAAACIiIiIRERERAAAAAAAAAAACIiIiERER"
                  + "EAAAAAAAAAAAAAAiIhERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==");

          static final byte[] IMAGE_4BIT_GAP1B = Base64.getMimeDecoder().decode(
                  "Qk16AgAAAAAAAHoAAAAoAAAAIAAAACAAAAABAAQAAAAAAAACAAATCwAAEwsA"
                  + "AAQAAAAEAAAAAAAAAADAAADgoEAA////AKqqqqqqqqqqqqqqqqqqqqqqqqqq"
                  + "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqoAAAAAAAAAAAAAAAAA"
                  + "AAAAAAAAAAAAIiIREQAAAAAAAAAAAAACIiIiEREREAAAAAAAAAAAIiIiIhER"
                  + "EREAAAAAAAAAIiIiIiIREREREQAAAAAAAiIiIiIiEREREREQAAAAACIiIiIi"
                  + "IhEREREREQAAAAAiIiIiIiIREREREREAAAACIiIiIiIiEREREREREAAAIiIi"
                  + "IiIiIhEREREREREAACIiIiIiIiIRERERERERAAAiIiIiIiIiEREREREREQAC"
                  + "IiIiIiIiIhEREREREREQAiIiIiIiIiIREREREREREAIiIiIiIiIiERERERER"
                  + "ERACIiIiIiIiIhEREREREREQAiIiIiIiIiIREREREREREAIiIiIiIiIiERER"
                  + "ERERERACIiIiIiIiIhEREREREREQAiIiIiIiIiIREREREREREAAiIiIiIiIi"
                  + "EREREREREQAAIiIiIiIiIhEREREREREAACIiIiIiIiIRERERERERAAACIiIi"
                  + "IiIiEREREREREAAAACIiIiIiIhEREREREQAAAAAiIiIiIiIREREREREAAAAA"
                  + "AiIiIiIiEREREREQAAAAAAAiIiIiIhERERERAAAAAAAAACIiIiIRERERAAAA"
                  + "AAAAAAACIiIiEREREAAAAAAAAAAAAAAiIhERAAAAAAAAAAAAAAAAAAAAAAAA"
                  + "AAAAAA==");

      }
      ---"IndexedBMPReadTest.java"--

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

            jdv Jayathirth D V
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated: