1 // SPDX-License-Identifier: LGPL-3.0-linking-exception
2 unit BGRAAnimatedGif;
3
4 {$mode objfpc}{$H+}
5 {$i bgrabitmap.inc}
6
7 interface
8
9 uses
10 BGRAClasses, SysUtils, BGRAGraphics, FPImage, BGRABitmap, BGRABitmapTypes,
11 BGRAPalette, BGRAGifFormat;
12
13 type
14 TDisposeMode = BGRAGifFormat.TDisposeMode;
15 TGifSubImage = BGRAGifFormat.TGifSubImage;
16 TGifSubImageArray = BGRAGifFormat.TGifSubImageArray;
17
18 //how to deal with the background under the GIF animation
19 TGifBackgroundMode = (gbmSimplePaint, gbmEraseBackground,
20 gbmSaveBackgroundOnce, gbmUpdateBackgroundContinuously);
21
22 { TBGRAAnimatedGif }
23
24 TBGRAAnimatedGif = class(TGraphic)
25 private
26 FAspectRatio: single;
27 FWidth, FHeight: integer;
28 FBackgroundColor: TColor;
29
30 FPrevDate: TDateTime;
31 FPaused: boolean;
32 FTimeAccumulator: double;
33 FCurrentImage, FWantedImage: integer;
34 FTotalAnimationTime: int64;
35 FPreviousDisposeMode: TDisposeMode;
36
37 FBackgroundImage, FPreviousVirtualScreen, FStretchedVirtualScreen,
38 FInternalVirtualScreen, FRestoreImage: TBGRABitmap;
39 FImageChanged: boolean;
40
41 procedure CheckFrameIndex(AIndex: integer);
GetAverageDelayMsnull42 function GetAverageDelayMs: integer;
GetCountnull43 function GetCount: integer;
GetFrameDelayMsnull44 function GetFrameDelayMs(AIndex: integer): integer;
GetFrameDisposeModenull45 function GetFrameDisposeMode(AIndex: integer): TDisposeMode;
GetFrameHasLocalPalettenull46 function GetFrameHasLocalPalette(AIndex: integer): boolean;
GetFrameImagenull47 function GetFrameImage(AIndex: integer): TBGRABitmap;
GetFrameImagePosnull48 function GetFrameImagePos(AIndex: integer): TPoint;
GetTimeUntilNextImagenull49 function GetTimeUntilNextImage: integer;
50 procedure Render(StretchWidth, StretchHeight: integer);
51 procedure SetAspectRatio(AValue: single);
52 procedure SetBackgroundColor(AValue: TColor);
53 procedure SetFrameDelayMs(AIndex: integer; AValue: integer);
54 procedure SetFrameDisposeMode(AIndex: integer; AValue: TDisposeMode);
55 procedure SetFrameHasLocalPalette(AIndex: integer; AValue: boolean);
56 procedure SetFrameImage(AIndex: integer; AValue: TBGRABitmap);
57 procedure SetFrameImagePos(AIndex: integer; AValue: TPoint);
58 procedure UpdateSimple(Canvas: TCanvas; ARect: TRect;
59 DrawOnlyIfChanged: boolean = True);
60 procedure UpdateEraseBackground(Canvas: TCanvas; ARect: TRect;
61 DrawOnlyIfChanged: boolean = True);
62 procedure Init;
GetBitmapnull63 function GetBitmap: TBitmap;
GetMemBitmapnull64 function GetMemBitmap: TBGRABitmap;
65 procedure SaveBackgroundOnce(Canvas: TCanvas; ARect: TRect);
66 procedure SetCurrentImage(Index: integer);
67
68 protected
69 FImages: TGifSubImageArray;
70
71 {TGraphic}
72 procedure Draw(ACanvas: TCanvas; const Rect: TRect); override;
GetEmptynull73 function GetEmpty: boolean; override;
GetHeightnull74 function GetHeight: integer; override;
GetTransparentnull75 function GetTransparent: boolean; override;
GetWidthnull76 function GetWidth: integer; override;
77 procedure SetHeight({%H-}Value: integer); override;
78 procedure SetTransparent({%H-}Value: boolean); override;
79 procedure SetWidth({%H-}Value: integer); override;
80 procedure ClearViewer; virtual;
81
82 public
83 EraseColor: TColor;
84 BackgroundMode: TGifBackgroundMode;
85 LoopCount: Word;
86 LoopDone: Integer;
87
88 constructor Create(filenameUTF8: string); overload;
89 constructor Create(stream: TStream); overload;
90 constructor Create(stream: TStream; AMaxImageCount: integer); overload;
91 constructor Create; overload; override;
Duplicatenull92 function Duplicate: TBGRAAnimatedGif;
AddFramenull93 function AddFrame(AImage: TFPCustomImage; X,Y: integer; ADelayMs: integer;
94 ADisposeMode: TDisposeMode = dmErase; AHasLocalPalette: boolean = false) : integer;
95 procedure InsertFrame(AIndex: integer; AImage: TFPCustomImage; X,Y: integer; ADelayMs: integer;
96 ADisposeMode: TDisposeMode = dmErase; AHasLocalPalette: boolean = false);
97 procedure DeleteFrame(AIndex: integer; AEnsureNextFrameDoesNotChange: boolean);
98
99 //add a frame that replaces completely the previous one
AddFullFramenull100 function AddFullFrame(AImage: TFPCustomImage; ADelayMs: integer;
101 AHasLocalPalette: boolean = true): integer;
102 procedure InsertFullFrame(AIndex: integer;
103 AImage: TFPCustomImage; ADelayMs: integer;
104 AHasLocalPalette: boolean = true);
105 procedure ReplaceFullFrame(AIndex: integer;
106 AImage: TFPCustomImage; ADelayMs: integer;
107 AHasLocalPalette: boolean = true);
108
109 {TGraphic}
110 procedure LoadFromStream(Stream: TStream); overload; override;
111 procedure LoadFromStream(Stream: TStream; AMaxImageCount: integer); overload;
112 procedure LoadFromResource(AFilename: string);
113 procedure SaveToStream(Stream: TStream); overload; override;
114 procedure LoadFromFile(const AFilenameUTF8: string); override;
115 procedure SaveToFile(const AFilenameUTF8: string); override;
GetFileExtensionsnull116 class function GetFileExtensions: string; override;
117
118 procedure SetSize(AWidth,AHeight: integer); virtual;
119 procedure SaveToStream(Stream: TStream; AQuantizer: TBGRAColorQuantizerAny;
120 ADitheringAlgorithm: TDitheringAlgorithm); overload; virtual;
121 procedure Clear; override;
122 destructor Destroy; override;
123 procedure Pause;
124 procedure Resume;
125
126 procedure Show(Canvas: TCanvas; ARect: TRect); overload;
127 procedure Update(Canvas: TCanvas; ARect: TRect); overload;
128 procedure Hide(Canvas: TCanvas; ARect: TRect); overload;
129
130 property BackgroundColor: TColor Read FBackgroundColor write SetBackgroundColor;
131 property Count: integer Read GetCount;
132 property Width: integer Read FWidth;
133 property Height: integer Read FHeight;
134 property Paused: boolean Read FPaused;
135 property Bitmap: TBitmap Read GetBitmap;
136 property MemBitmap: TBGRABitmap Read GetMemBitmap;
137 property CurrentImage: integer Read FCurrentImage Write SetCurrentImage;
138 property TimeUntilNextImageMs: integer read GetTimeUntilNextImage;
139 property FrameImage[AIndex: integer]: TBGRABitmap read GetFrameImage write SetFrameImage;
140 property FrameHasLocalPalette[AIndex: integer]: boolean read GetFrameHasLocalPalette write SetFrameHasLocalPalette;
141 property FrameImagePos[AIndex: integer]: TPoint read GetFrameImagePos write SetFrameImagePos;
142 property FrameDelayMs[AIndex: integer]: integer read GetFrameDelayMs write SetFrameDelayMs;
143 property FrameDisposeMode[AIndex: integer]: TDisposeMode read GetFrameDisposeMode write SetFrameDisposeMode;
144 property AspectRatio: single read FAspectRatio write SetAspectRatio;
145 property TotalAnimationTimeMs: Int64 read FTotalAnimationTime;
146 property AverageDelayMs: integer read GetAverageDelayMs;
147 end;
148
149 { TBGRAReaderGIF }
150
151 TBGRAReaderGIF = class(TFPCustomImageReader)
152 protected
153 procedure InternalRead(Str: TStream; Img: TFPCustomImage); override;
InternalChecknull154 function InternalCheck(Str: TStream): boolean; override;
155 end;
156
157 { TBGRAWriterGIF }
158
159 TBGRAWriterGIF = class(TFPCustomImageWriter)
160 protected
161 procedure InternalWrite(Str: TStream; Img: TFPCustomImage); override;
162 end;
163
164 const
165 GifBackgroundModeStr: array[TGifBackgroundMode] of string =
166 ('gbmSimplePaint', 'gbmEraseBackground', 'gbmSaveBackgroundOnce',
167 'gbmUpdateBackgroundContinuously');
168
169 implementation
170
171 uses BGRABlend, BGRAUTF8{$IFDEF BGRABITMAP_USE_LCL}, Graphics{$ENDIF};
172
173 const
174 {$IFDEF ENDIAN_LITTLE}
175 AlphaMask = $FF000000;
176 {$ELSE}
177 AlphaMask = $000000FF;
178 {$ENDIF}
179
180
181 { TBGRAAnimatedGif }
182
TBGRAAnimatedGif.GetFileExtensionsnull183 class function TBGRAAnimatedGif.GetFileExtensions: string;
184 begin
185 Result := 'gif';
186 end;
187
188 procedure TBGRAAnimatedGif.SetSize(AWidth, AHeight: integer);
189 begin
190 ClearViewer;
191 FWidth := AWidth;
192 FHeight := AHeight;
193 end;
194
195 procedure TBGRAAnimatedGif.SaveToStream(Stream: TStream;
196 AQuantizer: TBGRAColorQuantizerAny;
197 ADitheringAlgorithm: TDitheringAlgorithm);
198 var data: TGIFData;
199 begin
200 data.Height:= Height;
201 data.Width := Width;
202 data.AspectRatio := 1;
203 data.BackgroundColor := BackgroundColor;
204 data.Images := FImages;
205 data.LoopCount := LoopCount;
206 GIFSaveToStream(data, Stream, AQuantizer, ADitheringAlgorithm);
207 end;
208
209 procedure TBGRAAnimatedGif.Render(StretchWidth, StretchHeight: integer);
210 var
211 curDate: TDateTime;
212 previousImage, nextImage: integer;
213
214 begin
215 if FInternalVirtualScreen = nil then
216 begin
217 FInternalVirtualScreen := TBGRABitmap.Create(FWidth, FHeight);
218 if (Count = 0) and (BackgroundColor <> clNone) then
219 FInternalVirtualScreen.Fill(BackgroundColor)
220 else
221 FInternalVirtualScreen.Fill(BGRAPixelTransparent);
222 FImageChanged := True;
223 end;
224
225 if Count = 0 then
226 exit;
227
228 previousImage := FCurrentImage;
229
230 curDate := Now;
231 if FWantedImage <> -1 then
232 begin
233 nextImage := FWantedImage;
234 FTimeAccumulator := 0;
235 FWantedImage := -1;
236 end
237 else
238 if FCurrentImage = -1 then
239 begin
240 nextImage := 0;
241 FTimeAccumulator := 0;
242 FPreviousDisposeMode := dmNone;
243 end
244 else
245 begin
246 if not FPaused then
247 IncF(FTimeAccumulator, (curDate - FPrevDate) * 24 * 60 * 60 * 1000);
248 if FTotalAnimationTime > 0 then FTimeAccumulator:= frac(FTimeAccumulator/FTotalAnimationTime)*FTotalAnimationTime;
249 nextImage := FCurrentImage;
250 while FTimeAccumulator > FImages[nextImage].DelayMs do
251 begin
252 DecF(FTimeAccumulator, FImages[nextImage].DelayMs);
253 Inc(nextImage);
254 if nextImage >= Count then
255 begin
256 if (LoopCount > 0) and (LoopDone >= LoopCount-1) then
257 begin
258 LoopDone := LoopCount;
259 dec(nextImage);
260 break;
261 end else
262 begin
263 nextImage := 0;
264 inc(LoopDone);
265 end;
266 end;
267
268 if nextImage = previousImage then
269 begin
270 if not ((LoopCount > 0) and (LoopDone >= LoopCount-1)) then
271 begin
272 Inc(nextImage);
273 if nextImage >= Count then
274 nextImage := 0;
275 end;
276 break;
277 end;
278 end;
279 end;
280 FPrevDate := curDate;
281
282 while FCurrentImage <> nextImage do
283 begin
284 Inc(FCurrentImage);
285 if FCurrentImage >= Count then
286 begin
287 FCurrentImage := 0;
288 FPreviousDisposeMode := dmErase;
289 end;
290
291 case FPreviousDisposeMode of
292 dmErase: FInternalVirtualScreen.Fill(BGRAPixelTransparent);
293 dmRestore: if FRestoreImage <> nil then
294 FInternalVirtualScreen.PutImage(0, 0, FRestoreImage, dmSet);
295 end;
296
297 with FImages[FCurrentImage] do
298 begin
299 if disposeMode = dmRestore then
300 begin
301 if FRestoreImage = nil then
302 FRestoreImage := TBGRABitmap.Create(FWidth, FHeight);
303 FRestoreImage.PutImage(0, 0, FInternalVirtualScreen, dmSet);
304 end;
305
306 if Image <> nil then
307 FInternalVirtualScreen.PutImage(Position.X, Position.Y, Image,
308 dmSetExceptTransparent);
309 FPreviousDisposeMode := DisposeMode;
310 end;
311
312 FImageChanged := True;
313 previousImage := FCurrentImage;
314 FInternalVirtualScreen.InvalidateBitmap;
315 end;
316
317 if FStretchedVirtualScreen <> nil then
318 FStretchedVirtualScreen.FreeReference;
319 if (FInternalVirtualScreen.Width = StretchWidth) and
320 (FInternalVirtualScreen.Height = StretchHeight) then
321 FStretchedVirtualScreen := TBGRABitmap(FInternalVirtualScreen.NewReference)
322 else
323 FStretchedVirtualScreen :=
324 TBGRABitmap(FInternalVirtualScreen.Resample(StretchWidth, StretchHeight));
325 end;
326
327 procedure TBGRAAnimatedGif.SetAspectRatio(AValue: single);
328 begin
329 if AValue < 0.25 then AValue := 0.25;
330 if AValue > 4 then AValue := 4;
331 if FAspectRatio=AValue then Exit;
332 FAspectRatio:=AValue;
333 end;
334
335 procedure TBGRAAnimatedGif.SetBackgroundColor(AValue: TColor);
336 begin
337 if FBackgroundColor=AValue then Exit;
338 FBackgroundColor:=AValue;
339 end;
340
341 procedure TBGRAAnimatedGif.SetFrameDelayMs(AIndex: integer; AValue: integer);
342 begin
343 CheckFrameIndex(AIndex);
344 if AValue < 0 then AValue := 0;
345 FTotalAnimationTime := FTotalAnimationTime + AValue - FImages[AIndex].DelayMs;
346 FImages[AIndex].DelayMs := AValue;
347 end;
348
349 procedure TBGRAAnimatedGif.SetFrameDisposeMode(AIndex: integer;
350 AValue: TDisposeMode);
351 begin
352 CheckFrameIndex(AIndex);
353 FImages[AIndex].DisposeMode := AValue;
354 end;
355
356 procedure TBGRAAnimatedGif.SetFrameHasLocalPalette(AIndex: integer;
357 AValue: boolean);
358 begin
359 CheckFrameIndex(AIndex);
360 FImages[AIndex].HasLocalPalette := AValue;
361
362 end;
363
364 procedure TBGRAAnimatedGif.SetFrameImage(AIndex: integer; AValue: TBGRABitmap);
365 var ACopy: TBGRABitmap;
366 begin
367 CheckFrameIndex(AIndex);
368 ACopy := AValue.Duplicate;
369 FImages[AIndex].Image.FreeReference;
370 FImages[AIndex].Image := ACopy;
371 end;
372
373 procedure TBGRAAnimatedGif.SetFrameImagePos(AIndex: integer; AValue: TPoint);
374 begin
375 CheckFrameIndex(AIndex);
376 FImages[AIndex].Position := AValue;
377 end;
378
379 procedure TBGRAAnimatedGif.UpdateSimple(Canvas: TCanvas; ARect: TRect;
380 DrawOnlyIfChanged: boolean = True);
381 begin
382 if FPreviousVirtualScreen <> nil then
383 begin
384 FPreviousVirtualScreen.FreeReference;
385 FPreviousVirtualScreen := nil;
386 end;
387
388 Render(ARect.Right - ARect.Left, ARect.Bottom - ARect.Top);
389 if FImageChanged then
390 begin
391 FStretchedVirtualScreen.Draw(Canvas, ARect.Left, ARect.Top, False);
392 FImageChanged := False;
393 end
394 else
395 if not DrawOnlyIfChanged then
396 FStretchedVirtualScreen.Draw(Canvas, ARect.Left, ARect.Top, False);
397
398 FPreviousVirtualScreen := TBGRABitmap(FStretchedVirtualScreen.NewReference);
399 end;
400
401 procedure TBGRAAnimatedGif.CheckFrameIndex(AIndex: integer);
402 begin
403 if (AIndex < 0) or (AIndex >= Count) then Raise ERangeError.Create('Index out of bounds');
404 end;
405
TBGRAAnimatedGif.GetAverageDelayMsnull406 function TBGRAAnimatedGif.GetAverageDelayMs: integer;
407 var sum: int64;
408 i: Integer;
409 begin
410 if Count > 0 then
411 begin
412 sum := 0;
413 for i := 0 to Count-1 do
414 inc(sum, FrameDelayMs[i]);
415 result := sum div Count;
416 end else
417 result := 100; //default
418 end;
419
GetCountnull420 function TBGRAAnimatedGif.GetCount: integer;
421 begin
422 Result := length(FImages);
423 end;
424
GetFrameDelayMsnull425 function TBGRAAnimatedGif.GetFrameDelayMs(AIndex: integer): integer;
426 begin
427 CheckFrameIndex(AIndex);
428 result := FImages[AIndex].DelayMs;
429 end;
430
TBGRAAnimatedGif.GetFrameDisposeModenull431 function TBGRAAnimatedGif.GetFrameDisposeMode(AIndex: integer): TDisposeMode;
432 begin
433 CheckFrameIndex(AIndex);
434 result := FImages[AIndex].DisposeMode;
435 end;
436
TBGRAAnimatedGif.GetFrameHasLocalPalettenull437 function TBGRAAnimatedGif.GetFrameHasLocalPalette(AIndex: integer): boolean;
438 begin
439 CheckFrameIndex(AIndex);
440 result := FImages[AIndex].HasLocalPalette;
441 end;
442
GetFrameImagenull443 function TBGRAAnimatedGif.GetFrameImage(AIndex: integer): TBGRABitmap;
444 begin
445 CheckFrameIndex(AIndex);
446 result := FImages[AIndex].Image;
447 end;
448
TBGRAAnimatedGif.GetFrameImagePosnull449 function TBGRAAnimatedGif.GetFrameImagePos(AIndex: integer): TPoint;
450 begin
451 CheckFrameIndex(AIndex);
452 result := FImages[AIndex].Position;
453 end;
454
TBGRAAnimatedGif.GetTimeUntilNextImagenull455 function TBGRAAnimatedGif.GetTimeUntilNextImage: integer;
456 var
457 acc: double;
458 begin
459 if Count <= 1 then result := 60*1000 else
460 if (FWantedImage <> -1) or (FCurrentImage = -1) then
461 result := 0
462 else
463 begin
464 acc := FTimeAccumulator;
465 if not FPaused then IncF(acc, (Now- FPrevDate) * 24 * 60 * 60 * 1000);
466 if acc >= FImages[FCurrentImage].DelayMs then
467 result := 0
468 else
469 result := round(FImages[FCurrentImage].DelayMs-FTimeAccumulator);
470 end;
471 end;
472
473 constructor TBGRAAnimatedGif.Create(filenameUTF8: string);
474 begin
475 inherited Create;
476 Init;
477 LoadFromFile(filenameUTF8);
478 end;
479
480 constructor TBGRAAnimatedGif.Create(stream: TStream);
481 begin
482 inherited Create;
483 Init;
484 LoadFromStream(stream);
485 end;
486
487 constructor TBGRAAnimatedGif.Create(stream: TStream; AMaxImageCount: integer);
488 begin
489 inherited Create;
490 Init;
491 LoadFromStream(stream, AMaxImageCount);
492 end;
493
494 constructor TBGRAAnimatedGif.Create;
495 begin
496 inherited Create;
497 Init;
498 LoadFromStream(nil);
499 end;
500
Duplicatenull501 function TBGRAAnimatedGif.Duplicate: TBGRAAnimatedGif;
502 var
503 i: integer;
504 begin
505 Result := TBGRAAnimatedGif.Create;
506 setlength(Result.FImages, length(FImages));
507 for i := 0 to high(FImages) do
508 begin
509 Result.FImages[i] := FImages[i];
510 FImages[i].Image.NewReference;
511 end;
512 Result.FWidth := FWidth;
513 Result.FHeight := FHeight;
514 Result.FBackgroundColor := FBackgroundColor;
515 end;
516
AddFramenull517 function TBGRAAnimatedGif.AddFrame(AImage: TFPCustomImage; X, Y: integer;
518 ADelayMs: integer; ADisposeMode: TDisposeMode; AHasLocalPalette: boolean
519 ): integer;
520 begin
521 result := length(FImages);
522 setlength(FImages, length(FImages)+1);
523 if ADelayMs < 0 then ADelayMs:= 0;
524 with FImages[result] do
525 begin
526 Image := TBGRABitmap.Create(AImage);
527 Position := Point(x,y);
528 DelayMs := ADelayMs;
529 HasLocalPalette := AHasLocalPalette;
530 DisposeMode := ADisposeMode;
531 end;
532 inc(FTotalAnimationTime, ADelayMs);
533 end;
534
535 procedure TBGRAAnimatedGif.InsertFrame(AIndex: integer; AImage: TFPCustomImage; X,
536 Y: integer; ADelayMs: integer; ADisposeMode: TDisposeMode;
537 AHasLocalPalette: boolean);
538 var i: integer;
539 begin
540 if (AIndex < 0) or (AIndex > Count) then
541 raise ERangeError.Create('Index out of bounds');
542 setlength(FImages, length(FImages)+1);
543 if ADelayMs < 0 then ADelayMs:= 0;
544 for i := high(FImages) downto AIndex+1 do
545 FImages[i] := FImages[i-1];
546 with FImages[AIndex] do
547 begin
548 Image := TBGRABitmap.Create(AImage);
549 Position := Point(x,y);
550 DelayMs := ADelayMs;
551 HasLocalPalette := AHasLocalPalette;
552 DisposeMode := ADisposeMode;
553 end;
554 inc(FTotalAnimationTime, ADelayMs);
555 end;
556
AddFullFramenull557 function TBGRAAnimatedGif.AddFullFrame(AImage: TFPCustomImage;
558 ADelayMs: integer; AHasLocalPalette: boolean): integer;
559 begin
560 if (AImage.Width <> Width) or (AImage.Height <> Height) then
561 raise exception.Create('Size mismatch');
562 if Count > 0 then
563 FrameDisposeMode[Count-1] := dmErase;
564 result := AddFrame(AImage, 0,0, ADelayMs, dmErase, AHasLocalPalette);
565 end;
566
567 procedure TBGRAAnimatedGif.InsertFullFrame(AIndex: integer;
568 AImage: TFPCustomImage; ADelayMs: integer; AHasLocalPalette: boolean);
569 var nextImage: TBGRABitmap;
570 begin
571 if (AIndex < 0) or (AIndex > Count) then
572 raise ERangeError.Create('Index out of bounds');
573
574 if AIndex = Count then
575 AddFullFrame(AImage, ADelayMs, AHasLocalPalette)
576 else
577 begin
578 //if previous image did not clear up, ensure that
579 //next image will stay the same
580 if (AIndex > 0) and (FrameDisposeMode[AIndex-1] <> dmErase) then
581 begin
582 CurrentImage := AIndex;
583 nextImage := MemBitmap.Duplicate;
584 FrameImagePos[AIndex] := Point(0,0);
585 FrameImage[AIndex] := nextImage;
586 FrameHasLocalPalette[AIndex] := true;
587 FreeAndNil(nextImage);
588
589 FrameDisposeMode[AIndex-1] := dmErase;
590 end;
591
592 InsertFrame(AIndex, AImage, 0,0, ADelayMs, dmErase, AHasLocalPalette);
593 end;
594 end;
595
596 procedure TBGRAAnimatedGif.ReplaceFullFrame(AIndex: integer;
597 AImage: TFPCustomImage; ADelayMs: integer; AHasLocalPalette: boolean);
598 begin
599 DeleteFrame(AIndex, True);
600 if AIndex > 0 then FrameDisposeMode[AIndex-1] := dmErase;
601 InsertFrame(AIndex, AImage, 0,0, ADelayMs, dmErase, AHasLocalPalette);
602 end;
603
604 procedure TBGRAAnimatedGif.DeleteFrame(AIndex: integer;
605 AEnsureNextFrameDoesNotChange: boolean);
606 var
607 nextImage: TBGRABitmap;
608 i: Integer;
609 begin
610 CheckFrameIndex(AIndex);
611
612 //if this frame did not clear up, ensure that
613 //next image will stay the same
614 if AEnsureNextFrameDoesNotChange and
615 ((AIndex < Count-1) and (FrameDisposeMode[AIndex] <> dmErase)) then
616 begin
617 CurrentImage := AIndex+1;
618 nextImage := MemBitmap.Duplicate;
619 FrameImagePos[AIndex+1] := Point(0,0);
620 FrameImage[AIndex+1] := nextImage;
621 FrameHasLocalPalette[AIndex+1] := true;
622 FreeAndNil(nextImage);
623 end;
624
625 dec(FTotalAnimationTime, FImages[AIndex].DelayMs);
626
627 FImages[AIndex].Image.FreeReference;
628 for i := AIndex to Count-2 do
629 FImages[i] := FImages[i+1];
630 SetLength(FImages, Count-1);
631
632 if (CurrentImage >= Count) then
633 CurrentImage := 0;
634 end;
635
636 procedure TBGRAAnimatedGif.LoadFromStream(Stream: TStream);
637 begin
638 LoadFromStream(Stream, maxLongint);
639 end;
640
641 procedure TBGRAAnimatedGif.LoadFromStream(Stream: TStream;
642 AMaxImageCount: integer);
643 var data: TGIFData;
644 i: integer;
645 begin
646 data := GIFLoadFromStream(Stream, AMaxImageCount);
647
648 ClearViewer;
649 Clear;
650 FWidth := data.Width;
651 FHeight := data.Height;
652 FBackgroundColor := data.BackgroundColor;
653 FAspectRatio:= data.AspectRatio;
654 LoopDone := 0;
655 LoopCount := data.LoopCount;
656
657 SetLength(FImages, length(data.Images));
658 FTotalAnimationTime:= 0;
659 for i := 0 to high(FImages) do
660 begin
661 FImages[i] := data.Images[i];
662 inc(FTotalAnimationTime, FImages[i].DelayMs);
663 end;
664 end;
665
666 procedure TBGRAAnimatedGif.LoadFromResource(AFilename: string);
667 var
668 stream: TStream;
669 begin
670 stream := BGRAResource.GetResourceStream(AFilename);
671 try
672 LoadFromStream(stream);
673 finally
674 stream.Free;
675 end;
676 end;
677
678 procedure TBGRAAnimatedGif.SaveToStream(Stream: TStream);
679 begin
680 SaveToStream(Stream, BGRAColorQuantizerFactory, daFloydSteinberg);
681 end;
682
683 procedure TBGRAAnimatedGif.LoadFromFile(const AFilenameUTF8: string);
684 var stream: TFileStreamUTF8;
685 begin
686 stream := TFileStreamUTF8.Create(AFilenameUTF8,fmOpenRead or fmShareDenyWrite);
687 try
688 LoadFromStream(stream);
689 finally
690 Stream.Free;
691 end;
692 end;
693
694 procedure TBGRAAnimatedGif.SaveToFile(const AFilenameUTF8: string);
695 var
696 Stream: TFileStreamUTF8;
697 begin
698 Stream := TFileStreamUTF8.Create(AFilenameUTF8, fmCreate);
699 try
700 SaveToStream(Stream);
701 finally
702 Stream.Free;
703 end;
704 end;
705
706 procedure TBGRAAnimatedGif.Draw(ACanvas: TCanvas; const Rect: TRect);
707 begin
708 if FBackgroundImage <> nil then
709 FreeAndNil(FBackgroundImage);
710 SaveBackgroundOnce(ACanvas, Rect);
711
712 if FPreviousVirtualScreen <> nil then
713 begin
714 FPreviousVirtualScreen.FreeReference;
715 FPreviousVirtualScreen := nil;
716 end;
717
718 Render(Rect.Right - Rect.Left, Rect.Bottom - Rect.Top);
719 FStretchedVirtualScreen.Draw(ACanvas, Rect.Left, Rect.Top, false);
720 FImageChanged := False;
721
722 FPreviousVirtualScreen := TBGRABitmap(FStretchedVirtualScreen.Duplicate);
723 end;
724
TBGRAAnimatedGif.GetEmptynull725 function TBGRAAnimatedGif.GetEmpty: boolean;
726 begin
727 Result := (length(FImages) = 0);
728 end;
729
TBGRAAnimatedGif.GetHeightnull730 function TBGRAAnimatedGif.GetHeight: integer;
731 begin
732 Result := FHeight;
733 end;
734
TBGRAAnimatedGif.GetTransparentnull735 function TBGRAAnimatedGif.GetTransparent: boolean;
736 begin
737 Result := True;
738 end;
739
GetWidthnull740 function TBGRAAnimatedGif.GetWidth: integer;
741 begin
742 Result := FWidth;
743 end;
744
745 procedure TBGRAAnimatedGif.SetHeight(Value: integer);
746 begin
747 //not implemented
748 end;
749
750 procedure TBGRAAnimatedGif.SetTransparent(Value: boolean);
751 begin
752 //not implemented
753 end;
754
755 procedure TBGRAAnimatedGif.SetWidth(Value: integer);
756 begin
757 //not implemented
758 end;
759
760 procedure TBGRAAnimatedGif.ClearViewer;
761 begin
762 FCurrentImage := -1;
763 FWantedImage := -1;
764 FTimeAccumulator := 0;
765
766 if FStretchedVirtualScreen <> nil then
767 FStretchedVirtualScreen.FreeReference;
768 if FPreviousVirtualScreen <> nil then
769 FPreviousVirtualScreen.FreeReference;
770 FInternalVirtualScreen.Free;
771 FRestoreImage.Free;
772 FBackgroundImage.Free;
773
774 FInternalVirtualScreen := nil;
775 FStretchedVirtualScreen := nil;
776 FRestoreImage := nil;
777 FBackgroundImage := nil;
778 FPreviousVirtualScreen := nil;
779
780 FPreviousDisposeMode := dmNone;
781 end;
782
783 procedure TBGRAAnimatedGif.SaveBackgroundOnce(Canvas: TCanvas; ARect: TRect);
784 begin
785 if (FBackgroundImage <> nil) and
786 ((FBackgroundImage.Width <> ARect.Right - ARect.Left) or
787 (FBackgroundImage.Height <> ARect.Bottom - ARect.Top)) then
788 FreeAndNil(FBackgroundImage);
789
790 if (BackgroundMode in [gbmSaveBackgroundOnce, gbmUpdateBackgroundContinuously]) and
791 (FBackgroundImage = nil) then
792 begin
793 FBackgroundImage := TBGRABitmap.Create(ARect.Right - ARect.Left,
794 ARect.Bottom - ARect.Top);
795 FBackgroundImage.GetImageFromCanvas(Canvas, ARect.Left, ARect.Top);
796 end;
797 end;
798
799 procedure TBGRAAnimatedGif.SetCurrentImage(Index: integer);
800 begin
801 if (Index >= 0) and (Index < Length(FImages)) then
802 FWantedImage := Index;
803 end;
804
805 procedure TBGRAAnimatedGif.Clear;
806 var
807 i: integer;
808 begin
809 inherited Clear;
810
811 for i := 0 to Count - 1 do
812 FImages[i].Image.FreeReference;
813 FImages := nil;
814 LoopDone := 0;
815 LoopCount := 0;
816 end;
817
818 destructor TBGRAAnimatedGif.Destroy;
819 begin
820 Clear;
821
822 if FStretchedVirtualScreen <> nil then
823 FStretchedVirtualScreen.FreeReference;
824 if FPreviousVirtualScreen <> nil then
825 FPreviousVirtualScreen.FreeReference;
826 FInternalVirtualScreen.Free;
827 FRestoreImage.Free;
828 FBackgroundImage.Free;
829 inherited Destroy;
830 end;
831
832 procedure TBGRAAnimatedGif.Pause;
833 begin
834 FPaused := True;
835 end;
836
837 procedure TBGRAAnimatedGif.Resume;
838 begin
839 FPaused := False;
840 end;
841
842 procedure TBGRAAnimatedGif.Show(Canvas: TCanvas; ARect: TRect);
843 begin
844 Canvas.StretchDraw(ARect, self);
845 end;
846
847 procedure TBGRAAnimatedGif.Update(Canvas: TCanvas; ARect: TRect);
848 var
849 n: integer;
850 PChangePix, PNewPix, PBackground, PNewBackground: PLongWord;
851 oldpix, newpix, newbackpix: LongWord;
852 NewBackgroundImage: TBGRABitmap;
853 begin
854 if (BackgroundMode = gbmUpdateBackgroundContinuously) and
855 (FBackgroundImage = nil) then
856 BackgroundMode := gbmSaveBackgroundOnce;
857
858 SaveBackgroundOnce(Canvas, ARect);
859
860 case BackgroundMode of
861 gbmSimplePaint:
862 begin
863 UpdateSimple(Canvas, ARect);
864 exit;
865 end;
866 gbmEraseBackground:
867 begin
868 UpdateEraseBackground(Canvas, ARect);
869 exit;
870 end;
871 gbmSaveBackgroundOnce, gbmUpdateBackgroundContinuously:
872 begin
873 if FPreviousVirtualScreen <> nil then
874 begin
875 if (FPreviousVirtualScreen.Width <> ARect.Right - ARect.Left) or
876 (FPreviousVirtualScreen.Height <> ARect.Bottom - ARect.Top) then
877 begin
878 FPreviousVirtualScreen.FreeReference;
879 FPreviousVirtualScreen := nil;
880 end
881 else
882 FPreviousVirtualScreen := TBGRABitmap(FPreviousVirtualScreen.GetUnique);
883 end;
884
885 Render(ARect.Right - ARect.Left, ARect.Bottom - ARect.Top);
886
887 if FImageChanged then
888 begin
889 if BackgroundMode = gbmUpdateBackgroundContinuously then
890 begin
891 NewBackgroundImage :=
892 TBGRABitmap.Create(FStretchedVirtualScreen.Width,
893 FStretchedVirtualScreen.Height);
894 NewBackgroundImage.GetImageFromCanvas(Canvas, ARect.Left, ARect.Top);
895
896 if FPreviousVirtualScreen = nil then
897 begin
898 FPreviousVirtualScreen := TBGRABitmap.Create(FWidth, FHeight);
899 FPreviousVirtualScreen.Fill(BGRAPixelTransparent);
900 end;
901
902 PChangePix := PLongWord(FPreviousVirtualScreen.Data);
903 PNewPix := PLongWord(FStretchedVirtualScreen.Data);
904 PBackground := PLongWord(FBackgroundImage.Data);
905 PNewBackground := PLongWord(NewBackgroundImage.Data);
906 for n := FStretchedVirtualScreen.NbPixels - 1 downto 0 do
907 begin
908 oldpix := PChangePix^;
909
910 if (oldpix and AlphaMask = AlphaMask) then //pixel opaque précédent
911 begin
912 newbackpix := PNewBackground^;
913 if (newbackpix <> oldpix) then //stocke nouveau fond
914 PBackground^ := newbackpix;
915 end;
916
917 newpix := PNewPix^;
918
919 if newpix and AlphaMask = AlphaMask then
920 PChangePix^ := newpix //pixel opaque
921 else if newpix and AlphaMask > 0 then
922 begin
923 PChangePix^ := PBackground^;
924 DrawPixelInlineNoAlphaCheck(PBGRAPixel(PChangePix), PBGRAPixel(@newpix)^);
925 end
926 else if PChangePix^ and AlphaMask <> 0 then
927 PChangePix^ := PBackground^; //efface précédent
928
929 { if newpix and AlphaMask > AlphaLimit then PChangePix^ := newpix or AlphaMask //pixel opaque
930 else if PChangePix^ and AlphaMask <> 0 then PChangePix^ := PBackground^; //efface précédent}
931
932 Inc(PNewPix);
933 Inc(PChangePix);
934 Inc(PBackground);
935 Inc(PNewBackground);
936 end;
937 NewBackgroundImage.Free;
938 FPreviousVirtualScreen.InvalidateBitmap;
939 FPreviousVirtualScreen.Draw(Canvas, ARect.Left, ARect.Top, false);
940 FPreviousVirtualScreen.PutImage(0, 0, FStretchedVirtualScreen, dmSet);
941 end
942 else
943 begin
944 if FPreviousVirtualScreen = nil then
945 begin
946 FStretchedVirtualScreen.Draw(Canvas, ARect.Left, ARect.Top, false);
947 FPreviousVirtualScreen :=
948 TBGRABitmap(FStretchedVirtualScreen.NewReference);
949 end
950 else
951 begin
952 PChangePix := PLongWord(FPreviousVirtualScreen.Data);
953 PNewPix := PLongWord(FStretchedVirtualScreen.Data);
954 PBackground := PLongWord(FBackgroundImage.Data);
955 for n := FStretchedVirtualScreen.NbPixels - 1 downto 0 do
956 begin
957 newpix := PNewPix^;
958
959 if newpix and AlphaMask = AlphaMask then
960 PChangePix^ := newpix //pixel opaque
961 else if newpix and AlphaMask > 0 then
962 begin
963 PChangePix^ := PBackground^;
964 DrawPixelInlineNoAlphaCheck(PBGRAPixel(PChangePix), PBGRAPixel(@newpix)^);
965 end
966 else if PChangePix^ and AlphaMask <> 0 then
967 PChangePix^ := PBackground^; //efface précédent
968
969 { if newpix and AlphaMask > AlphaLimit then PChangePix^ := newpix or AlphaMask //pixel opaque
970 else if PChangePix^ and AlphaMask <> 0 then PChangePix^ := PBackground^; //efface précédent}
971
972 Inc(PNewPix);
973 Inc(PChangePix);
974 Inc(PBackground);
975 end;
976 FPreviousVirtualScreen.InvalidateBitmap;
977 FPreviousVirtualScreen.Draw(Canvas, ARect.Left, ARect.Top, false);
978 FPreviousVirtualScreen.PutImage(0, 0, FStretchedVirtualScreen, dmSet);
979 end;
980 end;
981 FImageChanged := False;
982 end;
983 end;
984 end;
985 end;
986
987 procedure TBGRAAnimatedGif.Hide(Canvas: TCanvas; ARect: TRect);
988 var
989 shape: TBGRABitmap;
990 p, pback: PBGRAPixel;
991 MemEraseColor: TBGRAPixel;
992 n: integer;
993 begin
994 MemEraseColor := ColorToBGRA(EraseColor);
995 if FPreviousVirtualScreen <> nil then
996 begin
997 if (FPreviousVirtualScreen.Width <> ARect.Right - ARect.Left) or
998 (FPreviousVirtualScreen.Height <> ARect.Bottom - ARect.Top) then
999 begin
1000 FPreviousVirtualScreen.FreeReference;
1001 FPreviousVirtualScreen := nil;
1002 end;
1003 end;
1004
1005 case BackgroundMode of
1006 gbmEraseBackground, gbmSimplePaint:
1007 begin
1008 if FPreviousVirtualScreen <> nil then
1009 begin
1010 shape := TBGRABitmap(FPreviousVirtualScreen.Duplicate);
1011 p := shape.Data;
1012 for n := shape.NbPixels - 1 downto 0 do
1013 begin
1014 if p^.alpha <> 0 then
1015 p^ := MemEraseColor
1016 else
1017 p^ := BGRAPixelTransparent;
1018 Inc(p);
1019 end;
1020 shape.Draw(Canvas, ARect.Left, ARect.Top, false);
1021 shape.FreeReference;
1022 end;
1023 end;
1024 gbmSaveBackgroundOnce, gbmUpdateBackgroundContinuously:
1025 begin
1026 if (FPreviousVirtualScreen <> nil) and (FBackgroundImage <> nil) then
1027 begin
1028 shape := TBGRABitmap(FPreviousVirtualScreen.Duplicate);
1029 p := shape.Data;
1030 pback := FBackgroundImage.Data;
1031 for n := shape.NbPixels - 1 downto 0 do
1032 begin
1033 if p^.alpha <> 0 then
1034 p^ := pback^
1035 else
1036 p^ := BGRAPixelTransparent;
1037 Inc(p);
1038 Inc(pback);
1039 end;
1040 shape.Draw(Canvas, ARect.Left, ARect.Top, false);
1041 shape.FreeReference;
1042 end;
1043 end;
1044 end;
1045 end;
1046
1047 procedure TBGRAAnimatedGif.UpdateEraseBackground(Canvas: TCanvas;
1048 ARect: TRect; DrawOnlyIfChanged: boolean);
1049 var
1050 n: integer;
1051 PChangePix, PNewPix: PLongWord;
1052 newpix: LongWord;
1053 MemPixEraseColor: LongWord;
1054 begin
1055 if EraseColor = clNone then
1056 begin
1057 UpdateSimple(Canvas, ARect, DrawOnlyIfChanged);
1058 exit;
1059 end;
1060
1061 if FPreviousVirtualScreen <> nil then
1062 begin
1063 if (FPreviousVirtualScreen.Width <> ARect.Right - ARect.Left) or
1064 (FPreviousVirtualScreen.Height <> ARect.Bottom - ARect.Top) then
1065 begin
1066 FPreviousVirtualScreen.FreeReference;
1067 FPreviousVirtualScreen := nil;
1068 end
1069 else
1070 FPreviousVirtualScreen := TBGRABitmap(FPreviousVirtualScreen.GetUnique);
1071 end;
1072
1073 Render(ARect.Right - ARect.Left, ARect.Bottom - ARect.Top);
1074 if FImageChanged then
1075 begin
1076 PBGRAPixel(@MemPixEraseColor)^ := ColorToBGRA(EraseColor);
1077 if FPreviousVirtualScreen = nil then
1078 begin
1079 FStretchedVirtualScreen.Draw(Canvas, ARect.Left, ARect.Top, false);
1080 FPreviousVirtualScreen := TBGRABitmap(FStretchedVirtualScreen.NewReference);
1081 end
1082 else
1083 begin
1084 PChangePix := PLongWord(FPreviousVirtualScreen.Data);
1085 PNewPix := PLongWord(FStretchedVirtualScreen.Data);
1086 for n := FStretchedVirtualScreen.NbPixels - 1 downto 0 do
1087 begin
1088 newpix := PNewPix^;
1089
1090 if newpix and AlphaMask = AlphaMask then
1091 PChangePix^ := newpix //pixel opaque
1092 else if newpix and AlphaMask > 0 then
1093 begin
1094 PChangePix^ := MemPixEraseColor;
1095 DrawPixelInlineNoAlphaCheck(PBGRAPixel(PChangePix), PBGRAPixel(@newpix)^);
1096 end
1097 else if PChangePix^ and AlphaMask <> 0 then
1098 PChangePix^ := MemPixEraseColor; //efface précédent
1099 { if newpix and AlphaMask > AlphaLimit then PChangePix^ := newpix or AlphaMask //pixel opaque
1100 else if PChangePix^ and AlphaMask <> 0 then PChangePix^ := MemPixEraseColor; //efface précédent}
1101
1102 Inc(PNewPix);
1103 Inc(PChangePix);
1104 end;
1105 FPreviousVirtualScreen.InvalidateBitmap;
1106 FPreviousVirtualScreen.Draw(Canvas, ARect.Left, ARect.Top, false);
1107 FPreviousVirtualScreen.PutImage(0, 0, FStretchedVirtualScreen, dmSet);
1108 end;
1109
1110 FImageChanged := False;
1111 end;
1112 end;
1113
1114 procedure TBGRAAnimatedGif.Init;
1115 begin
1116 BackgroundMode := gbmSaveBackgroundOnce;
1117 LoopCount := 0;
1118 LoopDone := 0;
1119 end;
1120
GetBitmapnull1121 function TBGRAAnimatedGif.GetBitmap: TBitmap;
1122 begin
1123 Render(FWidth, FHeight);
1124 Result := FStretchedVirtualScreen.Bitmap;
1125 end;
1126
GetMemBitmapnull1127 function TBGRAAnimatedGif.GetMemBitmap: TBGRABitmap;
1128 begin
1129 Render(FWidth, FHeight);
1130 Result := FStretchedVirtualScreen;
1131 end;
1132
1133 { TBGRAReaderGIF }
1134
1135 procedure TBGRAReaderGIF.InternalRead(Str: TStream; Img: TFPCustomImage);
1136 var
1137 gif: TBGRAAnimatedGif;
1138 x, y: integer;
1139 Mem: TBGRABitmap;
1140 begin
1141 gif := TBGRAAnimatedGif.Create(Str, 1);
1142 Mem := gif.MemBitmap;
1143 if Img is TBGRABitmap then
1144 begin
1145 TBGRABitmap(Img).Assign(Mem);
1146 end
1147 else
1148 begin
1149 Img.SetSize(gif.Width, gif.Height);
1150 for y := 0 to gif.Height - 1 do
1151 for x := 0 to gif.Width - 1 do
1152 with Mem.GetPixel(x, y) do
1153 Img.Colors[x, y] := FPColor(red * $101, green * $101, blue *
1154 $101, alpha * $101);
1155 end;
1156 gif.Free;
1157 end;
1158
TBGRAReaderGIF.InternalChecknull1159 function TBGRAReaderGIF.InternalCheck(Str: TStream): boolean;
1160 var
1161 GIFSignature: TGIFSignature;
1162 savepos: int64;
1163 begin
1164 savepos := str.Position;
1165 try
1166 fillchar({%H-}GIFSignature, sizeof(GIFSignature), 0);
1167 str.Read(GIFSignature, sizeof(GIFSignature));
1168 if (GIFSignature[1] = 'G') and (GIFSignature[2] = 'I') and
1169 (GIFSignature[3] = 'F') then
1170 begin
1171 Result := True;
1172 end
1173 else
1174 Result := False;
1175 except
1176 on ex: Exception do
1177 Result := False;
1178 end;
1179 str.Position := savepos;
1180 end;
1181
1182 { TBGRAWriterGIF }
1183
1184 procedure TBGRAWriterGIF.InternalWrite(Str: TStream; Img: TFPCustomImage);
1185 var
1186 gif: TBGRAAnimatedGif;
1187 begin
1188 gif := TBGRAAnimatedGif.Create;
1189 try
1190 gif.SetSize(Img.Width,Img.Height);
1191 gif.AddFrame(Img, 0,0,0);
1192 gif.SaveToStream(Str, BGRAColorQuantizerFactory, daFloydSteinberg);
1193 except
1194 on ex: EColorQuantizerMissing do
1195 begin
1196 FreeAndNil(gif);
1197 raise EColorQuantizerMissing.Create('Please define the color quantizer factory. You can do that with the following statements: Uses BGRAPalette, BGRAColorQuantization; BGRAColorQuantizerFactory:= TBGRAColorQuantizer;');
1198 end;
1199 on ex: Exception do
1200 begin
1201 FreeAndNil(gif);
1202 raise ex;
1203 end;
1204 end;
1205 FreeAndNil(gif);
1206 end;
1207
1208 initialization
1209
1210 DefaultBGRAImageReader[ifGif] := TBGRAReaderGIF;
1211 DefaultBGRAImageWriter[ifGif] := TBGRAWriterGIF;
1212
1213 //Free Pascal Image
1214 ImageHandlers.RegisterImageReader('Animated GIF', TBGRAAnimatedGif.GetFileExtensions,
1215 TBGRAReaderGIF);
1216 ImageHandlers.RegisterImageWriter('Animated GIF', TBGRAAnimatedGif.GetFileExtensions,
1217 TBGRAWriterGIF);
1218
1219 {$IFDEF BGRABITMAP_USE_LCL}
1220 //Lazarus Picture
1221 TPicture.RegisterFileFormat(TBGRAAnimatedGif.GetFileExtensions, 'Animated GIF',
1222 TBGRAAnimatedGif);
1223 {$ENDIF}
1224 end.
1225
1226