|
| 1 | +{******************************************************************************} |
| 2 | +{ } |
| 3 | +{ SkAnimatedImageHelper: an helper class for TSkAnimatedImage } |
| 4 | +{ } |
| 5 | +{ Copyright (c) 2022-2023 (Ethea S.r.l.) } |
| 6 | +{ Author: Carlo Barazzetta } |
| 7 | +{ Contributors: } |
| 8 | +{ } |
| 9 | +{ https://github.com/EtheaDev/StyledComponents } |
| 10 | +{ } |
| 11 | +{******************************************************************************} |
| 12 | +{ } |
| 13 | +{ Licensed under the Apache License, Version 2.0 (the "License"); } |
| 14 | +{ you may not use this file except in compliance with the License. } |
| 15 | +{ You may obtain a copy of the License at } |
| 16 | +{ } |
| 17 | +{ http://www.apache.org/licenses/LICENSE-2.0 } |
| 18 | +{ } |
| 19 | +{ Unless required by applicable law or agreed to in writing, software } |
| 20 | +{ distributed under the License is distributed on an "AS IS" BASIS, } |
| 21 | +{ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } |
| 22 | +{ See the License for the specific language governing permissions and } |
| 23 | +{ limitations under the License. } |
| 24 | +{ } |
| 25 | +{******************************************************************************} |
| 26 | +unit Vcl.SkAnimatedImageHelper; |
| 27 | + |
| 28 | +interface |
| 29 | + |
| 30 | +{$INCLUDE StyledComponents.inc} |
| 31 | + |
| 32 | +uses |
| 33 | + Winapi.Windows |
| 34 | + , Winapi.Messages |
| 35 | + , System.Types |
| 36 | + , System.Classes |
| 37 | +(* |
| 38 | + , System.UITypes |
| 39 | + , Vcl.Controls |
| 40 | + , Vcl.ImgList |
| 41 | +*) |
| 42 | + , Vcl.Graphics |
| 43 | + , Vcl.Skia |
| 44 | + ; |
| 45 | + |
| 46 | +type |
| 47 | + TSkAnimatedImageHelper = class helper for TSkAnimatedImage |
| 48 | + strict private |
| 49 | + function AnimationLoaded: Boolean; |
| 50 | + public |
| 51 | + //Initialization functions |
| 52 | + procedure InitAnimation(ALoop, AInverse: Boolean; |
| 53 | + AProgress: Double); |
| 54 | + procedure ClearAnimationData; |
| 55 | + |
| 56 | + function CanPlayAnimation: Boolean; |
| 57 | + function CanResumeAnimation: Boolean; |
| 58 | + function CanPauseAnimation: Boolean; |
| 59 | + function CanStopAnimation: Boolean; |
| 60 | + function AnimationRunning: Boolean; |
| 61 | + function AnimationRunningInverse: Boolean; |
| 62 | + function AnimationRunningNormal: Boolean; |
| 63 | + |
| 64 | + function GetProgress: Double; |
| 65 | + function GetProgressPercentage: SmallInt; |
| 66 | + procedure SetProgress(const AValue: Double); |
| 67 | + procedure SetProgressPercentage(const AValue: SmallInt); |
| 68 | + |
| 69 | + function GetLoop: Boolean; |
| 70 | + procedure SetLoop(const Value: Boolean); |
| 71 | + function GetInverse: Boolean; |
| 72 | + procedure SetInverse(const Value: Boolean); |
| 73 | + |
| 74 | + function GetLottieText: string; |
| 75 | + |
| 76 | + procedure StopAnimation; |
| 77 | + procedure PauseAnimation; |
| 78 | + procedure ResumeAnimation; |
| 79 | + procedure StartAnimation(const AFromBegin: Boolean = True); |
| 80 | + |
| 81 | + procedure RenderTo(ACanvas: TCanvas; ARect: TRectF; |
| 82 | + AProgress: Double; AOpacity: Single); |
| 83 | + end; |
| 84 | + |
| 85 | +implementation |
| 86 | + |
| 87 | +uses |
| 88 | + System.Skia |
| 89 | + , System.SysUtils |
| 90 | + , System.math |
| 91 | + ; |
| 92 | + |
| 93 | +{ TSkAnimatedImageHelper } |
| 94 | + |
| 95 | +function TSkAnimatedImageHelper.AnimationLoaded: Boolean; |
| 96 | +begin |
| 97 | + Result := (Length(Source.Data) > 0) and Assigned(Codec) and |
| 98 | + not Codec.Isstatic; |
| 99 | +end; |
| 100 | + |
| 101 | +procedure TSkAnimatedImageHelper.InitAnimation(ALoop, AInverse: Boolean; |
| 102 | + AProgress: Double); |
| 103 | +begin |
| 104 | + Animation.Loop := ALoop; |
| 105 | + Animation.Inverse := AInverse; |
| 106 | + Animation.Progress := AProgress; |
| 107 | +end; |
| 108 | + |
| 109 | +function TSkAnimatedImageHelper.AnimationRunning: Boolean; |
| 110 | +begin |
| 111 | + Result := AnimationLoaded and Animation.Enabled and Animation.Running; |
| 112 | +end; |
| 113 | + |
| 114 | +function TSkAnimatedImageHelper.AnimationRunningInverse: Boolean; |
| 115 | +begin |
| 116 | + Result := AnimationRunning and Animation.Inverse; |
| 117 | +end; |
| 118 | + |
| 119 | +function TSkAnimatedImageHelper.AnimationRunningNormal: Boolean; |
| 120 | +begin |
| 121 | + Result := AnimationRunning and not Animation.Inverse; |
| 122 | +end; |
| 123 | + |
| 124 | +function TSkAnimatedImageHelper.CanPlayAnimation: Boolean; |
| 125 | +begin |
| 126 | + Result := AnimationLoaded and not Animation.Running; |
| 127 | +end; |
| 128 | + |
| 129 | +function TSkAnimatedImageHelper.CanResumeAnimation: Boolean; |
| 130 | +begin |
| 131 | + Result := AnimationLoaded and not AnimationRunning and |
| 132 | + (Animation.Progress <> 0) and |
| 133 | + (Animation.Loop or (Animation.Progress <> 1)); |
| 134 | +end; |
| 135 | + |
| 136 | +function TSkAnimatedImageHelper.CanPauseAnimation: Boolean; |
| 137 | +begin |
| 138 | + Result := AnimationRunning; |
| 139 | +end; |
| 140 | + |
| 141 | +function TSkAnimatedImageHelper.CanStopAnimation: Boolean; |
| 142 | +begin |
| 143 | + Result := AnimationRunning; |
| 144 | +end; |
| 145 | + |
| 146 | +procedure TSkAnimatedImageHelper.ClearAnimationData; |
| 147 | +begin |
| 148 | + Source.Data := []; |
| 149 | +end; |
| 150 | + |
| 151 | +function TSkAnimatedImageHelper.GetProgressPercentage: SmallInt; |
| 152 | +begin |
| 153 | + if AnimationLoaded then |
| 154 | + Result := Round(inherited Animation.Progress * 100) |
| 155 | + else |
| 156 | + Result := 0; |
| 157 | +end; |
| 158 | + |
| 159 | +procedure TSkAnimatedImageHelper.SetProgress(const AValue: Double); |
| 160 | +var |
| 161 | + LEvent: TNotifyEvent; |
| 162 | +begin |
| 163 | + if Assigned(Animation) then |
| 164 | + begin |
| 165 | + if Assigned(OnAnimationProcess) then |
| 166 | + LEvent := OnAnimationProcess |
| 167 | + else |
| 168 | + LEvent := nil; |
| 169 | + OnAnimationProcess := nil; |
| 170 | + Try |
| 171 | + Animation.Stop; |
| 172 | + Animation.Progress := AValue; |
| 173 | + Finally |
| 174 | + if Assigned(LEvent) then |
| 175 | + OnAnimationProcess := LEvent; |
| 176 | + End; |
| 177 | + end; |
| 178 | +end; |
| 179 | + |
| 180 | +function TSkAnimatedImageHelper.GetLottieText: string; |
| 181 | +var |
| 182 | + LFormat: TFormatInfo; |
| 183 | + LStringStream: TStringStream; |
| 184 | +begin |
| 185 | + Result := ''; |
| 186 | + if AnimationLoaded then |
| 187 | + begin |
| 188 | + if Codec.TryDetectFormat(Source.Data, LFormat) then |
| 189 | + begin |
| 190 | + if SameText(LFormat.Name, 'Lottie') then |
| 191 | + begin |
| 192 | + LStringStream := TStringStream.Create('', TEncoding.UTF8); |
| 193 | + try |
| 194 | + LStringStream.writebuffer(Source.Data[0], length(Source.Data)); |
| 195 | + Result := LStringStream.DataString; |
| 196 | + finally |
| 197 | + LStringStream.Free; |
| 198 | + end; |
| 199 | + end; |
| 200 | + end; |
| 201 | + end; |
| 202 | +end; |
| 203 | + |
| 204 | +function TSkAnimatedImageHelper.GetProgress: Double; |
| 205 | +begin |
| 206 | + if AnimationLoaded then |
| 207 | + Result := inherited Animation.Progress |
| 208 | + else |
| 209 | + Result := 0; |
| 210 | +end; |
| 211 | + |
| 212 | +procedure TSkAnimatedImageHelper.SetProgressPercentage(const AValue: SmallInt); |
| 213 | +begin |
| 214 | + if Assigned(Animation) then |
| 215 | + Animation.Progress := AValue / 100; |
| 216 | +end; |
| 217 | + |
| 218 | +function TSkAnimatedImageHelper.GetLoop: Boolean; |
| 219 | +begin |
| 220 | + Result := Assigned(Animation) and Animation.Loop; |
| 221 | +end; |
| 222 | + |
| 223 | +procedure TSkAnimatedImageHelper.SetLoop(const Value: Boolean); |
| 224 | +begin |
| 225 | + Animation.Loop := Value; |
| 226 | +end; |
| 227 | + |
| 228 | +function TSkAnimatedImageHelper.GetInverse: Boolean; |
| 229 | +begin |
| 230 | + Result := Assigned(Animation) and Animation.Inverse; |
| 231 | +end; |
| 232 | + |
| 233 | +procedure TSkAnimatedImageHelper.SetInverse(const Value: Boolean); |
| 234 | +begin |
| 235 | + if Assigned(Animation) then |
| 236 | + Animation.Inverse := Value; |
| 237 | +end; |
| 238 | + |
| 239 | + |
| 240 | +procedure TSkAnimatedImageHelper.StopAnimation; |
| 241 | +begin |
| 242 | + if Assigned(Animation) then |
| 243 | + Animation.Stop; |
| 244 | +end; |
| 245 | + |
| 246 | +procedure TSkAnimatedImageHelper.PauseAnimation; |
| 247 | +begin |
| 248 | + if Assigned(Animation) then |
| 249 | + Animation.StopAtCurrent; |
| 250 | +end; |
| 251 | + |
| 252 | +procedure TSkAnimatedImageHelper.ResumeAnimation; |
| 253 | +begin |
| 254 | + if Assigned(Animation) then |
| 255 | + begin |
| 256 | + Animation.StartFromCurrent := Animation.Progress <> 1; |
| 257 | + Animation.start; |
| 258 | + end; |
| 259 | +end; |
| 260 | + |
| 261 | + |
| 262 | +procedure TSkAnimatedImageHelper.StartAnimation(const AFromBegin: Boolean = True); |
| 263 | +begin |
| 264 | + if Assigned(Animation) then |
| 265 | + begin |
| 266 | + Animation.StartFromCurrent := not AFromBegin; |
| 267 | + if AFromBegin then |
| 268 | + begin |
| 269 | + if Animation.Inverse then |
| 270 | + inherited Animation.Progress := 1 |
| 271 | + else |
| 272 | + inherited Animation.Progress := 0; |
| 273 | + end |
| 274 | + else |
| 275 | + begin |
| 276 | + if not Animation.Inverse and (Animation.Progress = 1) then |
| 277 | + inherited Animation.Progress := 0 |
| 278 | + else if Animation.Inverse and (Animation.Progress = 0) then |
| 279 | + inherited Animation.Progress := 1; |
| 280 | + end; |
| 281 | + if AnimationLoaded and not Animation.Running then |
| 282 | + Animation.Start; |
| 283 | + end; |
| 284 | +end; |
| 285 | + |
| 286 | +procedure TSkAnimatedImageHelper.RenderTo(ACanvas: TCanvas; ARect: TRectF; |
| 287 | + AProgress: Double; AOpacity: Single); |
| 288 | +var |
| 289 | + LBitmap: TBitmap; |
| 290 | + LRect: TRectF; |
| 291 | + LTop, LLeft, LWidth, LHeight: Integer; |
| 292 | +begin |
| 293 | + ACanvas.Lock; |
| 294 | + try |
| 295 | + Animation.Progress := AProgress; |
| 296 | + LTop := Round(ARect.Top); |
| 297 | + LLeft := Round(ARect.Left); |
| 298 | + LWidth := Round(ARect.Width); |
| 299 | + LHeight := Round(ARect.Height); |
| 300 | + LBitmap := TBitmap.Create; |
| 301 | + try |
| 302 | + LBitmap.PixelFormat := pf32bit; |
| 303 | + LBitmap.AlphaFormat := afPremultiplied; |
| 304 | + LBitmap.SetSize(LWidth, LHeight); |
| 305 | + LRect := TRect.Create(0,0,LWidth, LHeight); |
| 306 | + LBitmap.SkiaDraw( |
| 307 | + procedure (const ASkCanvas: ISkCanvas) |
| 308 | + begin |
| 309 | + RenderFrame(ASkCanvas, LRect, AProgress, AOpacity); |
| 310 | + end |
| 311 | + ); |
| 312 | + ACanvas.Draw(LTop, LLeft, LBitmap); |
| 313 | + finally |
| 314 | + LBitmap.Free; |
| 315 | + end; |
| 316 | + finally |
| 317 | + ACanvas.Unlock; |
| 318 | + end; |
| 319 | +end; |
| 320 | + |
| 321 | +end. |
0 commit comments