--------------------------------- -- 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; with Ada.Exceptions, Ada.Unchecked_Deallocation; package body GID.Headers is use Ada.Exceptions; ------------------------------------------------------- -- 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, d: Character; FITS_challenge: String(1..5); -- without the initial GIF_challenge : String(1..5); -- without the initial PNG_challenge : String(1..7); -- without the initial PNG_signature: constant String:= "PNG" & ASCII.CR & ASCII.LF & ASCII.SUB & ASCII.LF; 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' => Character'Read(image.stream, d); if c=d then if c = 'I' then image.detailed_format:= To_Bounded_String("TIFF, little-endian"); else image.detailed_format:= To_Bounded_String("TIFF, big-endian"); end if; image.format:= TIFF; return; 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 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; generic type Number is mod <>; procedure Read_Intel_x86_number( from : in Stream_Access; n : out Number ); pragma Inline(Read_Intel_x86_number); generic type Number is mod <>; procedure Big_endian_number( from : in out Input_buffer; n : out Number ); pragma Inline(Big_endian_number); procedure Read_Intel_x86_number( from : in Stream_Access; n : out Number ) is b: U8; m: Number:= 1; begin n:= 0; for i in 1..Number'Size/8 loop U8'Read(from, b); n:= n + m * Number(b); m:= m * 256; end loop; end Read_Intel_x86_number; procedure Big_endian_number( 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; procedure Read_Intel is new Read_Intel_x86_number( U16 ); procedure Read_Intel is new Read_Intel_x86_number( U32 ); procedure Big_endian is new Big_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; pragma Warnings(off, dummy); w, dummy16: U16; pragma Warnings(off, 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:= Natural(n); -- Pos= 23. Bitmap height, in pixels. Four bytes Read_Intel(image.stream, n); image.height:= Natural(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_exception( unsupported_image_subformat'Identity, "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_exception( unsupported_image_subformat'Identity, "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); image.width:= Natural(screen_width); image.height:= Natural(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_exception( -- error_in_image_data'Identity, -- "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, Bounded_255; 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; ---------------- -- PNG header -- ---------------- procedure Load_PNG_header (image: in out Image_descriptor) is use Decoding_PNG, Buffering; ch: Chunk_head; n, dummy: U32; pragma Warnings(off, 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_exception( error_in_image_data'Identity, "Expected 'IHDR' chunk as first chunk in PNG stream" ); end if; Big_endian(image.buffer, n); if n = 0 then Raise_exception( error_in_image_data'Identity, "PNG image with zero width" ); end if; image.width:= Natural(n); Big_endian(image.buffer, n); if n = 0 then Raise_exception( error_in_image_data'Identity, "PNG image with zero height" ); end if; image.height:= Natural(n); Get_Byte(image.buffer, b); 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_exception( error_in_image_data'Identity, "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_exception( error_in_image_data'Identity, "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_exception( error_in_image_data'Identity, "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_exception( error_in_image_data'Identity, "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_exception( error_in_image_data'Identity, "PNG, type 6 (RGBA): wrong bit-per-channel depth" ); end case; when others => Raise_exception( error_in_image_data'Identity, "Unknown PNG color type" ); end case; Get_Byte(image.buffer, b); if b /= 0 then Raise_exception( error_in_image_data'Identity, "Unknown PNG compression; ISO/IEC 15948:2003" & " knows only 'method 0' (deflate)" ); end if; Get_Byte(image.buffer, b); if b /= 0 then Raise_exception( error_in_image_data'Identity, "Unknown PNG filtering; ISO/IEC 15948:2003 knows only 'method 0'" ); end if; Get_Byte(image.buffer, b); image.interlaced:= b = 1; -- Adam7 Big_endian(image.buffer, dummy); -- Chunk's CRC if palette then loop Read(image, ch); case ch.kind is when IEND => Raise_exception( error_in_image_data'Identity, "PNG: there must be a palette, found IEND" ); when PLTE => if ch.length rem 3 /= 0 then Raise_exception( error_in_image_data'Identity, "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(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; ------------------------ -- 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 | 16 => -- RGB 3*5 bit | RGBA 3*3+1 bit null; when 24 | 32 => -- RGB 3*8 bit | RGBA 4*8 bit null; when others => Raise_exception( error_in_image_data'Identity, "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_exception( unsupported_image_subformat'Identity, "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 32 | 24 | 16 | 15 | 8 => null; when others => Raise_exception( unsupported_image_subformat'Identity, "TGA bits per pixels =" & Integer'Image(image.bits_per_pixel) ); end case; image.flag_1:= (tga_image_descriptor and 32) /= 0; -- top first -- *** 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 begin raise known_but_unsupported_image_format; end Load_TIFF_header; end;