Changeset 521 for GraphicTest/Packages/bgrabitmap/bgragifformat.pas
- Timestamp:
- Apr 17, 2019, 12:58:41 AM (5 years ago)
- Location:
- GraphicTest
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
GraphicTest
- Property svn:ignore
-
old new 8 8 GraphicTest.lps 9 9 GraphicTest.dbg 10 heaptrclog.trc
-
- Property svn:ignore
-
GraphicTest/Packages/bgrabitmap/bgragifformat.pas
r494 r521 72 72 AspectRatio: single; 73 73 BackgroundColor: TColor; 74 LoopCount: Word; 74 75 Images: array of TGifSubImage; 75 76 end; … … 89 90 GIFExtensionIntroducer = $21; 90 91 GIFBlockTerminator = $00; 92 GIFFileTerminator = $3B; 91 93 92 94 GIFGraphicControlExtension_TransparentFlag = $01; //transparent color index is provided … … 104 106 GIFCodeTableSize = 4096; 105 107 108 NetscapeApplicationIdentifier = 'NETSCAPE2.0'; 109 NetscapeSubBlockIdLoopCount = 1; 110 NetscapeSubBlockIdBuffering = 2; 111 106 112 function CeilLn2(AValue: Integer): integer; 107 113 function BGRAToPackedRgbTriple(color: TBGRAPixel): TPackedRGBTriple; 108 114 function PackedRgbTribleToBGRA(rgb: TPackedRGBTriple): TBGRAPixel; 109 function GIFLoadFromStream(stream: TStream ): TGIFData;115 function GIFLoadFromStream(stream: TStream; MaxImageCount: integer = maxLongint): TGIFData; 110 116 procedure GIFSaveToStream(AData: TGifData; Stream: TStream; AQuantizerFactory: TBGRAColorQuantizerAny; 111 117 ADitheringAlgorithm: TDitheringAlgorithm); … … 117 123 //Adapted from the work of Udo Schmal, http://www.gocher.me/FPWriteGIF 118 124 procedure GIFEncodeLZW(AStream: TStream; AImageData: PByte; 119 AImageWidth, AImageHeight: integer; ABitDepth: integer);125 AImageWidth, AImageHeight: integer; ABitDepth: byte); 120 126 121 127 implementation … … 224 230 if (bytinbuf = 0) then 225 231 begin 226 AStream.Read(bytinbuf, 1); 232 if AStream.Read(bytinbuf, 1) <> 1 then 233 raise exception.Create('Unexpected end of stream'); 234 227 235 if (bytinbuf = 0) then 236 begin 228 237 endofsrc := True; 238 result := endcode; 239 exit; 240 end; 229 241 AStream.Read(bytbuf, bytinbuf); 230 242 bytbufidx := 0; … … 238 250 bitbuf := bitbuf shr codelen; 239 251 Dec(bitsinbuf, codelen); 252 //write(inttostr(result)+'@'+inttostr(codelen)+' '); 240 253 end; 241 254 … … 278 291 if interlaced then 279 292 begin 280 while (ycnt >= yd) and (pass < 5) do 281 begin 293 while ycnt >= yd do 294 begin 295 if pass >= 5 then exit; 296 282 297 Inc(pass); 283 298 ycnt := GIFInterlacedStart[pass]; 284 299 ystep := GIFInterlacedStep[pass]; 285 300 end; 286 end ;301 end else exit; 287 302 end; 288 303 … … 346 361 InitStringTable; 347 362 curcode := getnextcode; 363 //Write('Reading '); 348 364 while (curcode <> endcode) and (pass < 5) and not endofsrc do 349 365 begin … … 370 386 begin 371 387 if (curcode > stridx) then 388 begin 389 //write('!Invalid! '); 372 390 break; 391 end; 373 392 AddStr2Tab(Code2Str(oldcode), firstchar(Code2Str(oldcode))); 374 393 WriteStr(Code2Str(stridx - 1)); … … 379 398 end; 380 399 DoneStringTable; 400 //Writeln; 381 401 if not endofsrc then 382 402 begin 383 403 bytinbuf:= 0; 384 AStream.Read (bytinbuf, 1);404 AStream.ReadBuffer(bytinbuf, 1); 385 405 if bytinbuf <> 0 then 386 406 raise exception.Create('Invalid GIF format: expecting block terminator'); … … 391 411 //Adapted from the work of Udo Schmal, http://www.gocher.me/FPWriteGIF 392 412 procedure GIFEncodeLZW(AStream: TStream; AImageData: PByte; 393 AImageWidth, AImageHeight: integer; ABitDepth: integer); 413 AImageWidth, AImageHeight: integer; ABitDepth: byte); 414 415 var //input position 416 PInput, PInputEnd: PByte; 417 418 // get the next pixel from the bitmap 419 function ReadValue: byte; 420 begin 421 result := PInput^; 422 Inc(PInput); 423 end; 424 425 var // GIF buffer can be up to 255 bytes long 426 OutputBufferSize: NativeInt; 427 OutputBuffer: packed array[0..255] of byte; 428 429 procedure FlushByteOutput; 430 begin 431 if OutputBufferSize > 0 then 432 begin 433 OutputBuffer[0] := OutputBufferSize; 434 AStream.WriteBuffer(OutputBuffer, OutputBufferSize+1); 435 OutputBufferSize := 0; 436 end; 437 end; 438 439 procedure OutputByte(AValue: byte); 440 begin 441 if OutputBufferSize = 255 then FlushByteOutput; 442 inc(OutputBufferSize); 443 OutputBuffer[OutputBufferSize] := AValue; 444 end; 445 446 type TCode = Word; 447 394 448 var 395 LZWSize: byte; 396 OutputBufferSize: NativeInt; 397 OutputBuffer: packed array[0..255] of byte; 398 399 rPrefix: array[0..GIFCodeTableSize-1] of integer; // string prefixes 400 rSuffix: array[0..GIFCodeTableSize-1] of integer; // string suffixes 401 rCodeStack: array[0..GIFCodeTableSize-1] of byte; // encoded pixels 402 rSP: integer; // pointer into CodeStack 403 rClearCode: integer; // reset decode params 404 rEndCode: integer; // last code in input stream 405 rCurSize: integer; // current code size 406 rBitString: integer; // steady stream of bits to be decoded 407 rBits: integer; // number of valid bits in BitString 408 rMaxVal: boolean; // max code value found? 409 rCurX: integer; // position of next pixel 410 rCurY: integer; // position of next pixel 411 rCurScan: PByte; 412 rFirstSlot: integer; // for encoding an image 413 rNextSlot: integer; // for encoding 414 rRowsLeft: integer; // rows left to do 415 rLast: integer; // last byte read in 416 rUnget: boolean; // read a new byte, or use zLast? 417 418 procedure FlushOutput; 449 BitBuffer : DWord; // steady stream of bit output 450 BitBufferLen : Byte; // number of bits in buffer 451 CurCodeSize : byte; // current code size 452 453 // save the code in the output data stream 454 procedure WriteCode(Code: TCode); 455 begin 456 //Write(IntToStr(Code)+'@'+IntToStr(CurCodeSize)+' '); 457 458 // append code to bit buffer 459 BitBuffer := BitBuffer or (Code shl BitBufferLen); 460 BitBufferLen := BitBufferLen + CurCodeSize; 461 // output whole bytes 462 while BitBufferLen >= 8 do 463 begin 464 OutputByte(BitBuffer and $ff); 465 BitBuffer := BitBuffer shr 8; 466 BitBufferLen -= 8; 467 end; 468 end; 469 470 procedure CloseBitOutput; 471 begin 472 // write out the rest of the bit string 473 // and add padding bits if necessary 474 while BitBufferLen > 0 do 475 begin 476 OutputByte(BitBuffer and $ff); 477 BitBuffer := BitBuffer shr 8; 478 if BitBufferLen >= 8 then 479 BitBufferLen -= 8 480 else 481 BitBufferLen := 0; 482 end; 483 end; 484 485 type 486 PCodeTableEntry = ^TCodeTableEntry; 487 TCodeTableEntry = packed record 488 Prefix: TCode; 489 LongerFirst, LongerLast: TCode; 490 Suffix, Padding: Byte; 491 NextWithPrefix: TCode; 492 end; 493 494 var 495 ClearCode : TCode; // reset decode params 496 EndStreamCode : TCode; // last code in input stream 497 FirstCodeSlot : TCode; // first slot when table is empty 498 NextCodeSlot : TCode; // next slot to be used 499 500 PEntry: PCodeTableEntry; 501 CodeTable: array of TCodeTableEntry; 502 CurrentCode : TCode; // code representing current string 503 504 procedure DoClearCode; 505 var 506 i: Word; 507 begin 508 for i := 0 to (1 shl ABitDepth)-1 do 509 with CodeTable[i] do 510 begin 511 LongerFirst:= 0; 512 LongerLast:= 0; 513 end; 514 515 WriteCode(ClearCode); 516 CurCodeSize := ABitDepth + 1; 517 NextCodeSlot := FirstCodeSlot; 518 end; 519 520 var 521 CurValue: Byte; 522 i: TCode; 523 found: boolean; // decoded string in prefix table? 524 begin 525 if ABitDepth > 8 then 526 raise exception.Create('Maximum bit depth is 8'); 527 528 //output 529 AStream.WriteByte(ABitDepth); 530 ClearCode := 1 shl ABitDepth; 531 EndStreamCode := ClearCode + 1; 532 FirstCodeSlot := ClearCode + 2; 533 CurCodeSize := ABitDepth + 1; 534 535 OutputBufferSize := 0; 536 BitBuffer := 0; 537 BitBufferLen := 0; 538 539 //input 540 PInput := AImageData; 541 PInputEnd := AImageData + PtrInt(AImageWidth)*AImageHeight; 542 543 setlength(CodeTable, GIFCodeTableSize); 544 DoClearCode; 545 //write('Writing '); 546 547 while PInput < PInputEnd do 419 548 begin 420 if OutputBufferSize > 0 then 549 CurrentCode := ReadValue; 550 if CurrentCode >= ClearCode then 551 raise exception.Create('Internal error'); 552 553 //try to match the longest string 554 while PInput < PInputEnd do 421 555 begin 422 OutputBuffer[0] := OutputBufferSize; 423 AStream.WriteBuffer(OutputBuffer, OutputBufferSize+1); 424 OutputBufferSize := 0; 425 end; 426 end; 427 428 procedure OutputByte(AValue: byte); 429 begin 430 if OutputBufferSize = 255 then FlushOutput; 431 inc(OutputBufferSize); 432 OutputBuffer[OutputBufferSize] := AValue; 433 end; 434 435 procedure LZWReset; 436 var i: integer; 437 begin 438 for i := 0 to (GIFCodeTableSize - 1) do 439 begin 440 rPrefix[i] := 0; 441 rSuffix[i] := 0; 442 end; 443 rCurSize := LZWSize + 1; 444 rClearCode := (1 shl LZWSize); 445 rEndCode := rClearCode + 1; 446 rFirstSlot := (1 shl (rCurSize - 1)) + 2; 447 rNextSlot := rFirstSlot; 448 rMaxVal := false; 449 end; 450 451 // save a code value on the code stack 452 procedure LZWSaveCode(Code: integer); 453 begin 454 rCodeStack[rSP] := Code; 455 inc(rSP); 456 end; 457 458 // save the code in the output data stream 459 procedure LZWPutCode(code: integer); 460 var 461 n: integer; 462 b: byte; 463 begin 464 // write out finished bytes 465 // a literal "8" for 8 bits per byte 466 while (rBits >= 8) do 467 begin 468 b := (rBitString and $ff); 469 rBitString := (rBitString shr 8); 470 rBits := rBits - 8; 471 OutputByte(b); 472 end; 473 // make sure no junk bits left above the first byte 474 rBitString := (rBitString and $ff); 475 // and save out-going code 476 n := (code shl rBits); 477 rBitString := (rBitString or n); 478 rBits := rBits + rCurSize; 479 end; 480 481 // get the next pixel from the bitmap, and return it as an index into the colormap 482 function LZWReadBitmap: integer; 483 begin 484 if rUnget then 485 begin 486 result := rLast; 487 rUnget := false; 488 end 489 else 490 begin 491 if rCurScan = nil then 492 rCurScan := AImageData + rCurY*AImageWidth; 493 result := (rCurScan+rCurX)^; 494 inc(rCurX); // inc X position 495 if (rCurX >= AImageWidth) then // bumping Y ? 556 CurValue := ReadValue; 557 558 found := false; 559 560 i := CodeTable[CurrentCode].LongerFirst; 561 while i <> 0 do 496 562 begin 497 rCurX := 0; 498 inc(rCurY); 499 rCurScan := nil; 500 dec(rRowsLeft); 563 PEntry := @CodeTable[i]; 564 if PEntry^.Suffix = CurValue then 565 begin 566 found := true; 567 CurrentCode := i; 568 break; 569 end; 570 i := PEntry^.NextWithPrefix; 571 end; 572 573 if not found then 574 begin 575 PEntry := @CodeTable[CurrentCode]; 576 if PEntry^.LongerFirst = 0 then 577 begin 578 //store the first and last code being longer 579 PEntry^.LongerFirst := NextCodeSlot; 580 PEntry^.LongerLast := NextCodeSlot; 581 end else 582 begin 583 //link next entry having the same prefix 584 CodeTable[PEntry^.LongerLast].NextWithPrefix:= NextCodeSlot; 585 PEntry^.LongerLast := NextCodeSlot; 586 end; 587 588 // add new encode table entry 589 PEntry := @CodeTable[NextCodeSlot]; 590 PEntry^.Prefix := CurrentCode; 591 PEntry^.Suffix := CurValue; 592 PEntry^.LongerFirst := 0; 593 PEntry^.LongerLast := 0; 594 PEntry^.NextWithPrefix := 0; 595 inc(NextCodeSlot); 596 597 Dec(PInput); 598 break; 501 599 end; 502 600 end; 503 rLast := result; 601 602 // write the code of the longest entry found 603 WriteCode(CurrentCode); 604 605 if NextCodeSlot >= GIFCodeTableSize then 606 DoClearCode 607 else if NextCodeSlot > 1 shl CurCodeSize then 608 inc(CurCodeSize); 504 609 end; 505 610 506 var 507 i,n, 508 cc: integer; // current code to translate 509 oc: integer; // last code encoded 510 found: boolean; // decoded string in prefix table? 511 pixel: byte; // lowest code to search for 512 ldx: integer; // last index found 513 fdx: integer; // current index found 514 b: byte; 515 begin 516 LZWSize := ABitDepth; 517 AStream.WriteBuffer(LZWSize, 1); 518 OutputBufferSize := 0; 519 520 // init data block 521 fillchar(rCodeStack, sizeof(rCodeStack), 0); 522 rBitString := 0; 523 rBits := 0; 524 rCurX := 0; 525 rCurY := 0; 526 rCurScan := nil; 527 rLast := 0; 528 rUnget:= false; 529 530 LZWReset; 531 // all within the data record 532 // always save the clear code first ... 533 LZWPutCode(rClearCode); 534 // and first pixel 535 oc := LZWReadBitmap; 536 LZWPutCode(oc); 537 // nothing found yet (but then, we haven't searched) 538 ldx := 0; 539 fdx := 0; 540 // and the rest of the pixels 541 rRowsLeft := AImageHeight; 542 while (rRowsLeft > 0) do 543 begin 544 rSP := 0; // empty the stack of old data 545 n := LZWReadBitmap; // next pixel from the bitmap 546 LZWSaveCode(n); 547 cc := rCodeStack[0]; // beginning of the string 548 // add new encode table entry 549 rPrefix[rNextSlot] := oc; 550 rSuffix[rNextSlot] := cc; 551 inc(rNextSlot); 552 if (rNextSlot >= GIFCodeTableSize) then 553 rMaxVal := true 554 else if (rNextSlot > (1 shl rCurSize)) then 555 inc(rCurSize); 556 // find the running string of matching codes 557 ldx := cc; 558 found := true; 559 while (found and (rRowsLeft > 0)) do 560 begin 561 n := LZWReadBitmap; 562 LZWSaveCode(n); 563 cc := rCodeStack[0]; 564 if (ldx < rFirstSlot) then 565 i := rFirstSlot 566 else 567 i := ldx + 1; 568 pixel := rCodeStack[rSP - 1]; 569 found := false; 570 while ((not found) and (i < rNextSlot)) do 571 begin 572 found := ((rPrefix[i] = ldx) and (rSuffix[i] = pixel)); 573 inc(i); 574 end; 575 if (found) then 576 begin 577 ldx := i - 1; 578 fdx := i - 1; 579 end; 580 end; 581 // if not found, save this index, and get the same code again 582 if (not found) then 583 begin 584 rUnget := true; 585 rLast := rCodeStack[rSP-1]; 586 dec(rSP); 587 cc := ldx; 588 end 589 else 590 cc := fdx; 591 // whatever we got, write it out as current table entry 592 LZWPutCode(cc); 593 if (rMaxVal and (rRowsLeft > 0)) then 594 begin 595 LZWPutCode(rClearCode); 596 LZWReset; 597 cc := LZWReadBitmap; 598 LZWPutCode(cc); 599 end; 600 oc := cc; 601 end; 602 LZWPutCode(rEndCode); 603 // write out the rest of the bit string 604 while (rBits > 0) do 605 begin 606 b := (rBitString and $ff); 607 rBitString := (rBitString shr 8); 608 rBits := rBits - 8; 609 OutputByte(b); 610 end; 611 FlushOutput; 612 b := 0; 613 AStream.Write(b, 1); 611 WriteCode(EndStreamCode); 612 CloseBitOutput; 613 FlushByteOutput; 614 615 AStream.WriteByte(0); //GIF block terminator 616 //Writeln; 614 617 end; 615 618 616 function GIFLoadFromStream(stream: TStream ): TGIFData;619 function GIFLoadFromStream(stream: TStream; MaxImageCount: integer = maxLongint): TGIFData; 617 620 618 621 procedure DumpData; … … 625 628 stream.position := stream.position + Count; 626 629 until (Count = 0) or (stream.position >= stream.size); 630 end; 631 632 function ReadString: string; 633 var Count: byte; 634 begin 635 Count := 0; 636 stream.Read(Count, 1); 637 setlength(result, Count); 638 if Count > 0 then 639 stream.ReadBuffer(result[1], length(result)); 627 640 end; 628 641 … … 715 728 GIFExtensionBlock: TGIFExtensionBlock; 716 729 GIFGraphicControlExtension: TGIFGraphicControlExtension; 717 mincount, Count: byte; 730 mincount, Count, SubBlockId: byte; 731 app: String; 718 732 719 733 begin 720 734 stream.ReadBuffer({%H-}GIFExtensionBlock, sizeof(GIFExtensionBlock)); 721 735 case GIFExtensionBlock.FunctionCode of 722 $F9: 736 $F9: //graphic control extension 723 737 begin 724 738 Count := 0; … … 745 759 DumpData; 746 760 end; 761 $ff: //application extension 762 begin 763 app := ReadString; 764 if app <> '' then 765 begin 766 if app = NetscapeApplicationIdentifier then 767 begin 768 repeat 769 Count := 0; 770 stream.Read(Count,1); 771 if Count = 0 then break; 772 stream.ReadBuffer({%H-}SubBlockId,1); 773 Dec(Count); 774 if (SubBlockId = NetscapeSubBlockIdLoopCount) and (Count >= 2) then 775 begin 776 stream.ReadBuffer(result.LoopCount, 2); 777 dec(Count,2); 778 result.LoopCount := LEtoN(result.LoopCount); 779 if result.LoopCount > 0 then inc(result.LoopCount); 780 end; 781 stream.Position:= stream.Position+Count; 782 until false; 783 end else 784 DumpData; 785 end; 786 end 747 787 else 748 788 begin … … 758 798 result.Images := nil; 759 799 result.AspectRatio := 1; 800 result.LoopCount := 1; 760 801 if stream = nil then exit; 761 802 … … 790 831 case GIFBlockID of 791 832 ';': ; 792 ',': LoadImage; 833 ',': begin 834 if NbImages >= MaxImageCount then break; 835 LoadImage; 836 end; 793 837 '!': ReadExtension; 794 838 else … … 1009 1053 for x := 0 to Image.Width -1 do 1010 1054 begin 1011 pdest^ := APalette.IndexOfColor(psource^); 1055 if psource^.alpha < 128 then 1056 pdest^ := APalette.IndexOfColor(BGRAPixelTransparent) 1057 else 1058 pdest^ := APalette.IndexOfColor(BGRA(psource^.red,psource^.green,psource^.blue,255)); 1012 1059 inc(psource); 1013 1060 inc(pdest); … … 1087 1134 for i := 0 to ImageCount-1 do 1088 1135 WriteImage(i); 1136 end; 1137 1138 procedure WriteLoopExtension; 1139 var 1140 app: shortstring; 1141 w: Word; 1142 begin 1143 if AData.LoopCount = 1 then exit; 1144 1145 Stream.WriteByte(GIFExtensionIntroducer); 1146 Stream.WriteByte($ff); 1147 app := NetscapeApplicationIdentifier; 1148 Stream.WriteBuffer(app[0], length(app)+1); 1149 1150 Stream.WriteByte(3); 1151 Stream.WriteByte(NetscapeSubBlockIdLoopCount); 1152 if AData.LoopCount = 0 then 1153 w := 0 1154 else 1155 w := AData.LoopCount-1; 1156 w := NtoLE(w); 1157 Stream.WriteWord(w); 1158 1159 Stream.WriteByte(0); 1089 1160 end; 1090 1161 … … 1106 1177 WriteGlobalPalette; 1107 1178 1179 WriteLoopExtension; 1180 1108 1181 WriteImages; 1109 Stream.WriteByte( $3B); //end of file1182 Stream.WriteByte(GIFFileTerminator); //end of file 1110 1183 1111 1184 finally
Note:
See TracChangeset
for help on using the changeset viewer.