1-- 2-- Convert any image or animation file to BMP file(s). 3-- 4-- Middle-size test/demo for the GID (Generic Image Decoder) package. 5-- 6-- Supports: 7-- - Transparency (blends transparent or partially opaque areas with a 8-- background image, gid.gif, or a fixed, predefined colour) 9-- - Display orientation (JPEG EXIF informations from digital cameras) 10-- 11-- For a smaller and simpler example, look for mini.adb . 12-- 13 14with GID; 15 16with Ada.Calendar; 17with Ada.Characters.Handling; use Ada.Characters.Handling; 18with Ada.Command_Line; use Ada.Command_Line; 19with Ada.Streams.Stream_IO; use Ada.Streams.Stream_IO; 20with Ada.Strings.Unbounded; use Ada.Strings.Unbounded; 21with Ada.Text_IO; use Ada.Text_IO; 22with Ada.Unchecked_Deallocation; 23 24with Interfaces; 25 26procedure To_BMP is 27 28 default_bkg_name: constant String:= "gid.gif"; 29 30 procedure Blurb is 31 begin 32 Put_Line(Standard_Error, "To_BMP * Converts any image file to a BMP file"); 33 Put_Line(Standard_Error, "Simple test for the GID (Generic Image Decoder) package"); 34 Put_Line(Standard_Error, "Package version " & GID.version & " dated " & GID.reference); 35 Put_Line(Standard_Error, "URL: " & GID.web); 36 New_Line(Standard_Error); 37 Put_Line(Standard_Error, "Syntax:"); 38 Put_Line(Standard_Error, "to_bmp [-] [-<background_image_name>] <image_1> [<image_2>...]"); 39 New_Line(Standard_Error); 40 Put_Line(Standard_Error, "Options:"); 41 Put_Line(Standard_Error, " '-': don't output image (testing only)"); 42 Put_Line(Standard_Error, " '-<background_image_name>':"); 43 Put_Line(Standard_Error, " use specifed background to mix with transparent images"); 44 Put_Line(Standard_Error, " (otherwise, trying with '"& default_bkg_name &"' or single color)"); 45 New_Line(Standard_Error); 46 Put_Line(Standard_Error, "Output: "".dib"" is added the full input name(s)"); 47 Put_Line(Standard_Error, " Reason of "".dib"": unknown synonym of "".bmp"";"); 48 Put_Line(Standard_Error, " just do ""del *.dib"" for cleanup"); 49 New_Line(Standard_Error); 50 end Blurb; 51 52 -- Image used as background for displaying images having transparency 53 background_image_name: Unbounded_String:= Null_Unbounded_String; 54 55 use Interfaces; 56 57 type Byte_Array is array(Integer range <>) of Unsigned_8; 58 type p_Byte_Array is access Byte_Array; 59 procedure Dispose is new Ada.Unchecked_Deallocation(Byte_Array, p_Byte_Array); 60 61 forgive_errors: constant Boolean:= False; 62 error: Boolean; 63 64 img_buf, bkg_buf: p_Byte_Array:= null; 65 bkg: GID.Image_descriptor; 66 67 generic 68 correct_orientation: GID.Orientation; 69 -- Load image into a 24-bit truecolor BGR raw bitmap (for a BMP output) 70 procedure Load_raw_image( 71 image : in out GID.Image_descriptor; 72 buffer: in out p_Byte_Array; 73 next_frame: out Ada.Calendar.Day_Duration 74 ); 75 -- 76 procedure Load_raw_image( 77 image : in out GID.Image_descriptor; 78 buffer: in out p_Byte_Array; 79 next_frame: out Ada.Calendar.Day_Duration 80 ) 81 is 82 subtype Primary_color_range is Unsigned_8; 83 subtype U16 is Unsigned_16; 84 image_width: constant Positive:= GID.Pixel_width(image); 85 image_height: constant Positive:= GID.Pixel_height(image); 86 padded_line_size_x: constant Positive:= 87 4 * Integer(Float'Ceiling(Float(image_width) * 3.0 / 4.0)); 88 padded_line_size_y: constant Positive:= 89 4 * Integer(Float'Ceiling(Float(image_height) * 3.0 / 4.0)); 90 -- (in bytes) 91 idx: Integer; 92 mem_x, mem_y: Natural; 93 bkg_padded_line_size: Positive; 94 bkg_width, bkg_height: Natural; 95 -- 96 procedure Set_X_Y (x, y: Natural) is 97 pragma Inline(Set_X_Y); 98 use GID; 99 rev_x: constant Natural:= image_width - (x+1); 100 rev_y: constant Natural:= image_height - (y+1); 101 begin 102 case correct_orientation is 103 when Unchanged => 104 idx:= 3 * x + padded_line_size_x * y; 105 when Rotation_90 => 106 idx:= 3 * rev_y + padded_line_size_y * x; 107 when Rotation_180 => 108 idx:= 3 * rev_x + padded_line_size_x * rev_y; 109 when Rotation_270 => 110 idx:= 3 * y + padded_line_size_y * rev_x; 111 end case; 112 mem_x:= x; 113 mem_y:= y; 114 end Set_X_Y; 115 -- 116 -- No background version of Put_Pixel 117 -- 118 procedure Put_Pixel_without_bkg ( 119 red, green, blue : Primary_color_range; 120 alpha : Primary_color_range 121 ) 122 is 123 pragma Inline(Put_Pixel_without_bkg); 124 pragma Warnings(off, alpha); -- alpha is just ignored 125 use GID; 126 begin 127 buffer(idx..idx+2):= (blue, green, red); 128 -- GID requires us to look to next pixel for next time: 129 case correct_orientation is 130 when Unchanged => 131 idx:= idx + 3; 132 when Rotation_90 => 133 idx:= idx + padded_line_size_y; 134 when Rotation_180 => 135 idx:= idx - 3; 136 when Rotation_270 => 137 idx:= idx - padded_line_size_y; 138 end case; 139 end Put_Pixel_without_bkg; 140 -- 141 -- Unicolor background version of Put_Pixel 142 -- 143 procedure Put_Pixel_with_unicolor_bkg ( 144 red, green, blue : Primary_color_range; 145 alpha : Primary_color_range 146 ) 147 is 148 pragma Inline(Put_Pixel_with_unicolor_bkg); 149 u_red : constant:= 200; 150 u_green: constant:= 133; 151 u_blue : constant:= 32; 152 begin 153 if alpha = 255 then 154 buffer(idx..idx+2):= (blue, green, red); 155 else -- blend with bckground color 156 buffer(idx) := Primary_color_range((U16(alpha) * U16(blue) + U16(255-alpha) * u_blue )/255); 157 buffer(idx+1):= Primary_color_range((U16(alpha) * U16(green) + U16(255-alpha) * u_green)/255); 158 buffer(idx+2):= Primary_color_range((U16(alpha) * U16(red) + U16(255-alpha) * u_red )/255); 159 end if; 160 idx:= idx + 3; 161 -- ^ GID requires us to look to next pixel on the right for next time. 162 end Put_Pixel_with_unicolor_bkg; 163 -- 164 -- Background image version of Put_Pixel 165 -- 166 procedure Put_Pixel_with_image_bkg ( 167 red, green, blue : Primary_color_range; 168 alpha : Primary_color_range 169 ) 170 is 171 pragma Inline(Put_Pixel_with_image_bkg); 172 b_red, 173 b_green, 174 b_blue : Primary_color_range; 175 bkg_idx: Natural; 176 begin 177 if alpha = 255 then 178 buffer(idx..idx+2):= (blue, green, red); 179 else -- blend with background image 180 bkg_idx:= 3 * (mem_x mod bkg_width) + bkg_padded_line_size * (mem_y mod bkg_height); 181 b_blue := bkg_buf(bkg_idx); 182 b_green:= bkg_buf(bkg_idx+1); 183 b_red := bkg_buf(bkg_idx+2); 184 buffer(idx) := Primary_color_range((U16(alpha) * U16(blue) + U16(255-alpha) * U16(b_blue) )/255); 185 buffer(idx+1):= Primary_color_range((U16(alpha) * U16(green) + U16(255-alpha) * U16(b_green))/255); 186 buffer(idx+2):= Primary_color_range((U16(alpha) * U16(red) + U16(255-alpha) * U16(b_red) )/255); 187 end if; 188 idx:= idx + 3; 189 -- ^ GID requires us to look to next pixel on the right for next time. 190 mem_x:= mem_x + 1; 191 end Put_Pixel_with_image_bkg; 192 193 stars: Natural:= 0; 194 procedure Feedback(percents: Natural) is 195 so_far: constant Natural:= percents / 5; 196 begin 197 for i in stars+1..so_far loop 198 Put( Standard_Error, '*'); 199 end loop; 200 stars:= so_far; 201 end Feedback; 202 203 -- Here, the exciting thing: the instanciation of 204 -- GID.Load_image_contents. In our case, we load the image 205 -- into a 24-bit bitmap (because we provide a Put_Pixel 206 -- that does that with the pixels), but we could do plenty 207 -- of other things instead, like display the image live on a GUI. 208 209 -- More exciting: for tuning performance, we have 3 different 210 -- instances of GID.Load_image_contents (each of them with the full 211 -- decoders for all formats, own specialized generic instances, inlines, 212 -- etc.) depending on the transparency features. 213 214 procedure BMP24_Load_without_bkg is 215 new GID.Load_image_contents( 216 Primary_color_range, 217 Set_X_Y, 218 Put_Pixel_without_bkg, 219 Feedback, 220 GID.fast 221 ); 222 223 procedure BMP24_Load_with_unicolor_bkg is 224 new GID.Load_image_contents( 225 Primary_color_range, 226 Set_X_Y, 227 Put_Pixel_with_unicolor_bkg, 228 Feedback, 229 GID.fast 230 ); 231 232 procedure BMP24_Load_with_image_bkg is 233 new GID.Load_image_contents( 234 Primary_color_range, 235 Set_X_Y, 236 Put_Pixel_with_image_bkg, 237 Feedback, 238 GID.fast 239 ); 240 241 begin 242 error:= False; 243 Dispose(buffer); 244 case correct_orientation is 245 when GID.Unchanged | GID.Rotation_180 => 246 buffer:= new Byte_Array(0..padded_line_size_x * GID.Pixel_height(image) - 1); 247 when GID.Rotation_90 | GID.Rotation_270 => 248 buffer:= new Byte_Array(0..padded_line_size_y * GID.Pixel_width(image) - 1); 249 end case; 250 if GID.Expect_transparency(image) then 251 if background_image_name = Null_Unbounded_String then 252 BMP24_Load_with_unicolor_bkg(image, next_frame); 253 else 254 bkg_width:= GID.Pixel_width(bkg); 255 bkg_height:= GID.Pixel_height(bkg); 256 bkg_padded_line_size:= 257 4 * Integer(Float'Ceiling(Float(bkg_width) * 3.0 / 4.0)); 258 BMP24_Load_with_image_bkg(image, next_frame); 259 end if; 260 else 261 BMP24_Load_without_bkg(image, next_frame); 262 end if; 263 -- -- For testing: white rectangle with a red half-frame. 264 -- buffer.all:= (others => 255); 265 -- for x in 0..GID.Pixel_width(image)-1 loop 266 -- Put_Pixel_with_unicolor_bkg(x,0,255,0,0,255); 267 -- end loop; 268 -- for y in 0..GID.Pixel_height(image)-1 loop 269 -- Put_Pixel_with_unicolor_bkg(0,y,255,0,0,255); 270 -- end loop; 271 exception 272 when others => 273 if forgive_errors then 274 error:= True; 275 next_frame:= 0.0; 276 else 277 raise; 278 end if; 279 end Load_raw_image; 280 281 procedure Load_raw_image_0 is new Load_raw_image(GID.Unchanged); 282 procedure Load_raw_image_90 is new Load_raw_image(GID.Rotation_90); 283 procedure Load_raw_image_180 is new Load_raw_image(GID.Rotation_180); 284 procedure Load_raw_image_270 is new Load_raw_image(GID.Rotation_270); 285 286 procedure Dump_BMP_24(name: String; i: GID.Image_descriptor) is 287 f: Ada.Streams.Stream_IO.File_Type; 288 type BITMAPFILEHEADER is record 289 bfType : Unsigned_16; 290 bfSize : Unsigned_32; 291 bfReserved1: Unsigned_16:= 0; 292 bfReserved2: Unsigned_16:= 0; 293 bfOffBits : Unsigned_32; 294 end record; 295 -- ^ No packing needed 296 BITMAPFILEHEADER_Bytes: constant:= 14; 297 298 type BITMAPINFOHEADER is record 299 biSize : Unsigned_32; 300 biWidth : Unsigned_32; 301 biHeight : Unsigned_32; 302 biPlanes : Unsigned_16:= 1; 303 biBitCount : Unsigned_16; 304 biCompression : Unsigned_32:= 0; 305 biSizeImage : Unsigned_32; 306 biXPelsPerMeter: Unsigned_32:= 0; 307 biYPelsPerMeter: Unsigned_32:= 0; 308 biClrUsed : Unsigned_32:= 0; 309 biClrImportant : Unsigned_32:= 0; 310 end record; 311 -- ^ No packing needed 312 BITMAPINFOHEADER_Bytes: constant:= 40; 313 314 FileInfo : BITMAPINFOHEADER; 315 FileHeader: BITMAPFILEHEADER; 316 -- 317 generic 318 type Number is mod <>; 319 procedure Write_Intel_x86_number(n: in Number); 320 321 procedure Write_Intel_x86_number(n: in Number) is 322 m: Number:= n; 323 bytes: constant Integer:= Number'Size/8; 324 begin 325 for i in 1..bytes loop 326 Unsigned_8'Write(Stream(f), Unsigned_8(m and 255)); 327 m:= m / 256; 328 end loop; 329 end Write_Intel_x86_number; 330 procedure Write_Intel is new Write_Intel_x86_number( Unsigned_16 ); 331 procedure Write_Intel is new Write_Intel_x86_number( Unsigned_32 ); 332 begin 333 FileHeader.bfType := 16#4D42#; -- 'BM' 334 FileHeader.bfOffBits := BITMAPINFOHEADER_Bytes + BITMAPFILEHEADER_Bytes; 335 FileInfo.biSize := BITMAPINFOHEADER_Bytes; 336 case GID.Display_orientation(i) is 337 when GID.Unchanged | GID.Rotation_180 => 338 FileInfo.biWidth := Unsigned_32(GID.Pixel_width(i)); 339 FileInfo.biHeight := Unsigned_32(GID.Pixel_height(i)); 340 when GID.Rotation_90 | GID.Rotation_270 => 341 FileInfo.biWidth := Unsigned_32(GID.Pixel_height(i)); 342 FileInfo.biHeight := Unsigned_32(GID.Pixel_width(i)); 343 end case; 344 FileInfo.biBitCount := 24; 345 FileInfo.biSizeImage := Unsigned_32(img_buf.all'Length); 346 347 FileHeader.bfSize := FileHeader.bfOffBits + FileInfo.biSizeImage; 348 349 Create(f, Out_File, name & ".dib"); 350 -- BMP Header, endian-safe: 351 Write_Intel(FileHeader.bfType); 352 Write_Intel(FileHeader.bfSize); 353 Write_Intel(FileHeader.bfReserved1); 354 Write_Intel(FileHeader.bfReserved2); 355 Write_Intel(FileHeader.bfOffBits); 356 -- 357 Write_Intel(FileInfo.biSize); 358 Write_Intel(FileInfo.biWidth); 359 Write_Intel(FileInfo.biHeight); 360 Write_Intel(FileInfo.biPlanes); 361 Write_Intel(FileInfo.biBitCount); 362 Write_Intel(FileInfo.biCompression); 363 Write_Intel(FileInfo.biSizeImage); 364 Write_Intel(FileInfo.biXPelsPerMeter); 365 Write_Intel(FileInfo.biYPelsPerMeter); 366 Write_Intel(FileInfo.biClrUsed); 367 Write_Intel(FileInfo.biClrImportant); 368 -- BMP raw BGR image: 369 declare 370 -- Workaround for the severe xxx'Read xxx'Write performance 371 -- problems in the GNAT and ObjectAda compilers (as in 2009) 372 -- This is possible if and only if Byte = Stream_Element and 373 -- arrays types are both packed the same way. 374 -- 375 subtype Size_test_a is Byte_Array(1..19); 376 subtype Size_test_b is Ada.Streams.Stream_Element_Array(1..19); 377 workaround_possible: constant Boolean:= 378 Size_test_a'Size = Size_test_b'Size and then 379 Size_test_a'Alignment = Size_test_b'Alignment; 380 -- 381 begin 382 if workaround_possible then 383 declare 384 use Ada.Streams; 385 SE_Buffer : Stream_Element_Array (0..Stream_Element_Offset(img_buf'Length-1)); 386 for SE_Buffer'Address use img_buf.all'Address; 387 pragma Import (Ada, SE_Buffer); 388 begin 389 Ada.Streams.Write(Stream(f).all, SE_Buffer(0..Stream_Element_Offset(img_buf'Length-1))); 390 end; 391 else 392 Byte_Array'Write(Stream(f), img_buf.all); -- the workaround is about this line... 393 end if; 394 end; 395 Close(f); 396 end Dump_BMP_24; 397 398 procedure Process(name: String; as_background, test_only: Boolean) is 399 f: Ada.Streams.Stream_IO.File_Type; 400 i: GID.Image_descriptor; 401 up_name: constant String:= To_Upper(name); 402 -- 403 next_frame, current_frame: Ada.Calendar.Day_Duration:= 0.0; 404 begin 405 -- 406 -- Load the image in its original format 407 -- 408 Open(f, In_File, name); 409 Put_Line(Standard_Error, "Processing " & name & "..."); 410 -- 411 GID.Load_image_header( 412 i, 413 Stream(f).all, 414 try_tga => 415 name'Length >= 4 and then 416 up_name(up_name'Last-3..up_name'Last) = ".TGA" 417 ); 418 Put_Line(Standard_Error, 419 " Image format: " & GID.Image_format_type'Image(GID.Format(i)) 420 ); 421 Put_Line(Standard_Error, 422 " Image detailed format: " & GID.Detailed_format(i) 423 ); 424 Put_Line(Standard_Error, 425 " Image sub-format ID (if any): " & Integer'Image(GID.Subformat(i)) 426 ); 427 Put_Line(Standard_Error, 428 " Dimensions in pixels: " & 429 Integer'Image(GID.Pixel_width(i)) & " x" & 430 Integer'Image(GID.Pixel_height(i)) 431 ); 432 Put_Line(Standard_Error, 433 " Display orientation: " & 434 GID.Orientation'Image(GID.Display_orientation(i)) 435 ); 436 Put(Standard_Error, 437 " Color depth: " & 438 Integer'Image(GID.Bits_per_pixel(i)) & " bits" 439 ); 440 if GID.Bits_per_pixel(i) <= 24 then 441 Put_Line(Standard_Error, 442 ',' & 443 Integer'Image(2**GID.Bits_per_pixel(i)) & " colors" 444 ); 445 else 446 New_Line(Standard_Error); 447 end if; 448 Put_Line(Standard_Error, 449 " Palette: " & Boolean'Image(GID.Has_palette(i)) 450 ); 451 Put_Line(Standard_Error, 452 " Greyscale: " & Boolean'Image(GID.Greyscale(i)) 453 ); 454 Put_Line(Standard_Error, 455 " RLE encoding (if any): " & Boolean'Image(GID.RLE_encoded(i)) 456 ); 457 Put_Line(Standard_Error, 458 " Interlaced (GIF: each frame's choice): " & Boolean'Image(GID.Interlaced(i)) 459 ); 460 Put_Line(Standard_Error, 461 " Expect transparency: " & Boolean'Image(GID.Expect_transparency(i)) 462 ); 463 Put_Line(Standard_Error, "1........10........20"); 464 Put_Line(Standard_Error, " | | "); 465 -- 466 if as_background then 467 case GID.Display_orientation(i) is 468 when GID.Unchanged => 469 Load_raw_image_0(i, bkg_buf, next_frame); 470 when GID.Rotation_90 => 471 Load_raw_image_90(i, bkg_buf, next_frame); 472 when GID.Rotation_180 => 473 Load_raw_image_180(i, bkg_buf, next_frame); 474 when GID.Rotation_270 => 475 Load_raw_image_270(i, bkg_buf, next_frame); 476 end case; 477 bkg:= i; 478 New_Line(Standard_Error); 479 Close(f); 480 return; 481 end if; 482 loop 483 case GID.Display_orientation(i) is 484 when GID.Unchanged => 485 Load_raw_image_0(i, img_buf, next_frame); 486 when GID.Rotation_90 => 487 Load_raw_image_90(i, img_buf, next_frame); 488 when GID.Rotation_180 => 489 Load_raw_image_180(i, img_buf, next_frame); 490 when GID.Rotation_270 => 491 Load_raw_image_270(i, img_buf, next_frame); 492 end case; 493 if not test_only then 494 Dump_BMP_24(name & Duration'Image(current_frame), i); 495 end if; 496 New_Line(Standard_Error); 497 if error then 498 Put_Line(Standard_Error, "Error!"); 499 end if; 500 exit when next_frame = 0.0; 501 current_frame:= next_frame; 502 end loop; 503 Close(f); 504 exception 505 when GID.unknown_image_format => 506 Put_Line(Standard_Error, " Image format is unknown!"); 507 if Is_Open(f) then 508 Close(f); 509 end if; 510 end Process; 511 512 test_only: Boolean:= False; 513 514begin 515 if Argument_Count=0 then 516 Blurb; 517 return; 518 end if; 519 Put_Line(Standard_Error, "To_BMP, using GID version " & GID.version & " dated " & GID.reference); 520 begin 521 Process(default_bkg_name, True, False); 522 -- if success: 523 background_image_name:= To_Unbounded_String(default_bkg_name); 524 exception 525 when Ada.Text_IO.Name_Error => 526 null; -- nothing bad, just couldn't find default background 527 end; 528 for i in 1..Argument_Count loop 529 declare 530 arg: constant String:= Argument(i); 531 begin 532 if arg /= "" and then arg(arg'First)='-' then 533 declare 534 opt: constant String:= arg(arg'First+1..arg'Last); 535 begin 536 if opt = "" then 537 test_only:= True; 538 else 539 Put_Line(Standard_Error, "Background image is " & opt); 540 Process(opt, True, False); 541 -- define this only after processing, otherwise 542 -- a transparent background will try to use 543 -- an undefined background 544 background_image_name:= To_Unbounded_String(opt); 545 end if; 546 end; 547 else 548 Process(arg, False, test_only); 549 end if; 550 end; 551 end loop; 552end To_BMP; 553