source: trunk/Packages/bgrabitmap/bgragifformat.pas

Last change on this file was 2, checked in by chronos, 5 years ago
File size: 33.6 KB
Line 
1unit BGRAGifFormat;
2
3{$mode objfpc}{$H+}
4
5interface
6
7uses
8 Classes, SysUtils, BGRAGraphics, BGRABitmap, BGRABitmapTypes,
9 BGRAPalette;
10
11type
12 //what to do when finishing a frame and starting the next one
13 TDisposeMode = (dmNone, //undefined value
14 dmKeep, //keep the changes done by the frame
15 dmErase, //clear everything after the frame
16 dmRestore); //restore to how it was before the frame
17
18 //one image in the array
19 TGifSubImage = record
20 Image: TBGRABitmap; //image to draw at the beggining of the frame
21 Position: TPoint; //relative position of the image in the frame
22 DelayMs: integer; //time in milliseconds to wait before going to next frame
23 DisposeMode: TDisposeMode; //what do do when going to next frame
24 HasLocalPalette: boolean; //the image has its own palette
25 end;
26 TGifSubImageArray = array of TGifSubImage;
27
28 TGIFSignature = packed array[1..6] of char; //'GIF87a' or 'GIF89a'
29
30 TGIFScreenDescriptor = packed record
31 Width, Height: word;
32 flags, //screen bit depth = ((flags shr 4) and 7) + 1
33 //palette bit depth = (flags and 7) + 1
34 BackgroundColorIndex, //index of background color in global palette
35 AspectRatio64 : byte; //0 if not specified, otherwise aspect ratio is (AspectRatio64 + 15) / 64
36 end;
37
38 TGIFImageDescriptor = packed record
39 x, y, Width, Height: word;
40 flags: byte;
41 end;
42
43 TGIFImageDescriptorWithHeader = packed record
44 ImageIntroducer: byte;
45 Image: TGIFImageDescriptor;
46 end;
47
48 TGIFExtensionBlock = packed record
49 FunctionCode: byte;
50 end;
51
52 TGIFGraphicControlExtension = packed record
53 flags: byte;
54 DelayHundredthSec: word;
55 TransparentColorIndex: byte;
56 end;
57
58 TGIFGraphicControlExtensionWithHeader = packed record
59 ExtensionIntroducer: byte;
60 FunctionCode: byte;
61 BlockSize: byte;
62 GraphicControl: TGIFGraphicControlExtension;
63 BlockTerminator: byte;
64 end;
65
66 TPackedRGBTriple = packed record
67 r, g, b: byte;
68 end;
69
70 TGIFData = record
71 Width, Height: integer;
72 AspectRatio: single;
73 BackgroundColor: TColor;
74 LoopCount: Word;
75 Images: array of TGifSubImage;
76 end;
77
78 { EColorQuantizerMissing }
79
80 EColorQuantizerMissing = class(Exception)
81 constructor Create;
82 constructor Create(AMessage: string);
83 end;
84
85const
86 GIFScreenDescriptor_GlobalColorTableFlag = $80; //global palette is present
87 GIFScreenDescriptor_GlobalColorSortFlag = $08; //global palette colors are sorted by importance
88
89 GIFImageIntroducer = $2c;
90 GIFExtensionIntroducer = $21;
91 GIFBlockTerminator = $00;
92 GIFFileTerminator = $3B;
93
94 GIFGraphicControlExtension_TransparentFlag = $01; //transparent color index is provided
95 GIFGraphicControlExtension_UserInputFlag = $02; //wait for user input at this frame (ignored)
96 GIFGraphicControlExtension_FunctionCode = $f9;
97 GIFGraphicControlExtension_DisposeModeShift = 2;
98
99 GIFImageDescriptor_LocalColorTableFlag = $80; //local palette is present
100 GIFImageDescriptor_InterlacedFlag = $40; //image data is interlaced
101 GIFImageDescriptor_LocalColorSortFlag = $20; //local palette colors are sorted by importance
102
103 GIFInterlacedStart: array[1..4] of longint = (0, 4, 2, 1);
104 GIFInterlacedStep: array[1..4] of longint = (8, 8, 4, 2);
105
106 GIFCodeTableSize = 4096;
107
108 NetscapeApplicationIdentifier = 'NETSCAPE2.0';
109 NetscapeSubBlockIdLoopCount = 1;
110 NetscapeSubBlockIdBuffering = 2;
111
112function CeilLn2(AValue: Integer): integer;
113function BGRAToPackedRgbTriple(color: TBGRAPixel): TPackedRGBTriple;
114function PackedRgbTribleToBGRA(rgb: TPackedRGBTriple): TBGRAPixel;
115function GIFLoadFromStream(stream: TStream; MaxImageCount: integer = maxLongint): TGIFData;
116procedure GIFSaveToStream(AData: TGifData; Stream: TStream; AQuantizerFactory: TBGRAColorQuantizerAny;
117 ADitheringAlgorithm: TDitheringAlgorithm);
118procedure GIFDecodeLZW(AStream: TStream; AImage: TBGRACustomBitmap;
119 const APalette: ArrayOfTBGRAPixel; transcolorIndex: integer;
120 interlaced: boolean);
121
122//Encode an image supplied as an sequence of bytes, from left to right and top to bottom.
123//Adapted from the work of Udo Schmal, http://www.gocher.me/FPWriteGIF
124procedure GIFEncodeLZW(AStream: TStream; AImageData: PByte;
125 AImageWidth, AImageHeight: integer; ABitDepth: byte);
126
127implementation
128
129function PackedRgbTribleToBGRA(rgb: TPackedRGBTriple): TBGRAPixel;
130begin
131 Result.red := rgb.r;
132 Result.green := rgb.g;
133 Result.blue := rgb.b;
134 Result.alpha := 255;
135end;
136
137function BGRAToPackedRgbTriple(color: TBGRAPixel): TPackedRGBTriple;
138begin
139 result.r := color.red;
140 result.g := color.green;
141 result.b := color.blue;
142end;
143
144function CeilLn2(AValue: Integer): integer;
145var comp: integer;
146begin
147 result := 0;
148 comp := 1;
149 while (comp < AValue) and (result < 30) do
150 begin
151 inc(result);
152 comp := comp shl 1;
153 end;
154end;
155
156procedure GIFDecodeLZW(AStream: TStream; AImage: TBGRACustomBitmap;
157 const APalette: ArrayOfTBGRAPixel; transcolorIndex: integer;
158 interlaced: boolean);
159var
160 xd, yd: longint;
161type
162 Pstr = ^Tstr;
163
164 Tstr = record
165 prefix: Pstr;
166 suffix: longint;
167 end;
168 Pstrtab = ^Tstrtab;
169 Tstrtab = array[0..GIFCodeTableSize-1] of Tstr;
170
171var
172 strtab: Pstrtab;
173 oldcode, curcode, clearcode, endcode: longint;
174 codesize, codelen, codemask: longint;
175 stridx: longint;
176 bitbuf, bitsinbuf: longint;
177 bytbuf: packed array[0..255] of byte;
178 bytinbuf, bytbufidx: byte;
179 endofsrc: boolean;
180 xcnt, ycnt, ystep, pass: longint;
181 pdest: PBGRAPixel;
182
183 procedure InitStringTable;
184 var
185 i: longint;
186 begin
187 new(strtab);
188 clearcode := 1 shl codesize;
189 endcode := clearcode + 1;
190 stridx := endcode + 1;
191 codelen := codesize + 1;
192 codemask := (1 shl codelen) - 1;
193 for i := 0 to clearcode - 1 do
194 begin
195 strtab^[i].prefix := nil;
196 strtab^[i].suffix := i;
197 end;
198 for i := clearcode to GIFCodeTableSize-1 do
199 begin
200 strtab^[i].prefix := nil;
201 strtab^[i].suffix := 0;
202 end;
203 end;
204
205 procedure ClearStringTable;
206 var
207 i: longint;
208 begin
209 clearcode := 1 shl codesize;
210 endcode := clearcode + 1;
211 stridx := endcode + 1;
212 codelen := codesize + 1;
213 codemask := (1 shl codelen) - 1;
214 for i := clearcode to GIFCodeTableSize-1 do
215 begin
216 strtab^[i].prefix := nil;
217 strtab^[i].suffix := 0;
218 end;
219 end;
220
221 procedure DoneStringTable;
222 begin
223 dispose(strtab);
224 end;
225
226 function GetNextCode: longint;
227 begin
228 while (bitsinbuf < codelen) do
229 begin
230 if (bytinbuf = 0) then
231 begin
232 if AStream.Read(bytinbuf, 1) <> 1 then
233 raise exception.Create('Unexpected end of stream');
234
235 if (bytinbuf = 0) then
236 begin
237 endofsrc := True;
238 result := endcode;
239 exit;
240 end;
241 AStream.Read(bytbuf, bytinbuf);
242 bytbufidx := 0;
243 end;
244 bitbuf := bitbuf or (longint(byte(bytbuf[bytbufidx])) shl bitsinbuf);
245 Inc(bytbufidx);
246 Dec(bytinbuf);
247 Inc(bitsinbuf, 8);
248 end;
249 Result := bitbuf and codemask;
250 bitbuf := bitbuf shr codelen;
251 Dec(bitsinbuf, codelen);
252 //write(inttostr(result)+'@'+inttostr(codelen)+' ');
253 end;
254
255 procedure AddStr2Tab(prefix: Pstr; suffix: longint);
256 begin
257 if stridx >= GIFCodeTableSize then exit;
258 strtab^[stridx].prefix := prefix;
259 strtab^[stridx].suffix := suffix;
260 Inc(stridx);
261 case stridx of
262 0..1: codelen := 1;
263 2..3: codelen := 2;
264 4..7: codelen := 3;
265 8..15: codelen := 4;
266 16..31: codelen := 5;
267 32..63: codelen := 6;
268 64..127: codelen := 7;
269 128..255: codelen := 8;
270 256..511: codelen := 9;
271 512..1023: codelen := 10;
272 1024..2047: codelen := 11;
273 2048..4096: codelen := 12;
274 end;
275 codemask := (1 shl codelen) - 1;
276 end;
277
278 function Code2Str(code: longint): Pstr;
279 begin
280 Result := addr(strtab^[code]);
281 end;
282
283 procedure WriteStr(s: Pstr);
284 var
285 colorIndex: integer;
286 begin
287 if (s^.prefix <> nil) then
288 WriteStr(s^.prefix);
289 if (ycnt >= yd) then
290 begin
291 if interlaced then
292 begin
293 while ycnt >= yd do
294 begin
295 if pass >= 5 then exit;
296
297 Inc(pass);
298 ycnt := GIFInterlacedStart[pass];
299 ystep := GIFInterlacedStep[pass];
300 end;
301 end else exit;
302 end;
303
304 colorIndex := s^.suffix;
305 if xcnt = 0 then pdest := AImage.ScanLine[ycnt];
306
307 if (colorIndex <> transcolorIndex) and (colorIndex >= 0) and
308 (colorIndex < length(APalette)) then
309 pdest^ := APalette[colorIndex];
310
311 Inc(xcnt);
312 inc(pdest);
313
314 if (xcnt >= xd) then
315 begin
316 pdest := nil;
317 xcnt := 0;
318 Inc(ycnt, ystep);
319
320 if not interlaced then
321 if (ycnt >= yd) then
322 begin
323 Inc(pass);
324 end;
325
326 end;
327 end;
328
329 function firstchar(s: Pstr): byte;
330 begin
331 while (s^.prefix <> nil) do
332 s := s^.prefix;
333 Result := s^.suffix;
334 end;
335
336begin
337 endofsrc := False;
338 xd := AImage.Width;
339 yd := AImage.Height;
340 xcnt := 0;
341 pdest := nil;
342 if interlaced then
343 begin
344 pass := 1;
345 ycnt := GIFInterlacedStart[pass];
346 ystep := GIFInterlacedStep[pass];
347 end
348 else
349 begin
350 pass := 4;
351 ycnt := 0;
352 ystep := 1;
353 end;
354 oldcode := 0;
355 bitbuf := 0;
356 bitsinbuf := 0;
357 bytinbuf := 0;
358 bytbufidx := 0;
359 codesize := 0;
360 AStream.Read(codesize, 1);
361 InitStringTable;
362 curcode := getnextcode;
363 //Write('Reading ');
364 while (curcode <> endcode) and (pass < 5) and not endofsrc do
365 begin
366 if (curcode = clearcode) then
367 begin
368 ClearStringTable;
369 repeat
370 curcode := getnextcode;
371 until (curcode <> clearcode);
372 if (curcode = endcode) then
373 break;
374 WriteStr(code2str(curcode));
375 oldcode := curcode;
376 end
377 else
378 begin
379 if (curcode < stridx) then
380 begin
381 WriteStr(Code2Str(curcode));
382 AddStr2Tab(Code2Str(oldcode), firstchar(Code2Str(curcode)));
383 oldcode := curcode;
384 end
385 else
386 begin
387 if (curcode > stridx) then
388 begin
389 //write('!Invalid! ');
390 break;
391 end;
392 AddStr2Tab(Code2Str(oldcode), firstchar(Code2Str(oldcode)));
393 WriteStr(Code2Str(stridx - 1));
394 oldcode := curcode;
395 end;
396 end;
397 curcode := getnextcode;
398 end;
399 DoneStringTable;
400 //Writeln;
401 if not endofsrc then
402 begin
403 bytinbuf:= 0;
404 AStream.ReadBuffer(bytinbuf, 1);
405 if bytinbuf <> 0 then
406 raise exception.Create('Invalid GIF format: expecting block terminator');
407 end;
408end;
409
410//Encode an image supplied as an sequence of bytes, from left to right and top to bottom.
411//Adapted from the work of Udo Schmal, http://www.gocher.me/FPWriteGIF
412procedure GIFEncodeLZW(AStream: TStream; AImageData: PByte;
413 AImageWidth, AImageHeight: integer; ABitDepth: byte);
414
415var //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
425var // 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
446type TCode = Word;
447
448var
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
485type
486 PCodeTableEntry = ^TCodeTableEntry;
487 TCodeTableEntry = packed record
488 Prefix: TCode;
489 LongerFirst, LongerLast: TCode;
490 Suffix, Padding: Byte;
491 NextWithPrefix: TCode;
492 end;
493
494var
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
520var
521 CurValue: Byte;
522 i: TCode;
523 found: boolean; // decoded string in prefix table?
524begin
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
548 begin
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
555 begin
556 CurValue := ReadValue;
557
558 found := false;
559
560 i := CodeTable[CurrentCode].LongerFirst;
561 while i <> 0 do
562 begin
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;
599 end;
600 end;
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);
609 end;
610
611 WriteCode(EndStreamCode);
612 CloseBitOutput;
613 FlushByteOutput;
614
615 AStream.WriteByte(0); //GIF block terminator
616 //Writeln;
617end;
618
619function GIFLoadFromStream(stream: TStream; MaxImageCount: integer = maxLongint): TGIFData;
620
621 procedure DumpData;
622 var
623 Count: byte;
624 begin
625 repeat
626 Count := 0;
627 stream.Read(Count, 1);
628 stream.position := stream.position + Count;
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));
640 end;
641
642var
643 NbImages: integer;
644
645 GIFSignature: TGIFSignature;
646 GIFScreenDescriptor: TGIFScreenDescriptor;
647 GIFBlockID: char;
648 GIFImageDescriptor: TGIFImageDescriptor;
649
650 globalPalette: ArrayOfTBGRAPixel;
651 localPalette: ArrayOfTBGRAPixel;
652
653 transcolorIndex: integer;
654 DelayMs: integer;
655 disposeMode: TDisposeMode;
656
657 procedure LoadGlobalPalette;
658 var
659 NbEntries, i: integer;
660 rgb: TPackedRGBTriple;
661 begin
662 NbEntries := 1 shl (GIFScreenDescriptor.flags and $07 + 1);
663 setlength(globalPalette, NbEntries);
664 for i := 0 to NbEntries - 1 do
665 begin
666 stream.ReadBuffer({%H-}rgb, 3);
667 globalPalette[i] := PackedRgbTribleToBGRA(rgb);
668 end;
669 end;
670
671 procedure LoadLocalPalette;
672 var
673 NbEntries, i: integer;
674 rgb: TPackedRGBTriple;
675 begin
676 NbEntries := 1 shl (GIFImageDescriptor.flags and $07 + 1);
677 setlength(localPalette, NbEntries);
678 for i := 0 to NbEntries - 1 do
679 begin
680 stream.ReadBuffer({%H-}rgb, 3);
681 localPalette[i] := PackedRgbTribleToBGRA(rgb);
682 end;
683 end;
684
685 procedure LoadImage;
686 var
687 imgWidth, imgHeight: integer;
688 img: TBGRABitmap;
689 Interlaced: boolean;
690 palette: ArrayOfTBGRAPixel;
691 begin
692 stream.Read(GIFImageDescriptor, sizeof(GIFImageDescriptor));
693 GIFImageDescriptor.Width := LEtoN(GIFImageDescriptor.Width);
694 GIFImageDescriptor.Height := LEtoN(GIFImageDescriptor.Height);
695 GIFImageDescriptor.x := LEtoN(GIFImageDescriptor.x);
696 GIFImageDescriptor.y := LEtoN(GIFImageDescriptor.y);
697 if (GIFImageDescriptor.flags and GIFImageDescriptor_LocalColorTableFlag =
698 GIFImageDescriptor_LocalColorTableFlag) then
699 LoadLocalPalette
700 else
701 localPalette := nil;
702
703 if localPalette <> nil then
704 palette := localPalette
705 else
706 palette := globalPalette;
707 imgWidth := GIFImageDescriptor.Width;
708 imgHeight := GIFImageDescriptor.Height;
709
710 if length(result.Images) <= NbImages then
711 setlength(result.Images, length(result.Images) * 2 + 1);
712 img := TBGRABitmap.Create(imgWidth, imgHeight);
713 img.Fill(BGRAPixelTransparent);
714 result.Images[NbImages].Image := img;
715 result.Images[NbImages].Position := point(GIFImageDescriptor.x, GIFImageDescriptor.y);
716 result.Images[NbImages].DelayMs := DelayMs;
717 result.Images[NbImages].DisposeMode := disposeMode;
718 result.Images[NbImages].HasLocalPalette := localPalette <> nil;
719 Inc(NbImages);
720
721 Interlaced := GIFImageDescriptor.flags and GIFImageDescriptor_InterlacedFlag =
722 GIFImageDescriptor_InterlacedFlag;
723 GIFDecodeLZW(stream, img, palette, transcolorIndex, Interlaced);
724 end;
725
726 procedure ReadExtension;
727 var
728 GIFExtensionBlock: TGIFExtensionBlock;
729 GIFGraphicControlExtension: TGIFGraphicControlExtension;
730 mincount, Count, SubBlockId: byte;
731 app: String;
732
733 begin
734 stream.ReadBuffer({%H-}GIFExtensionBlock, sizeof(GIFExtensionBlock));
735 case GIFExtensionBlock.FunctionCode of
736 $F9: //graphic control extension
737 begin
738 Count := 0;
739 stream.Read(Count, 1);
740 if Count < sizeof(GIFGraphicControlExtension) then
741 mincount := 0
742 else
743 begin
744 mincount := sizeof(GIFGraphicControlExtension);
745 stream.ReadBuffer({%H-}GIFGraphicControlExtension, mincount);
746 GIFGraphicControlExtension.DelayHundredthSec := LEtoN(GIFGraphicControlExtension.DelayHundredthSec);
747
748 if GIFGraphicControlExtension.flags and
749 GIFGraphicControlExtension_TransparentFlag =
750 GIFGraphicControlExtension_TransparentFlag then
751 transcolorIndex := GIFGraphicControlExtension.TransparentColorIndex
752 else
753 transcolorIndex := -1;
754 if GIFGraphicControlExtension.DelayHundredthSec <> 0 then
755 DelayMs := GIFGraphicControlExtension.DelayHundredthSec * 10;
756 DisposeMode := TDisposeMode((GIFGraphicControlExtension.flags shr GIFGraphicControlExtension_DisposeModeShift) and 7);
757 end;
758 stream.Position := Stream.Position + Count - mincount;
759 DumpData;
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
787 else
788 begin
789 DumpData;
790 end;
791 end;
792 end;
793
794begin
795 result.Width := 0;
796 result.Height := 0;
797 result.BackgroundColor := clNone;
798 result.Images := nil;
799 result.AspectRatio := 1;
800 result.LoopCount := 1;
801 if stream = nil then exit;
802
803 NbImages := 0;
804 transcolorIndex := -1;
805 DelayMs := 100;
806 disposeMode := dmErase;
807
808 FillChar({%H-}GIFSignature,sizeof(GIFSignature),0);
809 stream.Read(GIFSignature, sizeof(GIFSignature));
810 if (GIFSignature[1] = 'G') and (GIFSignature[2] = 'I') and (GIFSignature[3] = 'F') then
811 begin
812 stream.ReadBuffer({%H-}GIFScreenDescriptor, sizeof(GIFScreenDescriptor));
813 GIFScreenDescriptor.Width := LEtoN(GIFScreenDescriptor.Width);
814 GIFScreenDescriptor.Height := LEtoN(GIFScreenDescriptor.Height);
815 result.Width := GIFScreenDescriptor.Width;
816 result.Height := GIFScreenDescriptor.Height;
817 if GIFScreenDescriptor.AspectRatio64 = 0 then
818 result.AspectRatio:= 1
819 else
820 result.AspectRatio:= (GIFScreenDescriptor.AspectRatio64+15)/64;
821 if (GIFScreenDescriptor.flags and GIFScreenDescriptor_GlobalColorTableFlag =
822 GIFScreenDescriptor_GlobalColorTableFlag) then
823 begin
824 LoadGlobalPalette;
825 if GIFScreenDescriptor.BackgroundColorIndex < length(globalPalette) then
826 result.BackgroundColor :=
827 BGRAToColor(globalPalette[GIFScreenDescriptor.BackgroundColorIndex]);
828 end;
829 repeat
830 stream.ReadBuffer({%H-}GIFBlockID, sizeof(GIFBlockID));
831 case GIFBlockID of
832 ';': ;
833 ',': begin
834 if NbImages >= MaxImageCount then break;
835 LoadImage;
836 end;
837 '!': ReadExtension;
838 else
839 begin
840 raise Exception.Create('TBGRAAnimatedGif: unexpected block type');
841 break;
842 end;
843 end;
844 until (GIFBlockID = ';') or (stream.Position >= stream.size);
845 end
846 else
847 raise Exception.Create('TBGRAAnimatedGif: invalid header');
848 setlength(result.Images, NbImages);
849end;
850
851procedure GIFSaveToStream(AData: TGifData; Stream: TStream; AQuantizerFactory: TBGRAColorQuantizerAny;
852 ADitheringAlgorithm: TDitheringAlgorithm);
853var
854 signature: TGIFSignature;
855 screenDescriptor: TGIFScreenDescriptor;
856 globalPalette: TBGRAPalette;
857 globalQuantizer: TBGRACustomColorQuantizer;
858 globalTranspIndex: integer;
859
860 procedure AddColorsToPalette(AImage: TBGRACustomBitmap; APalette: TBGRAPalette);
861 var n: integer;
862 p: PBGRAPixel;
863 c: TBGRAPixel;
864 begin
865 p := AImage.Data;
866 for n := AImage.NbPixels-1 downto 0 do
867 begin
868 if p^.alpha < 255 then //transparent color will be needed to dither properly
869 APalette.AddColor(BGRAPixelTransparent);
870 if p^.alpha > 0 then //color may be needed to dither properly
871 begin
872 c := p^;
873 c.alpha := 255;
874 APalette.AddColor(c);
875 end;
876 inc(p);
877 end;
878 end;
879
880 function ImageCount: integer;
881 begin
882 result := length(AData.Images);
883 end;
884
885 function NeedGlobalPalette: boolean;
886 var i: integer;
887 begin
888 for i := 0 to ImageCount-1 do
889 if not AData.Images[i].HasLocalPalette then
890 begin
891 result := true;
892 exit;
893 end;
894 end;
895
896 function IndexOfGlobalColor(AColor: TBGRAPixel): integer;
897 begin
898 if Assigned(globalQuantizer) then
899 result := globalQuantizer.ReducedPalette.FindNearestColorIndex(AColor)
900 else
901 result := globalPalette.IndexOfColor(AColor);
902 end;
903
904 procedure MakeGlobalPalette;
905 var i: integer;
906 indexed: TBGRAIndexedPalette;
907 bitDepth: integer;
908 begin
909 globalPalette := TBGRAPalette.Create;
910 for i := 0 to ImageCount-1 do
911 if not AData.Images[i].HasLocalPalette then
912 AddColorsToPalette(AData.Images[i].Image, globalPalette);
913 if AData.BackgroundColor <> clNone then
914 globalPalette.AddColor(ColorToBGRA(ColorToRGB(AData.BackgroundColor)));
915
916 if globalPalette.Count > 256 then
917 begin
918 if Assigned(AQuantizerFactory) then
919 begin
920 globalQuantizer:= AQuantizerFactory.Create(globalPalette, False, 256);
921 globalPalette.Free;
922 globalPalette := TBGRAIndexedPalette.Create(globalQuantizer.ReducedPalette);
923 end
924 else
925 begin
926 globalPalette.Free;
927 raise EColorQuantizerMissing.Create;
928 end;
929 end else
930 begin
931 indexed := TBGRAIndexedPalette.Create(globalPalette);
932 globalPalette.Free;
933 globalPalette := indexed;
934 end;
935
936 globalTranspIndex:= globalPalette.IndexOfColor(BGRAPixelTransparent);
937 if AData.BackgroundColor <> clNone then
938 screenDescriptor.BackgroundColorIndex:= IndexOfGlobalColor(ColorToBGRA(ColorToRGB(AData.BackgroundColor))) and 255;
939
940 bitDepth := CeilLn2(globalPalette.Count);
941 if bitDepth > 8 then bitDepth:= 8;
942 if bitDepth < 1 then bitDepth:= 1;
943 screenDescriptor.flags := screenDescriptor.flags or GIFScreenDescriptor_GlobalColorTableFlag;
944 screenDescriptor.flags := screenDescriptor.flags or (bitDepth-1);
945 end;
946
947 procedure WritePalette(pal: TBGRAPalette; bitDepth: integer);
948 var i: integer;
949 numberToWrite,numberFromPal: Integer;
950 rgbs: ^TPackedRGBTriple;
951 black: TPackedRGBTriple;
952 begin
953 if not Assigned(pal) then exit;
954 numberToWrite:= 1 shl bitDepth;
955 numberFromPal := pal.Count;
956 if numberFromPal > numberToWrite then numberFromPal:= numberToWrite;
957 getmem(rgbs, numberToWrite*sizeof(TPackedRGBTriple));
958 try
959 for i := 0 to numberFromPal-1 do
960 rgbs[i] := BGRAToPackedRgbTriple(pal.Color[i]);
961 black := BGRAToPackedRgbTriple(ColorToBGRA(clBlack));
962 for i := numberFromPal to numberToWrite-1 do
963 rgbs[i] := black;
964 Stream.WriteBuffer(rgbs^,sizeof(TPackedRGBTriple)*numberToWrite);
965 finally
966 freemem(rgbs);
967 end;
968 end;
969
970 procedure WriteGlobalPalette;
971 begin
972 WritePalette(globalPalette, (screenDescriptor.flags and 7)+1);
973 end;
974
975 procedure FreeGlobalPalette;
976 begin
977 FreeAndNil(globalPalette);
978 FreeAndNil(globalQuantizer);
979 end;
980
981 procedure WriteImages;
982 var
983 localPalette: TBGRAPalette;
984 localQuantizer: TBGRACustomColorQuantizer;
985 localTranspIndex: integer;
986 imageDescriptor: TGIFImageDescriptorWithHeader;
987
988 procedure MakeLocalPalette(AFrameIndex: integer);
989 var
990 indexed: TBGRAIndexedPalette;
991 bitDepth: integer;
992 begin
993 localPalette := TBGRAPalette.Create;
994 AddColorsToPalette(AData.Images[AFrameIndex].Image, localPalette);
995 if localPalette.Count > 256 then
996 begin
997 if Assigned(AQuantizerFactory) then
998 begin
999 localQuantizer:= AQuantizerFactory.Create(localPalette, False, 256);
1000 localPalette.Free;
1001 localPalette := TBGRAIndexedPalette.Create(localQuantizer.ReducedPalette);
1002 end
1003 else
1004 begin
1005 localPalette.Free;
1006 raise EColorQuantizerMissing.Create;
1007 end;
1008 end else
1009 begin
1010 indexed := TBGRAIndexedPalette.Create(localPalette);
1011 localPalette.Free;
1012 localPalette := indexed;
1013 end;
1014
1015 localTranspIndex:= localPalette.IndexOfColor(BGRAPixelTransparent);
1016
1017 bitDepth := CeilLn2(localPalette.Count);
1018 if bitDepth > 8 then bitDepth:= 8;
1019 if bitDepth < 1 then bitDepth:= 1;
1020 imageDescriptor.Image.flags := imageDescriptor.Image.flags or GIFImageDescriptor_LocalColorTableFlag;
1021 imageDescriptor.Image.flags := imageDescriptor.Image.flags or (bitDepth-1);
1022 end;
1023
1024 procedure WriteLocalPalette;
1025 begin
1026 WritePalette(localPalette, (imageDescriptor.Image.flags and 7)+1);
1027 end;
1028
1029 procedure FreeLocalPalette;
1030 begin
1031 FreeAndNil(localPalette);
1032 FreeAndNil(localQuantizer);
1033 localTranspIndex:= -1;
1034 end;
1035
1036 procedure DitherAndCompressImage(AFrame: integer; APalette: TBGRAPalette; AQuantizer: TBGRACustomColorQuantizer);
1037 var ImageData: Pointer;
1038 Image: TBGRABitmap;
1039 y,x: NativeInt;
1040 psource: PBGRAPixel;
1041 pdest: PByte;
1042 begin
1043 Image := AData.Images[AFrame].Image;
1044 if Assigned(AQuantizer) then
1045 ImageData := AQuantizer.GetDitheredBitmapIndexedData(8, ADitheringAlgorithm, Image)
1046 else
1047 begin
1048 GetMem(ImageData, Image.Width*Image.Height);
1049 pdest := ImageData;
1050 for y := 0 to Image.Height -1 do
1051 begin
1052 psource := Image.ScanLine[y];
1053 for x := 0 to Image.Width -1 do
1054 begin
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));
1059 inc(psource);
1060 inc(pdest);
1061 end;
1062 end;
1063 end;
1064 try
1065 GIFEncodeLZW(Stream, ImageData, Image.Width, Image.Height, CeilLn2(APalette.Count));
1066 finally
1067 FreeMem(ImageData);
1068 end;
1069 end;
1070
1071 procedure WriteImage(AFrame: integer);
1072 var
1073 ext: TGIFGraphicControlExtensionWithHeader;
1074 transpIndex: integer;
1075 begin
1076 fillchar({%H-}ext, sizeof(ext), 0);
1077 try
1078 ext.ExtensionIntroducer := GIFExtensionIntroducer;
1079 ext.FunctionCode := GIFGraphicControlExtension_FunctionCode;
1080 ext.BlockSize := sizeof(ext.GraphicControl);
1081 ext.GraphicControl.DelayHundredthSec := (AData.Images[AFrame].DelayMs+5) div 10;
1082 ext.GraphicControl.TransparentColorIndex := 0;
1083 ext.GraphicControl.flags := integer(AData.Images[AFrame].DisposeMode) shl GIFGraphicControlExtension_DisposeModeShift;
1084 ext.BlockTerminator := GIFBlockTerminator;
1085 with AData.Images[AFrame].Position do
1086 begin
1087 imageDescriptor.Image.x := x;
1088 imageDescriptor.Image.y := y;
1089 end;
1090 with AData.Images[AFrame].Image do
1091 begin
1092 imageDescriptor.Image.Width := Width;
1093 imageDescriptor.Image.Height := Height;
1094 end;
1095 imageDescriptor.Image.flags := 0;
1096
1097 if AData.Images[AFrame].HasLocalPalette then MakeLocalPalette(AFrame);
1098
1099 if AData.Images[AFrame].Image.HasTransparentPixels then
1100 begin
1101 if AData.Images[AFrame].HasLocalPalette then
1102 transpIndex := localTranspIndex
1103 else
1104 transpIndex := globalTranspIndex;
1105 end else
1106 transpIndex := -1;
1107 if (transpIndex >= 0) and (transpIndex <= 255) then
1108 begin
1109 ext.GraphicControl.flags := ext.GraphicControl.flags or GIFGraphicControlExtension_TransparentFlag;
1110 ext.GraphicControl.TransparentColorIndex := transpIndex;
1111 end;
1112
1113 Stream.WriteBuffer(ext, sizeof(ext));
1114 Stream.WriteBuffer(imageDescriptor, sizeof(imageDescriptor));
1115 WriteLocalPalette;
1116
1117 if AData.Images[AFrame].HasLocalPalette then
1118 DitherAndCompressImage(AFrame, localPalette, localQuantizer)
1119 else
1120 DitherAndCompressImage(AFrame, globalPalette, globalQuantizer);
1121 finally
1122 FreeLocalPalette;
1123 end;
1124 end;
1125
1126 var
1127 i: integer;
1128 begin
1129 localPalette := nil;
1130 localQuantizer := nil;
1131 localTranspIndex:= -1;
1132 fillchar({%H-}imageDescriptor, sizeof(imageDescriptor), 0);
1133 imageDescriptor.ImageIntroducer := GIFImageIntroducer;
1134 for i := 0 to ImageCount-1 do
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);
1160 end;
1161
1162begin
1163 globalPalette := nil;
1164 globalQuantizer := nil;
1165 globalTranspIndex:= -1;
1166 try
1167 signature := 'GIF89a';
1168 screenDescriptor.Width := NtoLE(AData.Width);
1169 screenDescriptor.Height := NtoLE(AData.Height);
1170 screenDescriptor.flags := $70; //suppose 8-bit screen
1171 screenDescriptor.BackgroundColorIndex := 0; //not specified for now
1172 screenDescriptor.AspectRatio64 := round(AData.AspectRatio*64)-15;
1173 if NeedGlobalPalette then MakeGlobalPalette;
1174
1175 Stream.WriteBuffer(signature, sizeof(signature));
1176 Stream.WriteBuffer(screenDescriptor, sizeof(screenDescriptor));
1177 WriteGlobalPalette;
1178
1179 WriteLoopExtension;
1180
1181 WriteImages;
1182 Stream.WriteByte(GIFFileTerminator); //end of file
1183
1184 finally
1185 FreeGlobalPalette;
1186 end;
1187end;
1188
1189{ EColorQuantizerMissing }
1190
1191constructor EColorQuantizerMissing.Create;
1192begin
1193 inherited Create('Please provide a color quantizer class (one is provided in BGRAColorQuantization)')
1194end;
1195
1196constructor EColorQuantizerMissing.Create(AMessage: string);
1197begin
1198 inherited Create(AMessage);
1199end;
1200
1201end.
1202
Note: See TracBrowser for help on using the repository browser.