Source file : gid-decoding_gif.adb
-- GIF Decoder by André van Splunter
--
-- A GIF stream is made of several "blocks".
-- The image itself is contained in an Image Descriptor block.
--
with GID.Buffering, GID.Color_tables;
with Ada.Text_IO;
package body GID.Decoding_GIF is
generic
type Number is mod <>;
procedure Read_Intel_x86_number (
from : in out Input_buffer;
n : out Number
);
pragma Inline (Read_Intel_x86_number);
procedure Read_Intel_x86_number (
from : in out Input_buffer;
n : out Number
)
is
b : U8;
m : Number := 1;
begin
n := 0;
for i in 1 .. Number'Size / 8 loop
GID.Buffering.Get_Byte (from, b);
n := n + m * Number (b);
m := m * 256;
end loop;
end Read_Intel_x86_number;
procedure Read_Intel is new Read_Intel_x86_number (U16);
----------
-- Load --
----------
procedure Load (
image : in out Image_descriptor;
next_frame : out Ada.Calendar.Day_Duration
)
is
local : Image_descriptor;
-- With GIF, each frame is a local image with an eventual
-- palette, different dimensions, etc. ...
use GID.Buffering;
type GIFDescriptor is record
ImageLeft,
ImageTop,
ImageWidth,
ImageHeight : U16;
Depth : U8;
end record;
-- For loading from the GIF file
Descriptor : GIFDescriptor;
-- Coordinates
X, tlX, brX : Natural;
Y, tlY, brY : Natural;
-- Code information
subtype Code_size_range is Natural range 2 .. 12;
CurrSize : Code_size_range;
subtype Color_type is U8;
Transp_color : Color_type := 0;
-- GIF data is stored in blocks and sub-blocks.
-- We initialize block_read and block_size to force
-- reading and buffering the next sub-block
block_size : Natural := 0;
block_read : Natural := 0;
function Read_Byte return U8 is
pragma Inline (Read_Byte);
b : U8;
begin
if block_read >= block_size then
Get_Byte (image.buffer, b);
block_size := Natural (b);
block_read := 0;
end if;
Get_Byte (image.buffer, b);
block_read := block_read + 1;
return b;
end Read_Byte;
-- Used while reading the codes
bits_in : U8 := 8;
bits_buf : U8;
-- Local procedure to read the next code from the file
function Read_Code return Natural is
bit_mask : Natural := 1;
code : Natural := 0;
begin
-- Read the code, bit by bit
for Counter in reverse 0 .. CurrSize - 1 loop
-- Next bit
bits_in := bits_in + 1;
-- Maybe, a new byte needs to be loaded with a further 8 bits
if bits_in = 9 then
bits_buf := Read_Byte;
bits_in := 1;
end if;
-- Add the current bit to the code
if (bits_buf and 1) > 0 then
code := code + bit_mask;
end if;
bit_mask := bit_mask * 2;
bits_buf := bits_buf / 2;
end loop;
return code;
end Read_Code;
generic
-- Parameter(s) that are constant through
-- the whole image. Macro-expanded generics and
-- some optimization will trim corresponding "if's"
interlaced : Boolean;
transparency : Boolean;
pixel_mask : U32;
--
procedure GIF_Decode;
procedure GIF_Decode is
procedure Pixel_with_palette (b : U8) is
pragma Inline (Pixel_with_palette);
function Times_257 (x : Primary_color_range) return Primary_color_range is
pragma Inline (Times_257);
begin
return 16 * (16 * x) + x; -- this is 257 * x, = 16#0101# * x
-- Numbers 8-bit -> no OA warning at instanciation. Returns x if type Primary_color_range is mod 2**8.
end Times_257;
full_opaque : constant Primary_color_range := Primary_color_range'Last;
begin
if transparency and then b = Transp_color then
Put_Pixel (0, 0, 0, 0);
return;
end if;
case Primary_color_range'Modulus is
when 256 =>
Put_Pixel (
Primary_color_range (local.palette (Integer (b)).red),
Primary_color_range (local.palette (Integer (b)).green),
Primary_color_range (local.palette (Integer (b)).blue),
full_opaque
);
when 65_536 =>
Put_Pixel (
Times_257 (Primary_color_range (local.palette (Integer (b)).red)),
Times_257 (Primary_color_range (local.palette (Integer (b)).green)),
Times_257 (Primary_color_range (local.palette (Integer (b)).blue)),
-- Times_257 makes max intensity FF go to FFFF
full_opaque
);
when others =>
raise invalid_primary_color_range with "GIF: color range not supported";
end case;
end Pixel_with_palette;
-- Interlacing
Interlace_pass : Natural range 1 .. 4 := 1;
Span : Natural := 7;
-- Local procedure to draw a pixel
procedure Next_Pixel (code : Natural) is
pragma Inline (Next_Pixel);
c : constant Color_type := Color_type (U32 (code) and pixel_mask);
begin
-- Actually draw the pixel on screen buffer
if X < Integer (image.width) then
if interlaced and mode = nice then
for i in reverse 0 .. Span loop
if Y + i < Integer (image.height) then
Set_X_Y (X, Integer (image.height) - (Y + i) - 1);
Pixel_with_palette (c);
end if;
end loop;
elsif Y < Integer (image.height) then
Pixel_with_palette (c);
end if;
end if;
-- Move on to next pixel
X := X + 1;
-- Or next row, if necessary
if X = brX then
X := tlX;
if interlaced then
case Interlace_pass is
when 1 =>
Y := Y + 8;
if Y >= brY then
Y := 4;
Interlace_pass := 2;
Span := 3;
Feedback ((Interlace_pass * 100) / 4);
end if;
when 2 =>
Y := Y + 8;
if Y >= brY then
Y := 2;
Interlace_pass := 3;
Span := 1;
Feedback ((Interlace_pass * 100) / 4);
end if;
when 3 =>
Y := Y + 4;
if Y >= brY then
Y := 1;
Interlace_pass := 4;
Span := 0;
Feedback ((Interlace_pass * 100) / 4);
end if;
when 4 =>
Y := Y + 2;
end case;
if mode = fast and then Y < Integer (image.height) then
Set_X_Y (X, Integer (image.height) - Y - 1);
end if;
else -- not interlaced
Y := Y + 1;
if Y < Integer (image.height) then
Set_X_Y (X, Integer (image.height) - Y - 1);
end if;
if Y mod 32 = 0 then
Feedback ((Y * 100) / Integer (image.height));
end if;
end if;
end if;
end Next_Pixel;
-- The string table
Prefix : array (0 .. 4096) of Natural := (others => 0);
Suffix : array (0 .. 4096) of Natural := (others => 0);
-- Top of Stack was 1024 until files from
-- https://www.kaggle.com/c/carvana-image-masking-challenge
-- broke it (July 2017)...
Stack : array (0 .. 2048) of Natural;
-- Special codes (specific to GIF's flavour of LZW)
ClearCode : constant Natural := 2 ** CurrSize; -- Reset code
EndingCode : constant Natural := ClearCode + 1; -- End of file
FirstFree : constant Natural := ClearCode + 2; -- Strings start here
Slot : Natural := FirstFree; -- Last read code
InitCodeSize : constant Code_size_range := CurrSize + 1;
TopSlot : Natural := 2 ** InitCodeSize; -- Highest code for current size
Code : Natural;
StackPtr : Integer := 0;
Fc : Integer := 0;
Oc : Integer := 0;
C : Integer;
BadCodeCount : Natural := 0; -- the number of bad codes found
begin -- GIF_Decode
-- The decoder source and the cool comments are kindly donated by
-- André van Splunter.
--
CurrSize := InitCodeSize;
-- This is the main loop. For each code we get we pass through the
-- linked list of prefix codes, pushing the corresponding "character"
-- for each code onto the stack. When the list reaches a single
-- "character" we push that on the stack too, and then start unstacking
-- each character for output in the correct order. Special handling is
-- included for the clear code, and the whole thing ends when we get
-- an ending code.
C := Read_Code;
while C /= EndingCode loop
-- If the code is a clear code, reinitialize all necessary items.
if C = ClearCode then
CurrSize := InitCodeSize;
Slot := FirstFree;
TopSlot := 2 ** CurrSize;
-- Continue reading codes until we get a non-clear code
-- (Another unlikely, but possible case...)
C := Read_Code;
while C = ClearCode loop
C := Read_Code;
end loop;
-- If we get an ending code immediately after a clear code
-- (Yet another unlikely case), then break out of the loop.
exit when C = EndingCode;
-- Finally, if the code is beyond the range of already set codes,
-- (This one had better NOT happen... I have no idea what will
-- result from this, but I doubt it will look good...) then set
-- it to color zero.
if C >= Slot then
C := 0;
end if;
Oc := C;
Fc := C;
-- And let us not forget to output the char...
Next_Pixel (C);
else -- C /= ClearCode
-- In this case, it's not a clear code or an ending code, so
-- it must be a code code... So we can now decode the code into
-- a stack of character codes. (Clear as mud, right?)
Code := C;
-- Here we go again with one of those off chances... If, on the
-- off chance, the code we got is beyond the range of those
-- already set up (Another thing which had better NOT happen...)
-- we trick the decoder into thinking it actually got the last
-- code read. (Hmmn... I'm not sure why this works...
-- But it does...)
if Code >= Slot then
if Code > Slot then
BadCodeCount := BadCodeCount + 1;
end if;
Code := Oc;
Stack (StackPtr) := Fc rem 256;
StackPtr := StackPtr + 1;
end if;
-- Here we scan back along the linked list of prefixes, pushing
-- helpless characters (ie. suffixes) onto the stack as we do so.
while Code >= FirstFree loop
Stack (StackPtr) := Suffix (Code);
StackPtr := StackPtr + 1;
Code := Prefix (Code);
end loop;
-- Push the last character on the stack, and set up the new
-- prefix and suffix, and if the required slot number is greater
-- than that allowed by the current bit size, increase the bit
-- size. (NOTE - If we are all full, we *don't* save the new
-- suffix and prefix... I'm not certain if this is correct...
-- it might be more proper to overwrite the last code...
Stack (StackPtr) := Code rem 256;
if Slot < TopSlot then
Suffix (Slot) := Code rem 256;
Fc := Code;
Prefix (Slot) := Oc;
Slot := Slot + 1;
Oc := C;
end if;
if Slot >= TopSlot then
if CurrSize < 12 then
TopSlot := TopSlot * 2;
CurrSize := CurrSize + 1;
end if;
end if;
-- Now that we've pushed the decoded string (in reverse order)
-- onto the stack, lets pop it off and output it...
loop
Next_Pixel (Stack (StackPtr));
exit when StackPtr = 0;
StackPtr := StackPtr - 1;
end loop;
end if;
C := Read_Code;
end loop;
if full_trace and then BadCodeCount > 0 then
Ada.Text_IO.Put_Line (
"Found" & Integer'Image (BadCodeCount) &
" bad codes"
);
end if;
end GIF_Decode;
-- Here we have several specialized instances of GIF_Decode,
-- with parameters known at compile-time -> optimizing compilers
-- will do expensive tests about interlacing and transparency at compile-time,
-- not at run-time.
--
procedure GIF_Decode_interlaced_transparent_8 is
new GIF_Decode (interlaced => True, transparency => True, pixel_mask => 255);
procedure GIF_Decode_straight_transparent_8 is
new GIF_Decode (interlaced => False, transparency => True, pixel_mask => 255);
procedure GIF_Decode_interlaced_opaque_8 is
new GIF_Decode (interlaced => True, transparency => False, pixel_mask => 255);
procedure GIF_Decode_straight_opaque_8 is
new GIF_Decode (interlaced => False, transparency => False, pixel_mask => 255);
--
procedure Skip_sub_blocks is
temp : U8;
begin
sub_blocks_sequence :
loop
Get_Byte (image.buffer, temp); -- load sub-block length byte
exit sub_blocks_sequence when temp = 0;
-- null sub-block = end of sub-block sequence
for i in 1 .. temp loop
Get_Byte (image.buffer, temp); -- load sub-block byte
end loop;
end loop sub_blocks_sequence;
end Skip_sub_blocks;
temp, temp2, label : U8;
delay_frame : U16;
c : Character;
frame_interlaced : Boolean;
frame_transparency : Boolean := False;
local_palette : Boolean;
--
separator : Character;
-- Colour information
new_num_of_colours : Natural;
custom_pixel_mask : U32;
BitsPerPixel : Natural;
begin -- Load
next_frame := 0.0;
-- Scan various GIF blocks, until finding an image
loop
Get_Byte (image.buffer, temp);
separator := Character'Val (temp);
if full_trace then
Ada.Text_IO.Put (
"GIF separator [" & separator &
"][" & U8'Image (temp) & ']'
);
end if;
case separator is
when ',' => -- 16#2C#
exit;
-- Image descriptor will begin
-- See: 20. Image Descriptor
when ';' => -- 16#3B#
if full_trace then
Ada.Text_IO.Put (" - End of GIF");
end if;
image.next_frame := 0.0;
next_frame := image.next_frame;
return; -- End of GIF image
when '!' => -- 16#21# Extensions
if full_trace then
Ada.Text_IO.Put (" - Extension");
end if;
Get_Byte (image.buffer, label);
case label is
when 16#F9# => -- See: 23. Graphic Control Extension
if full_trace then
Ada.Text_IO.Put_Line (" - 16#F9#: Graphic Control Extension");
end if;
Get_Byte (image.buffer, temp);
if temp /= 4 then
raise error_in_image_data with "GIF: error in Graphic Control Extension";
end if;
Get_Byte (image.buffer, temp);
-- Reserved 3 Bits
-- Disposal Method 3 Bits
-- User Input Flag 1 Bit
-- Transparent Color Flag 1 Bit
frame_transparency := (temp and 1) = 1;
Read_Intel (image.buffer, delay_frame);
image.next_frame :=
image.next_frame + Ada.Calendar.Day_Duration (delay_frame) / 100.0;
next_frame := image.next_frame;
Get_Byte (image.buffer, temp);
Transp_color := Color_type (temp);
-- zero sub-block:
Get_Byte (image.buffer, temp);
when 16#FE# => -- See: 24. Comment Extension
if full_trace then
Ada.Text_IO.Put_Line (" - 16#FE#: Comment Extension");
sub_blocks_sequence :
loop
Get_Byte (image.buffer, temp); -- load sub-block length byte
exit sub_blocks_sequence when temp = 0;
-- null sub-block = end of sub-block sequence
for i in 1 .. temp loop
Get_Byte (image.buffer, temp2);
c := Character'Val (temp2);
Ada.Text_IO.Put (c);
end loop;
end loop sub_blocks_sequence;
Ada.Text_IO.New_Line;
else
Skip_sub_blocks;
end if;
when 16#01# => -- See: 25. Plain Text Extension
if full_trace then
Ada.Text_IO.Put_Line (" - 16#01#: Plain Text Extension");
end if;
Skip_sub_blocks;
when 16#FF# => -- See: 26. Application Extension
if full_trace then
Ada.Text_IO.Put_Line (" - 16#FF#: Application Extension");
end if;
Skip_sub_blocks;
when others =>
if full_trace then
Ada.Text_IO.Put_Line (" - Unused extension:" & U8'Image (label));
end if;
Skip_sub_blocks;
end case;
when ASCII.NUL =>
-- Occurs in some buggy GIFs (2016).
-- Seems a 2nd zero, the 1st marking the end of sub-block list.
if full_trace then
Ada.Text_IO.Put_Line (" - Wrong separator, skip and hope for the better...");
end if;
when others =>
raise error_in_image_data with
"GIF: unknown separator: [" & separator &
"] code:" & Integer'Image (Character'Pos (separator));
end case;
end loop;
-- Load the image descriptor
Read_Intel (image.buffer, Descriptor.ImageLeft);
Read_Intel (image.buffer, Descriptor.ImageTop);
Read_Intel (image.buffer, Descriptor.ImageWidth);
Read_Intel (image.buffer, Descriptor.ImageHeight);
Get_Byte (image.buffer, Descriptor.Depth);
-- Get image corner coordinates
tlX := Natural (Descriptor.ImageLeft);
tlY := Natural (Descriptor.ImageTop);
brX := tlX + Natural (Descriptor.ImageWidth);
brY := tlY + Natural (Descriptor.ImageHeight);
-- Local Color Table Flag 1 Bit
-- Interlace Flag 1 Bit
-- Sort Flag 1 Bit
-- Reserved 2 Bits
-- Size of Local Color Table 3 Bits
--
frame_interlaced := (Descriptor.Depth and 64) = 64;
local_palette := (Descriptor.Depth and 128) = 128;
local.format := GIF;
local.stream := image.stream;
local.buffer := image.buffer;
if local_palette then
-- Get amount of colours in image
BitsPerPixel := 1 + Natural (Descriptor.Depth and 7);
new_num_of_colours := 2 ** BitsPerPixel;
-- 21. Local Color Table
local.palette := new Color_table (0 .. new_num_of_colours - 1);
Color_tables.Load_palette (local);
image.buffer := local.buffer;
elsif image.palette = null then
raise error_in_image_data with "GIF: neither local, nor global palette";
else
-- Use global palette
new_num_of_colours := 2 ** image.subformat_id;
-- usually <= 2** image.bits_per_pixel
-- Just copy main palette
local.palette := new Color_table'(image.palette.all);
end if;
custom_pixel_mask := U32 (new_num_of_colours - 1);
if full_trace then
Ada.Text_IO.Put_Line (
" - Image, interlaced: " & Boolean'Image (frame_interlaced) &
"; local palette: " & Boolean'Image (local_palette) &
"; transparency: " & Boolean'Image (frame_transparency) &
"; transparency index:" & Color_type'Image (Transp_color)
);
end if;
-- Get initial code size
Get_Byte (image.buffer, temp);
if Natural (temp) not in Code_size_range then
raise error_in_image_data with
"GIF: wrong LZW code size (must be in 2..12), is" &
U8'Image (temp);
end if;
CurrSize := Natural (temp);
-- Start at top left of image
X := Natural (Descriptor.ImageLeft);
Y := Natural (Descriptor.ImageTop);
Set_X_Y (X, Integer (image.height) - Y - 1);
--
if new_num_of_colours < 256 then
-- "Rare" formats -> no need of best speed
declare
-- We create an instance with dynamic parameters
procedure GIF_Decode_general is
new GIF_Decode (frame_interlaced, frame_transparency, custom_pixel_mask);
begin
GIF_Decode_general;
end;
else
-- 8 bit, usual format: we try to make things
-- faster with specialized instanciations...
if frame_interlaced then
if frame_transparency then
GIF_Decode_interlaced_transparent_8;
else
GIF_Decode_interlaced_opaque_8;
end if;
else -- straight (non-interlaced)
if frame_transparency then
GIF_Decode_straight_transparent_8;
else
GIF_Decode_straight_opaque_8;
end if;
end if;
end if;
Feedback (100);
--
Get_Byte (image.buffer, temp); -- zero-size sub-block
end Load;
end GID.Decoding_GIF;
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.