Back to... PDF Writer

Source file : gid-headers.adb



---------------------------------
-- GID - Generic Image Decoder --
---------------------------------
--
--  Private child of GID, with helpers for identifying
--  image formats and reading header informations.
--

with GID.Buffering,
     GID.Color_tables,
     GID.Decoding_JPG,
     GID.Decoding_PNG,
     GID.Decoding_PNM;

with Ada.Unchecked_Deallocation;

package body GID.Headers is

  -------------------------------------------------------
  -- The very first: read signature to identify format --
  -------------------------------------------------------

  procedure Load_signature (
    image   : in out Image_descriptor;
    try_tga :        Boolean := False

  )
  is
    use Bounded_255;
    c : Character;
    FITS_challenge : String (1 .. 5);  --  without the initial
    GIF_challenge  : String (1 .. 5);  --  without the initial
    QOI_challenge  : String (1 .. 3);  --  without the initial
    QOI_signature  : constant String := "oif";
    PNG_challenge  : String (1 .. 7);  --  without the initial
    PNG_signature  : constant String := "PNG" & ASCII.CR & ASCII.LF & ASCII.SUB & ASCII.LF;
    PNM_challenge  : Character;
    TIFF_challenge : String (1 .. 3);  --  without the initial
    TIFF_signature : String (1 .. 2);
    procedure Dispose is
      new Ada.Unchecked_Deallocation (Color_table, p_Color_table);
  begin
    --  Some cleanup
    Dispose (image.palette);
    image.next_frame := 0.0;
    image.display_orientation := Unchanged;
    --
    Character'Read (image.stream, c);
    image.first_byte := Character'Pos (c);
    case c is
      when 'B' =>
        Character'Read (image.stream, c);
        if c = 'M' then
          image.detailed_format := To_Bounded_String ("BMP");
          image.format := BMP;
          return;
        end if;
      when 'S' =>
        String'Read (image.stream, FITS_challenge);
        if FITS_challenge = "IMPLE"  then
          image.detailed_format := To_Bounded_String ("FITS");
          image.format := FITS;
          return;
        end if;
      when 'G' =>
        String'Read (image.stream, GIF_challenge);
        if GIF_challenge = "IF87a" or GIF_challenge = "IF89a" then
          image.detailed_format := To_Bounded_String ('G' & GIF_challenge & ", ");
          image.format := GIF;
          return;
        end if;
      when 'I' | 'M' =>
        String'Read (image.stream, TIFF_challenge);
        if c = TIFF_challenge (1) then
          --  TIFF begins either with II (Intel) or MM (Motorola) - TIFF 6.0 Specification p.13
          if c = 'I' then
            image.detailed_format := To_Bounded_String ("TIFF, little-endian");
            image.endianess := little;
            TIFF_signature := '*' & ASCII.NUL; -- 42 (The Answer) on 16 bits
          else
            image.detailed_format := To_Bounded_String ("TIFF, big-endian");
            image.endianess := big;
            TIFF_signature := ASCII.NUL & '*'; -- 42 (The Answer) on 16 bits
          end if;
          if TIFF_challenge (2 .. 3) = TIFF_signature then
            image.format := TIFF;
            return;
          end if;
        end if;
      when Character'Val (16#FF#) =>
        Character'Read (image.stream, c);
        if c = Character'Val (16#D8#) then
          --  SOI (Start of Image) segment marker (FFD8)
          image.detailed_format := To_Bounded_String ("JPEG");
          image.format := JPEG;
          return;
        end if;
      when Character'Val (16#89#) =>
        String'Read (image.stream, PNG_challenge);
        if PNG_challenge = PNG_signature  then
          image.detailed_format := To_Bounded_String ("PNG");
          image.format := PNG;
          return;
        end if;
      when 'P' =>
        Character'Read (image.stream, PNM_challenge);
        if PNM_challenge in '1' .. '6' then
          image.detailed_format := To_Bounded_String ("PNM (PBM, PGM or PPM)");
          image.format := PNM;
          image.subformat_id := Integer'Value ((1 => PNM_challenge));
          return;
        end if;
      when 'q' =>
        String'Read (image.stream, QOI_challenge);
        if QOI_challenge = QOI_signature then
          image.format := QOI;
          image.RLE_encoded := True;
          return;
        end if;
      when others =>
        if try_tga then
          image.detailed_format := To_Bounded_String ("TGA");
          image.format := TGA;
          return;
        else
          raise unknown_image_format;
        end if;
    end case;
    raise unknown_image_format;
  end Load_signature;

  --  Define reading of unsigned numbers from a byte stream

  --  Little-endian
  generic
    type Number_LE is mod <>;
  procedure Read_Intel_x86_number (
    from_le : in     Stream_Access;
    n       :    out Number_LE
  );
    pragma Inline (Read_Intel_x86_number);

  generic
    type Number_BE is mod <>;
  procedure Big_endian_number (
    from_be : in     Stream_Access;
    n       :    out Number_BE
  );
    pragma Inline (Big_endian_number);

  generic
    type Number is mod <>;
  procedure Big_endian_number_buffered (
    from : in out Input_buffer;
    n    :    out Number
  );
    pragma Inline (Big_endian_number_buffered);

  generic
    type Number is mod <>;
  procedure Read_any_endian_number (
    from : in     Stream_Access;
    n    :    out Number;
    endi : in     Endianess_type
  );
    pragma Inline (Read_any_endian_number);

  --  Implementations

  procedure Read_Intel_x86_number (
    from_le : in     Stream_Access;
    n       :    out Number_LE
  )
  is
    b : U8;
    m : Number_LE := 1;
  begin
    n := 0;
    for i in 1 .. Number_LE'Size / 8 loop
      U8'Read (from_le, b);
      n := n + m * Number_LE (b);
      m := m * 256;
    end loop;
  end Read_Intel_x86_number;

  procedure Big_endian_number (
    from_be : in     Stream_Access;
    n       :    out Number_BE
  )
  is
    b : U8;
  begin
    n := 0;
    for i in 1 .. Number_BE'Size / 8 loop
      U8'Read (from_be, b);
      n := n * 256 + Number_BE (b);
    end loop;
  end Big_endian_number;

  procedure Big_endian_number_buffered (
    from : in out Input_buffer;
    n    :    out Number
  )
  is
    b : U8;
  begin
    n := 0;
    for i in 1 .. Number'Size / 8 loop
      Buffering.Get_Byte (from, b);
      n := n * 256 + Number (b);
    end loop;
  end Big_endian_number_buffered;

  procedure Read_any_endian_number (
    from : in     Stream_Access;
    n    :    out Number;
    endi : in     Endianess_type
  )
  is
    procedure Read_Intel is new Read_Intel_x86_number (Number);
    procedure Big_endian is new Big_endian_number (Number);
  begin
    case endi is
      when little => Read_Intel (from, n);
      when big =>    Big_endian (from, n);
    end case;
  end Read_any_endian_number;

  --  Instantiations

  procedure Read_Intel is new Read_Intel_x86_number (U16);
  procedure Read_Intel is new Read_Intel_x86_number (U32);
  procedure Big_endian_buffered is new Big_endian_number_buffered (U32);
  procedure Read_any_endian is new Read_any_endian_number (U32);

  ----------------------------------------------------------
  -- Loading of various format's headers (past signature) --
  ----------------------------------------------------------

  ----------------
  -- BMP header --
  ----------------

  procedure Load_BMP_header (image : in out Image_descriptor) is
    n, dummy : U32;
    w, dummy16 : U16;
    pragma Unreferenced (dummy, dummy16);
  begin
    --   Pos= 3, read the file size
    Read_Intel (image.stream, dummy);
    --   Pos= 7, read four bytes, unknown
    Read_Intel (image.stream, dummy);
    --   Pos= 11, read four bytes offset, file top to bitmap data.
    --            For 256 colors, this is usually 36 04 00 00
    Read_Intel (image.stream, dummy);
    --   Pos= 15. The beginning of Bitmap information header.
    --   Data expected:  28H, denoting 40 byte header
    Read_Intel (image.stream, dummy);
    --   Pos= 19. Bitmap width, in pixels.  Four bytes
    Read_Intel (image.stream, n);
    image.width :=  Positive_32 (n);
    --   Pos= 23. Bitmap height, in pixels.  Four bytes
    Read_Intel (image.stream, n);
    image.height := Positive_32 (n);
    --   Pos= 27, skip two bytes.  Data is number of Bitmap planes.
    Read_Intel (image.stream, dummy16); -- perform the skip
    --   Pos= 29, Number of bits per pixel
    --   Value 8, denoting 256 color, is expected
    Read_Intel (image.stream, w);
    case w is
      when 1 | 4 | 8 | 24 =>
        null;
      when others =>
        raise unsupported_image_subformat with "BMP bit depth =" & U16'Image (w);
    end case;
    image.bits_per_pixel := Integer (w);
    --   Pos= 31, read four bytes
    Read_Intel (image.stream, n);          -- Type of compression used
    --  BI_RLE8 = 1
    --  BI_RLE4 = 2
    if n /= 0 then
      raise unsupported_image_subformat with "BMP: RLE compression";
    end if;
    --
    Read_Intel (image.stream, dummy); -- Pos= 35, image size
    Read_Intel (image.stream, dummy); -- Pos= 39, horizontal resolution
    Read_Intel (image.stream, dummy); -- Pos= 43, vertical resolution
    Read_Intel (image.stream, n); -- Pos= 47, number of palette colors
    if image.bits_per_pixel <= 8 then
      if n = 0 then
        image.palette := new Color_table (0 .. 2**image.bits_per_pixel - 1);
      else
        image.palette := new Color_table (0 .. Natural (n) - 1);
      end if;
    end if;
    Read_Intel (image.stream, dummy); -- Pos= 51, number of important colors
    --   Pos= 55 (36H), - start of palette
    Color_tables.Load_palette (image);
  end Load_BMP_header;

  procedure Load_FITS_header (image : in out Image_descriptor) is
  begin
    raise known_but_unsupported_image_format;
  end Load_FITS_header;

  ----------------
  -- GIF header --
  ----------------

  procedure Load_GIF_header (image : in out Image_descriptor) is
    --  GIF - logical screen descriptor
    screen_width, screen_height           : U16;
    packed, background, aspect_ratio_code : U8;
    global_palette : Boolean;
  begin
    Read_Intel (image.stream, screen_width);
    Read_Intel (image.stream, screen_height);
    if screen_width = 0 then
      raise error_in_image_data with "GIF image: zero width";
    end if;
    if screen_height = 0 then
      raise error_in_image_data with "GIF image: zero height";
    end if;
    image.width := Positive_32 (screen_width);
    image.height := Positive_32 (screen_height);
    image.transparency := True; -- cannot exclude transparency at this level.
    U8'Read (image.stream, packed);
    --  Global Color Table Flag       1 Bit
    --  Color Resolution              3 Bits
    --  Sort Flag                     1 Bit
    --  Size of Global Color Table    3 Bits
    global_palette := (packed and 16#80#) /= 0;
    image.bits_per_pixel := Natural ((packed and 16#7F#) / 16#10#) + 1;
    --  Indicative:
    --  iv) [...] This value should be set to indicate the
    --      richness of the original palette
    U8'Read (image.stream, background);
    U8'Read (image.stream, aspect_ratio_code);
    Buffering.Attach_Stream (image.buffer, image.stream);
    if global_palette then
      image.subformat_id := 1 + (Natural (packed and 16#07#));
      --  palette's bits per pixels, usually <= image's
      --
      --  if image.subformat_id > image.bits_per_pixel then
      --    raise
      --      error_in_image_data with
      --      "GIF: global palette has more colors than the image" &
      --       image.subformat_id'img & image.bits_per_pixel'img;
      --  end if;
      image.palette := new Color_table (0 .. 2**(image.subformat_id) - 1);
      Color_tables.Load_palette (image);
    end if;
  end Load_GIF_header;

  -----------------
  -- JPEG header --
  -----------------

  procedure Load_JPEG_header (image : in out Image_descriptor) is
    --  http://en.wikipedia.org/wiki/JPEG
    use GID.Decoding_JPG, GID.Buffering;
    sh : Segment_head;
    b : U8;
  begin
    --  We have already passed the SOI (Start of Image) segment marker (FFD8).
    image.JPEG_stuff.restart_interval := 0;
    Attach_Stream (image.buffer, image.stream);
    loop
      Read (image, sh);
      case sh.kind is
        when DHT => -- Huffman Table
          Read_DHT (image, Natural (sh.length));
        when DQT =>
          Read_DQT (image, Natural (sh.length));
        when DRI => -- Restart Interval
          Read_DRI (image);
        when SOF_0 .. SOF_15 =>
          Read_SOF (image, sh);
          exit; -- we've got header-style informations, then it's time to quit
        when APP_1 =>
          Read_EXIF (image, Natural (sh.length));
        when others =>
          --  Skip segment data
          for i in 1 .. sh.length loop
            Get_Byte (image.buffer, b);
          end loop;
      end case;
    end loop;
  end Load_JPEG_header;

  procedure Load_QOI_header (image : in out Image_descriptor) is
    val_32 : U32;
    channels, colorspace : U8;
  begin
    Buffering.Attach_Stream (image.buffer, image.stream);
    Read_any_endian (image.stream, val_32, big);
    image.width := Positive_32 (val_32);
    Read_any_endian (image.stream, val_32, big);
    image.height := Positive_32 (val_32);
    U8'Read (image.stream, channels);
    image.bits_per_pixel := Positive (channels) * 8;
    image.transparency := channels = 4;
    U8'Read (image.stream, colorspace);
  end Load_QOI_header;

  ----------------
  -- PNG header --
  ----------------

  procedure Load_PNG_header (image : in out Image_descriptor) is
    use Decoding_PNG, Buffering;
    ch : Chunk_head;
    n, dummy : U32;
    pragma Unreferenced (dummy);
    b, color_type : U8;
    palette : Boolean := False;
  begin
    Buffering.Attach_Stream (image.buffer, image.stream);
    Read (image, ch);
    if ch.kind /= IHDR then
      raise error_in_image_data with "PNG: expected 'IHDR' chunk as first chunk in PNG stream";
    end if;
    Big_endian_buffered (image.buffer, n);
    if n = 0 then
      raise error_in_image_data with "PNG image with zero width";
    end if;
    if n > U32 (Positive_32'Last) then
      raise error_in_image_data with "PNG image: width value too large:" & U32'Image (n);
    end if;
    image.width :=  Positive_32 (n);
    Big_endian_buffered (image.buffer, n);
    if n = 0 then
      raise error_in_image_data with "PNG image with zero height";
    end if;
    if n > U32 (Positive_32'Last) then
      raise error_in_image_data with "PNG image: height value too large:" & U32'Image (n);
    end if;
    image.height := Positive_32 (n);
    Get_Byte (image.buffer, b);
    if b = 0 then
      raise error_in_image_data with "PNG image: zero bit-per-pixel";
    end if;
    image.bits_per_pixel := Integer (b);
    Get_Byte (image.buffer, color_type);
    image.subformat_id := Integer (color_type);
    case color_type is
      when 0 => -- Greyscale
        image.greyscale := True;
        case image.bits_per_pixel is
          when 1 | 2 | 4 | 8 | 16 =>
            null;
          when others =>
            raise error_in_image_data with
              "PNG, type 0 (greyscale): wrong bit-per-channel depth";
        end case;
      when 2 => -- RGB TrueColor
        case image.bits_per_pixel is
          when 8 | 16 =>
            image.bits_per_pixel := 3 * image.bits_per_pixel;
          when others =>
            raise error_in_image_data with
              "PNG, type 2 (RGB): wrong bit-per-channel depth";
        end case;
      when 3 => -- RGB with palette
        palette := True;
        case image.bits_per_pixel is
          when 1 | 2 | 4 | 8 =>
            null;
          when others =>
            raise error_in_image_data with
              "PNG, type 3: wrong bit-per-channel depth";
        end case;
      when 4 => -- Grey & Alpha
        image.greyscale := True;
        image.transparency := True;
        case image.bits_per_pixel is
          when 8 | 16 =>
            image.bits_per_pixel := 2 * image.bits_per_pixel;
          when others =>
            raise error_in_image_data with
              "PNG, type 4 (Greyscale & Alpha): wrong bit-per-channel depth";
        end case;
      when 6 => -- RGBA
        image.transparency := True;
        case image.bits_per_pixel is
          when 8 | 16 =>
            image.bits_per_pixel := 4 * image.bits_per_pixel;
          when others =>
            raise error_in_image_data with
              "PNG, type 6 (RGBA): wrong bit-per-channel depth";
        end case;
      when others =>
        raise error_in_image_data with "PNG: unknown color type";
    end case;
    Get_Byte (image.buffer, b);
    if b /= 0 then
      raise error_in_image_data with
        "PNG: unknown compression format; ISO/IEC 15948:2003" &
        " knows only 'method 0' (Deflate)";
    end if;
    Get_Byte (image.buffer, b);
    if b /= 0 then
      raise error_in_image_data with
        "PNG: unknown filtering; ISO/IEC 15948:2003 knows only 'method 0'";
    end if;
    Get_Byte (image.buffer, b);
    image.interlaced := b = 1; -- Adam7
    Big_endian_buffered (image.buffer, dummy); -- Chunk's CRC
    if palette then
      loop
        Read (image, ch);
        case ch.kind is
          when IEND =>
            raise error_in_image_data with
              "PNG: a palette (PLTE) is expected here, found IEND";
          when PLTE =>
            if ch.length rem 3 /= 0 then
              raise error_in_image_data with
                "PNG: palette chunk byte length must be a multiple of 3";
            end if;
            image.palette := new Color_table (0 .. Integer (ch.length / 3) - 1);
            Color_tables.Load_palette (image);
            Big_endian_buffered (image.buffer, dummy); -- Chunk's CRC
            exit;
          when others =>
            --  Skip chunk data and CRC
            for i in 1 .. ch.length + 4 loop
              Get_Byte (image.buffer, b);
            end loop;
        end case;
      end loop;
    end if;
  end Load_PNG_header;

  --------------------------------
  -- PNM (PBM, PGM, PPM) header --
  --------------------------------

  procedure Load_PNM_header (image : in out Image_descriptor) is
    use Decoding_PNM;
    depth_val : Integer;
  begin
    image.width := Get_Positive_32 (image.stream);
    case image.subformat_id is
      when 1 | 4 =>
        image.height := Get_Positive_32 (image.stream, needs_EOL => True);
        image.greyscale := True;
        image.bits_per_pixel := 3;
      when 2 .. 3 | 5 .. 6 =>
        image.height := Get_Positive_32 (image.stream);
        depth_val := Get_Integer (image.stream, needs_EOL => True);
        if depth_val /= 255 then
          raise unsupported_image_subformat with
            "PNM: maximum depth value" & Integer'Image (depth_val) &
             "; only 255 is supported";
        end if;
        image.greyscale := image.subformat_id = 2 or image.subformat_id = 5;
        image.bits_per_pixel := 24;
      when others =>
        raise unsupported_image_subformat with
          "PNM: P" & Integer'Image (image.subformat_id) & " not supported";
    end case;
  exception
    when Constraint_Error =>
      raise error_in_image_data with "PNM: invalid numeric value in PNM header";
  end Load_PNM_header;

  ------------------------
  -- TGA (Targa) header --
  ------------------------

  procedure Load_TGA_header (image : in out Image_descriptor) is
    --  TGA FILE HEADER, p.6
    --
    image_ID_length : U8; -- Field 1
    color_map_type : U8; -- Field 2
    image_type     : U8; -- Field 3
    --  Color Map Specification - Field 4
    first_entry_index   : U16; -- Field 4.1
    color_map_length    : U16; -- Field 4.2
    color_map_entry_size : U8;  -- Field 4.3
    --  Image Specification - Field 5
    x_origin : U16;
    y_origin : U16;
    image_width : U16;
    image_height : U16;
    pixel_depth : U8;
    tga_image_descriptor : U8;
    --
    dummy : U8;
    base_image_type : Integer;
  begin
    --  Read the header
    image_ID_length := image.first_byte;
    U8'Read (image.stream, color_map_type);
    U8'Read (image.stream, image_type);
    --   Color Map Specification - Field 4
    Read_Intel (image.stream, first_entry_index);
    Read_Intel (image.stream, color_map_length);
    U8'Read (image.stream, color_map_entry_size);
    --   Image Specification - Field 5
    Read_Intel (image.stream, x_origin);
    Read_Intel (image.stream, y_origin);
    Read_Intel (image.stream, image_width);
    Read_Intel (image.stream, image_height);
    U8'Read (image.stream, pixel_depth);
    U8'Read (image.stream, tga_image_descriptor);
    --  Done.
    --
    --  Image type:
    --      1 = 8-bit palette style
    --      2 = Direct [A]RGB image
    --      3 = grayscale
    --      9 = RLE version of Type 1
    --     10 = RLE version of Type 2
    --     11 = RLE version of Type 3
    --
    base_image_type := U8'Pos (image_type and 7);
    image.RLE_encoded := (image_type and 8) /= 0;
    --
    if color_map_type /= 0 then
      image.palette := new Color_table (
        Integer (first_entry_index) ..
        Integer (first_entry_index) + Integer (color_map_length) - 1
      );
      image.subformat_id := Integer (color_map_entry_size);
      case image.subformat_id is -- = palette's bit depth
        when 8 =>       -- Grey
          null;
        when 15 => -- RGB 3*5 bit
          null;
        when 16 => -- RGBA 3*5+1 bit
          image.transparency := True;
        when 24 => -- RGB 3*8 bit
          null;
        when 32 => -- RGBA 4*8 bit
          image.transparency := True;
        when others =>
          raise error_in_image_data with
            "TGA color map (palette): wrong bit depth:" &
            Integer'Image (image.subformat_id);
      end case;
    end if;
    --
    image.greyscale := False; -- ev. overridden later
    case base_image_type is
      when 1 =>
        image.greyscale := color_map_entry_size = 8;
      when 2 =>
        null;
      when 3 =>
        image.greyscale := True;
      when others =>
        raise unsupported_image_subformat with
          "TGA type =" & Integer'Image (base_image_type);
    end case;

    image.width  := U16'Pos (image_width);
    image.height := U16'Pos (image_height);
    image.bits_per_pixel := U8'Pos (pixel_depth);

    --  Make sure we are loading a supported TGA_type
    case image.bits_per_pixel is
      when 24 | 15 | 8 =>
        null;
      when 32 | 16 =>
        image.transparency := True;
      when others =>
        raise unsupported_image_subformat with
          "TGA bits per pixels =" & Integer'Image (image.bits_per_pixel) &
          "; supported bpp are: 8, 15, 16, 24, 32";
    end case;
    image.top_first := (tga_image_descriptor and 32) /= 0;
    --  *** Image and color map data
    --  * Image ID
    for i in 1 .. image_ID_length loop
      U8'Read (image.stream, dummy);
    end loop;
    --  * Color map data (palette)
    Color_tables.Load_palette (image);
    --  * Image data: Read by Load_image_contents.
  end Load_TGA_header;

  procedure Load_TIFF_header (image : in out Image_descriptor) is
    first_IFD_offset : U32;
    --
    --  IFD: Image File Directory. Basically, the image header.
    --  Issue with TIFF: often the image header is stored after the image data.
    --  This would need streams with Set_Index instead of a general stream
    --  (e.g. a file, not an HTTP stream), or to store the full image data
    --  in a temp buffer. Perhaps we'll do that one day.
  begin
    Read_any_endian (image.stream, first_IFD_offset, image.endianess);
    raise known_but_unsupported_image_format with
      "TIFF is not appropriate for streaming. Use PNG, BMP (lossless) or JPEG instead." &
      "Info: IFD Offset=" & U32'Image (first_IFD_offset);
  end Load_TIFF_header;

end GID.Headers;


Ada PDF Writer: Ada package for writing PDF files (.pdf). Ada programming.
Some news about Ada PDF Writer and other Ada projects on Gautier's blog.