-
Notifications
You must be signed in to change notification settings - Fork 19
/
cGraphGif.pas
261 lines (196 loc) · 6.53 KB
/
cGraphGif.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
UNIT cGraphGif;
{=============================================================================================================
Gabriel Moraru
2024.05
See Copyright.txt
--------------------------------------------------------------------------------------------------------------
Basic GIF functions:
ExtractFrame
FrameCount
FrameDelay
Uses TGIFRenderer as gif decoder.
External dependencies:
* GifParser.pas
Also see:
BioniX VCL\cFrameServerGIF.pas
Also see:
Play GIF image inside TImage: https://www.youtube.com/watch?v=eHw7SvimazM
Painting gif frames: http://www.delphigroups.info/2/9/322088.html
https://stackoverflow.com/questions/44332339/retrieve-the-first-frame-from-a-large-gif-in-optimal-time/44342164#44342164
Tester:
See cVideoAnimator.pas
100mb Animated Elementalist Lux Desktop Background.gif = 4.1s
-----------------------------------------------------------------------------------------------------------------------}
INTERFACE
USES
System.SysUtils, Vcl.Graphics, Vcl.Imaging.GIFImg;
TYPE
TGifLoader = class(TObject)
private
FileName: string;
GIFImg : TGIFImage;
Renderer: TGIFRenderer;
public
{ Output }
FrameDelay: Integer;
FrameCount: Cardinal;
constructor Create;
destructor Destroy; override;
{ Utils }
function Open(aFileName: string): Boolean;
function SaveFrames(OutputFolder: string): Boolean;
procedure SaveFrame (Frame: TBitmap; FrameNo: Integer; OutputFolder: string);
function ExtractFrame(FrameNo: Cardinal): TBitmap;
end;
function IsAnimatedGif (CONST FileName: string): Integer;
function IsAnimated (CONST AGraphFile: string): Boolean;
function ExtractMiddleFrame(CONST FileName: string; OUT FrameCount: Cardinal): TBitmap;
IMPLEMENTATION
USES
GifParser, ccCore, cbDialogs, cbAppData, {cbINIFile,} ccIO;
constructor TGifLoader.Create;
begin
inherited Create;
GIFImg:= TGIFImage.Create;
end;
destructor TGifLoader.Destroy;
begin
FreeAndNil(GIFImg);
FreeAndNil(Renderer);
inherited Destroy; { First stop the timer }
end;
function TGifLoader.Open(aFileName: string): Boolean; { GIF frames are stored in RAM or to disk }
begin
FrameCount:= 0;
Result := TRUE;
FileName := aFileName;
{ Load GIF }
TRY
GIFImg.Animate := FALSE; { This is set to False by default anyway! }
GIFImg.LoadFromFile(FileName);
EXCEPT
on E: Exception do
begin
AppData.LogError(E.ClassName+': '+ E.Message + ' - '+ FileName);
EXIT(FALSE); { Do not crash on failure }
end;
END;
{ STATIC GIF? }
FrameCount:= GIFImg.Images.Count;
if FrameCount <= 1 then
begin
AppData.LogError('This is not an animated GIF: '+ FileName);
EXIT(FALSE);
end;
{ GIF render }
FreeAndNil(Renderer); { Destroy it so we can created it with a new parameter }
Renderer:= TGIFRenderer.Create(GIFImg);
Renderer.Animate:= TRUE; { THIS IS FUCKING IMPORTANT! Animate must be true! Details: http://stackoverflow.com/questions/36444024/how-to-extract-frames-from-this-gif-image-access-violation-in-tgifrenderer-dra/36468732#36468732 }
{ Obtain frame delay }
VAR TempBMP:= TBitmap.Create;
TRY
TempBMP.SetSize(GIFImg.Width, GIFImg.Height);
Renderer.Draw(TempBMP.Canvas, TempBMP.Canvas.ClipRect); { THIS IS FUCKING IMPORTANT! WE NEED TO PAINT FIRST FRAME OTHERWIESE WE DON'T FET A VALID d FrameDelay }
Renderer.FrameIndex:= 0; { We return to the first frame }
FrameDelay:= Renderer.FrameDelay;
FINALLY
FreeAndNil(TempBMP);
END;
end;
{todo 5: save as PNG }
procedure TGifLoader.SaveFrame(Frame: TBitmap; FrameNo: Integer; OutputFolder: string); { Save frame to disk }
VAR
CurFile, sCounter: string;
begin
sCounter:= ccCore.LeadingZeros(IntToStr(FrameNo), 6);
CurFile:= OutputFolder+ ExtractOnlyName(FileName)+ sCounter+ '.bmp';
Frame.SaveToFile(CurFile);
end;
function TGifLoader.SaveFrames(OutputFolder: string): Boolean; { Stand alone function }
VAR
i: Integer;
BMP: TBitmap;
begin
Result:= FrameCount > 1; //del Result:= VideoOpened;
if Result then
begin
BMP:= TBitmap.Create;
TRY
BMP.SetSize(GIFImg.Width, GIFImg.Height);
{ Save each frame to disk }
for i:= 0 to FrameCount-1 DO
begin
if Renderer.Frame.Empty then Continue;
Renderer.Draw(BMP.Canvas, BMP.Canvas.ClipRect); { Todo: do zoom here! }
Renderer.NextFrame;
SaveFrame(BMP, i, OutputFolder); { Zoom }
end;
{ Check }
if FrameCount < 1
then AppData.LogWarn('AVI frame count mismatch!');
FINALLY
FreeAndNil(BMP);
END;
end
end;
function TGifLoader.ExtractFrame(FrameNo: Cardinal): TBitmap;
begin
if FrameNo >= FrameCount then
begin
MesajWarning('Invalid frame number. Total frames in this GIF: '+ IntToStr(FrameCount));
EXIT(NIL);
end;
Assert(Renderer.Frame <> NIL, 'No frame in renderer!');
if Renderer.Frame.Empty
then EXIT(NIL);
Result:= TBitmap.Create;
Result.SetSize(GIFImg.Width, GIFImg.Height);
Renderer.FrameIndex:= FrameNo; { Remember to go back to frame zero if neccessary }
Renderer.Draw(Result.Canvas, Result.Canvas.ClipRect);
end;
{-------------------------------------------------------------------------------------------------------------
Returns true in the input file is a movie or an animated gif (returns false for static GIFs).
https://stackoverflow.com/questions/59010649/how-to-detect-animated-gif
100mb Animated Elementalist Lux Desktop Background.gif = 4.1s
Tester:
c:\Myprojects\Project Testers\gr GIF frame counter\Tester.dpr
-------------------------------------------------------------------------------------------------------------}
function IsAnimated(CONST AGraphFile: string): Boolean;
VAR IsAnimGif: Boolean;
begin
if IsGIF(AGraphFile)
then IsAnimGif := IsAnimatedGif(AGraphFile) > 1
else IsAnimGif := FALSE;
Result:= IsAnimGif OR ccIO.IsVideo(AGraphFile);
end;
function IsAnimatedGif(CONST FileName: string): integer;
VAR
GIFImg: TGifReader;
begin
GIFImg := TGifReader.Create;
TRY
GIFImg.Read(FileName);
Result:= GIFImg.FrameIndex; //GifFrameList.Count;
FINALLY
FreeAndNil(GIFImg);
END;
end;
function ExtractMiddleFrame(CONST FileName: string; OUT FrameCount: Cardinal): TBitmap;
VAR
MidFrame: Integer;
Gif: TGifLoader;
begin
Result:= NIL;
Gif:= TGifLoader.Create;
TRY
if Gif.Open(FileName) then
begin
FrameCount:= Gif.FrameCount;
MidFrame:= FrameCount DIV 2;
Result:= Gif.ExtractFrame(MidFrame);
end;
FINALLY
FreeAndNil(Gif);
END;
end;
end.