{ Apophysis AV "Phoenix Edition" Copyright (C) 2021-2022 Alice V. Koryagina } unit Animate; interface uses Windows, Messages, SysUtils, Classes, Graphics, StdCtrls, ComCtrls, ExtCtrls, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Samples.Spin, Vcl.Buttons, Menus, ControlPoint, RenderingInterface, Translation; type TAnimateForm = class(TForm) seFPS: TSpinEdit; seDuration: TSpinEdit; edOutFlame: TEdit; btSetFlame: TSpeedButton; gbOutput: TGroupBox; pnlFPS: TPanel; pnlDuration: TPanel; pnlOutFlame: TPanel; btSaveAnimation: TButton; pnlFrameWidth: TPanel; pnlFrameHeight: TPanel; seFrameWidth: TSpinEdit; seFrameHeight: TSpinEdit; pnlPrefix: TPanel; edPrefix: TEdit; cbAspectRatio: TComboBox; pnlRatio: TPanel; AnimPages: TPageControl; tsSettings: TTabSheet; chkListFlames: TCheckBox; AnimStatus: TStatusBar; gbFrame: TGroupBox; tsAnimation: TTabSheet; gbAnimType: TGroupBox; cbAnimType: TComboBox; pnlAnimType: TPanel; pnlStartFlame: TPanel; cbFlames: TComboBox; pnlEndFlame: TPanel; cbFlamesTo: TComboBox; gbPreview: TGroupBox; AnimPreview: TImage; btClose: TButton; chkResetLocation: TCheckBox; chkRender: TCheckBox; QualityPopup: TPopupMenu; mnuLowQuality: TMenuItem; mnuMediumQuality: TMenuItem; mnuHighQuality: TMenuItem; pnlFrameExt: TPanel; cbFrameExt: TComboBox; gbFrameQuality: TGroupBox; pnlDensity: TPanel; pnlFilter: TPanel; pnlOversample: TPanel; txtDensity: TComboBox; txtFilterRadius: TEdit; sbFilterRadius: TSpinButton; seOversample: TSpinEdit; lbSeconds: TLabel; pnlPreview: TPanel; tbPlay: TSpeedButton; tbStop: TSpeedButton; chkInvertBG: TCheckBox; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure btSaveAnimationClick(Sender: TObject); procedure FormShow(Sender: TObject); procedure btSetFlameClick(Sender: TObject); procedure cbAspectRatioChange(Sender: TObject); procedure seFrameWidthChange(Sender: TObject); procedure seFrameHeightChange(Sender: TObject); procedure cbFlamesChange(Sender: TObject); procedure cbAnimTypeChange(Sender: TObject); procedure tbPlayClick(Sender: TObject); procedure btCloseClick(Sender: TObject); procedure mnuPreviewQualityClick(Sender: TObject); procedure chkRenderClick(Sender: TObject); procedure sbFilterRadiusDownClick(Sender: TObject); procedure sbFilterRadiusUpClick(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure tbStopClick(Sender: TObject); private { Private declarations } totFrames: integer; FrameWidth, FrameHeight: integer; FrameRatio: double; AnimPrefix, SaveAnimPath: string; AnimType: shortint; AnimCp, PreviewCp: TControlPoint; PreviewDensity: double; AnimRender: TRenderer; AnimProc, DrawPreviewProc: TProc; StatusGenerating, StatusFinished: string; CurrentFlame: string; StopAnimate: boolean; StartBG: array [0..2] of byte; procedure ARotateFlame; procedure ARotateReference; procedure ARotate3DCamera; procedure ARotateGradient; procedure ARotateHue; procedure AMorphing; procedure LerpBG(const i: integer); procedure DoAnimate; procedure FillFlameList; procedure ChangeControlState(const activate: boolean); public { Public declarations } procedure UpdateControls; end; var AnimateForm: TAnimateForm; implementation {$R *.dfm} uses Main, Editor, Global, XForm, XFormMan, CMap, FormRender, Registry; procedure TAnimateForm.btCloseClick(Sender: TObject); begin AnimRender.Stop; tbStopClick(Sender); Close; end; procedure TAnimateForm.btSaveAnimationClick(Sender: TObject); var i: word; FrameFolder: string; begin // AV: first we must check the output directory SaveAnimPath := edOutFlame.Text; if SaveAnimPath = '' then begin Application.MessageBox(PChar(TextByKey('render-status-nofilename')), ApophysisSVN, 48); exit; end; if not DirectoryExists(ExtractFilePath(SaveAnimPath)) then raise Exception.Create(TextByKey('render-status-pathdoesnotexist')); // AV if FileExists(SaveAnimPath) then begin if Application.MessageBox(PChar( Format(TextByKey('render-status-fileexists-message1'),[SaveAnimPath]) + #13#10 + TextByKey('render-status-fileexists-message2')), ApophysisSVN, 52) = ID_NO then exit; DeleteFile(SaveAnimPath); end; AnimPrefix := edPrefix.Text; cbFlamesChange(Sender); AnimCp.AdjustScale(FrameWidth, FrameHeight); StopAnimate := False; ChangeControlState(false); if chkInvertBG.Checked then for i := 0 to 2 do StartBG[i] := AnimCp.background[i]; AnimProc := procedure begin MainForm.SaveXMLFlame(AnimCp, AnimCp.name, SaveAnimPath); end; DoAnimate; if StopAnimate then begin AnimStatus.SimpleText := TextByKey('animate-status-stopped'); exit; end else AnimStatus.SimpleText := StatusFinished; ChangeControlState(true); if FileExists(SaveAnimPath) then begin if chkListFlames.Checked then begin OpenFile := SaveAnimPath; OpenFileType := ftXML; ListXML(SaveAnimPath, 1); end; if chkRender.Checked then begin if Assigned(RenderForm.Renderer) then if Application.MessageBox(PChar(TextByKey('render-status-confirmstop')), ApophysisSVN, 36) = ID_NO then exit; if Assigned(RenderForm.Renderer) then RenderForm.Renderer.Terminate; if Assigned(RenderForm.Renderer) then RenderForm.Renderer.WaitFor; // hmm #1 RenderForm.ResetControls; RenderForm.bRenderAll := true; if Assigned(RenderForm.Renderer) then RenderForm.Renderer.WaitFor; // hmm #2 RenderForm.RenderFlameFile := SaveAnimPath; SetLength(RenderForm.FlameNames, totFrames); for i := 0 to totFrames - 1 do RenderForm.FlameNames[i] := AnimPrefix + Format('%.3d', [i]); if CreateAnimFolder then begin FrameFolder := RemoveExt(SaveAnimPath) + '\'; CreateDir(FrameFolder); // create a separate folder for flames end else FrameFolder := ExtractFilePath(SaveAnimPath); RenderForm.Filename := FrameFolder + AnimCp.name + cbFrameExt.Items[cbFrameExt.ItemIndex]; renderFileFormat := cbFrameExt.ItemIndex + 1; renderOversample := seOversample.Value; renderFilterRadius := StrToFloat(txtFilterRadius.Text); renderDensity := StrToFloat(txtDensity.Text); renderWidth := seFrameWidth.Value; renderHeight := seFrameHeight.Value; SaveInFlame := False; // all flames are already saved RenderForm.Show; self.Close; RenderForm.btnRenderClick(Sender); end; end; end; procedure TAnimateForm.btSetFlameClick(Sender: TObject); begin with MainForm.SaveDialog do begin Filter := TextByKey('common-filter-flamefiles') + '|*.flame;*.xml|' + TextByKey('common-filter-allfiles') + '|*.*'; InitialDir := ParamFolder; FileName := ExtractFileName(edOutFlame.Text); if Execute then begin if ExtractFileExt(FileName) = '' then FileName := FileName + '.flame'; edOutFlame.Text := FileName; end; end; end; procedure TAnimateForm.cbAnimTypeChange(Sender: TObject); begin AnimType := cbAnimType.ItemIndex; if AnimType < 5 then begin cbFlamesTo.ItemIndex := cbFlames.ItemIndex; cbFlamesTo.Enabled := False; end else begin cbFlamesTo.Enabled := True; end; end; procedure TAnimateForm.cbAspectRatioChange(Sender: TObject); var r: double; begin case cbAspectRatio.ItemIndex of 0, 1: FrameRatio := FrameWidth / FrameHeight; 2: FrameRatio := 1.5; 3: FrameRatio := 4 / 3; 4: FrameRatio := 1.25; 5: FrameRatio := 16 / 9; 6: FrameRatio := 1.6; 7: FrameRatio := 21 / 9; end; if FrameRatio > (pnlPreview.Width / pnlPreview.Height) then begin AnimPreview.Width := pnlPreview.Width; r := FrameWidth / AnimPreview.Width; AnimPreview.Height := round(FrameHeight / r); AnimPreview.Left := 0; AnimPreview.Top := (pnlPreview.Height - AnimPreview.Height) shr 1; end else begin AnimPreview.Height := pnlPreview.Height; r := FrameHeight / AnimPreview.height; AnimPreview.Width := round(FrameWidth / r); AnimPreview.Top := 0; AnimPreview.Left := (pnlPreview.Width - AnimPreview.Width) shr 1; end; end; procedure TAnimateForm.cbFlamesChange(Sender: TObject); var flameXML: string; i: integer; begin i := cbFlames.ItemIndex; if i < 0 then exit; if i < (cbFlames.Items.Count - 1) then begin flameXML := LoadXMLFlameText(Openfile, cbFlames.Items[i]); MainForm.ParseXML(AnimCp, flameXML, true); end else AnimCp.Copy(MainCp); seFrameWidth.Value := AnimCp.Width; seFrameHeight.Value := AnimCp.Height; cbAspectRatioChange(Sender); DrawPreviewProc; end; procedure TAnimateForm.chkRenderClick(Sender: TObject); begin gbFrameQuality.Enabled := chkRender.Checked; cbFrameExt.Enabled := chkRender.Checked; end; procedure TAnimateForm.FillFlameList; var FItem: TListItem; begin cbFlames.Clear; for FItem in MainForm.ListView1.Items do cbFlames.AddItem(FItem.Caption, nil); cbFlames.AddItem(MainCp.name + CurrentFlame, nil); cbFlames.ItemIndex := cbFlames.Items.Count - 1; cbFlamesTo.Items.Assign(cbFlames.Items); cbFlamesTo.ItemIndex := cbFlames.ItemIndex; end; procedure TAnimateForm.LerpBG(const i: integer); var k, k1: double; c: byte; begin //if (totFrames <= 1) then exit; k := i / (totFrames - 1); k1 := (1 - k); for c := 0 to 2 do AnimCp.background[c] := Round(k1 * StartBG[c] + k * (255 - StartBG[c])); end; procedure TAnimateForm.ARotateFlame; var j: integer; i, nx: smallint; rstep: double; Triangles: TTriangles; fx: TXForm; begin rstep := 2 * pi / totFrames; nx := AnimCp.NumXForms; fx := TXForm.Create; fx.Assign(AnimCp.xform[nx]); AnimCp.TrianglesFromCp(Triangles); for j := 0 to totFrames - 1 do begin AnimStatus.SimpleText := Format(StatusGenerating, [j+1, totFrames]); for i := -1 to nx - 1 do Triangles[i] := RotateTriangle(Triangles[i], rstep * j); AnimCp.GetFromTriangles(Triangles, nx); AnimCp.xform[nx].Assign(fx); if chkResetLocation.Checked then AnimCp.CalcBoundbox; if chkInvertBG.Checked then LerpBG(j); AnimCp.name := AnimPrefix + Format('%.3d', [j]); AnimProc; if StopAnimate then break; end; fx.Free; end; procedure TAnimateForm.ARotateReference; var j, nx: integer; rstep: double; Triangles: TTriangles; fx: TXForm; begin rstep := 2 * pi / totFrames; nx := AnimCp.NumXForms; fx := TXForm.Create; fx.Assign(AnimCp.xform[nx]); AnimCp.TrianglesFromCp(Triangles); for j := 0 to totFrames - 1 do begin AnimStatus.SimpleText := Format(StatusGenerating, [j+1, totFrames]); Triangles[-1] := RotateTriangle(Triangles[-1], rstep * j); AnimCp.GetFromTriangles(Triangles, nx); AnimCp.xform[nx].Assign(fx); if chkResetLocation.Checked then AnimCp.CalcBoundbox; if chkInvertBG.Checked then LerpBG(j); AnimCp.name := AnimPrefix + Format('%.3d', [j]); AnimProc; if StopAnimate then break; end; fx.Free; end; procedure TAnimateForm.ARotate3DCamera; var j: integer; rstep: double; begin rstep := 2 * pi / totFrames; for j := 0 to totFrames - 1 do begin AnimStatus.SimpleText := Format(StatusGenerating, [j+1, totFrames]); AnimCp.cameraPitch := AnimCp.cameraPitch + rstep; AnimCp.cameraYaw := AnimCp.cameraYaw + rstep; AnimCp.cameraRoll := AnimCp.cameraRoll + rstep; if chkResetLocation.Checked then AnimCp.CalcBoundbox; if chkInvertBG.Checked then LerpBG(j); AnimCp.name := AnimPrefix + Format('%.3d', [j]); AnimProc; if StopAnimate then break; end; end; procedure TAnimateForm.ARotateGradient; var i, j, n: integer; rstep: double; SourceMap: TColorMap; begin SourceMap := AnimCp.cmap; rstep := 256 / totFrames; for j := 0 to totFrames - 1 do begin AnimStatus.SimpleText := Format(StatusGenerating, [j+1, totFrames]); for i := 0 to 255 do begin n := (256 + i - Round(rstep * j)) mod 256; AnimCp.cmap[i,0] := SourceMap[n,0]; AnimCp.cmap[i,1] := SourceMap[n,1]; AnimCp.cmap[i,2] := SourceMap[n,2]; end; if chkInvertBG.Checked then LerpBG(j); AnimCp.name := AnimPrefix + Format('%.3d', [j]); AnimProc; if StopAnimate then break; end; end; procedure TAnimateForm.ARotateHue; var j: integer; h: double; begin h := 1 / totFrames; for j := 0 to totFrames - 1 do begin AnimStatus.SimpleText := Format(StatusGenerating, [j+1, totFrames]); AnimCp.hue_rotation := h; RotateCMapHue(AnimCp); AnimCp.hue_rotation := 1; if chkInvertBG.Checked then LerpBG(j); AnimCp.name := AnimPrefix + Format('%.3d', [j]); AnimProc; if StopAnimate then break; end; end; procedure TAnimateForm.AMorphing; var j: integer; t: smallint; SourceCp, TargetCp: TControlPoint; flameXML: string; begin j := cbFlamesTo.ItemIndex; if j = cbFlames.ItemIndex then begin Application.MessageBox(PChar(TextByKey('animate-status-changeflame')), ApophysisSVN, 48); exit; end; SourceCp := AnimCp.Clone; SourceCp.time := 0; if j < (cbFlamesTo.Items.Count - 1) then begin TargetCp := TControlPoint.Create; flameXML := LoadXMLFlameText(Openfile, cbFlamesTo.Items[j]); MainForm.ParseXML(TargetCp, flameXML, true); end else TargetCp := MainCp.Clone; TargetCp.time := totFrames - 1; PrepareToInterpolation(SourceCp, TargetCp); // adjust non-common parameters if chkInvertBG.Checked then for j := 0 to 2 do // invert target background TargetCp.background[j] := 255 - SourceCp.background[j]; t := AnimType - 5; // interpolation type try for j := 0 to totFrames - 1 do begin AnimStatus.SimpleText := Format(StatusGenerating, [j+1, totFrames]); AnimCp.InterpolateAll(SourceCp, TargetCp, j, t); if chkResetLocation.Checked then AnimCp.CalcBoundbox; AnimCp.name := AnimPrefix + Format('%.3d', [j]); AnimProc; if StopAnimate then break; end; finally SourceCp.Free; TargetCp.Free; end; end; procedure TAnimateForm.DoAnimate; begin totFrames := seFPS.Value * seDuration.Value; try case AnimType of 0: ARotateFlame; 1: ARotateReference; 2: ARotate3DCamera; 3: ARotateGradient; 4: ARotateHue; 5..8: AMorphing; end; except // TODO end; end; procedure TAnimateForm.FormClose(Sender: TObject; var Action: TCloseAction); var Registry: TRegistry; begin { Write position to registry } Registry := TRegistry.Create; try Registry.RootKey := HKEY_CURRENT_USER; if Registry.OpenKey('\Software\' + APP_NAME + '\Forms\Animate', True) then begin Registry.WriteInteger('Top', self.Top); Registry.WriteInteger('Left', self.Left); Registry.WriteBool('ResetLocation', chkResetLocation.Checked); Registry.WriteBool('ListFlames', chkListFlames.Checked); Registry.WriteBool('RenderFrames', chkRender.Checked); Registry.WriteInteger('AnimationType', cbAnimType.ItemIndex); end; finally Registry.Free; end; end; procedure TAnimateForm.FormCreate(Sender: TObject); begin self.Caption := TextByKey('animate-title'); tsSettings.Caption := TextByKey('animate-general'); tsAnimation.Caption := TextByKey('animate-animation'); gbOutput.Caption := TextByKey('animate-output'); pnlOutFlame.Caption := TextByKey('animate-outflame'); pnlPrefix.Caption := TextByKey('animate-prefix'); gbPreview.Caption := TextByKey('animate-preview'); pnlFrameExt.Caption := TextByKey('animate-graphicext'); pnlDuration.Caption := TextByKey('animate-duration'); lbSeconds.Caption := TextByKey('common-seconds'); pnlFPS.Caption := TextByKey('animate-fps'); gbFrame.Caption := TextByKey('animate-frame'); pnlFrameWidth.Caption := TextByKey('common-width'); pnlFrameHeight.Caption := TextByKey('common-height'); pnlRatio.Caption := TextByKey('adjustment-tab-size-ratio'); cbAspectRatio.Items[0] := TextByKey('adjustment-tab-size-custom'); cbAspectRatio.Items[1] := TextByKey('common-keepaspect'); cbAspectRatio.ItemIndex := 1; // keep current aspect ratio gbFrameQuality.Caption := TextByKey('common-quality'); pnlFilter.Caption := TextByKey('common-filterradius'); pnlDensity.Caption := TextByKey('common-density'); pnlOversample.Caption := TextByKey('common-oversample'); gbAnimType.Caption := TextByKey('animate-parameters'); pnlAnimType.Caption := TextByKey('animate-type'); chkListFlames.Caption := TextByKey('animate-showframes'); chkRender.Caption := TextByKey('animate-render'); chkResetLocation.Caption := TextByKey('animate-resetlocation'); chkInvertBG.Caption := TextByKey('animate-invertbg'); tbPlay.Caption := TextByKey('common-start'); btSaveAnimation.Caption := TextByKey('animate-save'); btClose.Caption := TextByKey('common-close'); btSetFlame.Hint := TextByKey('common-browse'); StatusGenerating := TextByKey('animate-status-generating'); StatusFinished := TextByKey('animate-status-finished'); CurrentFlame := #32 + TextByKey('animate-currentflame'); pnlStartFlame.Caption := TextByKey('animate-initflame'); pnlEndFlame.Caption := TextByKey('animate-finalflame'); tbPlay.Hint := TextByKey('animate-playhint'); tbStop.Hint := TextByKey('animate-stophint'); tbStop.Caption := TextByKey('animate-stop'); btSaveAnimation.Hint := TextByKey('animate-savehint'); cbAnimType.Items[0] := TextByKey('animate-kind-rotateflame'); cbAnimType.Items[1] := TextByKey('animate-kind-rotatereference'); cbAnimType.Items[2] := TextByKey('animate-kind-rotatecamera'); cbAnimType.Items[3] := TextByKey('animate-kind-rotatepalette'); cbAnimType.Items[4] := TextByKey('animate-kind-rotatehue'); cbAnimType.Items[5] := TextByKey('animate-kind-morph1'); cbAnimType.Items[6] := TextByKey('animate-kind-morph2'); cbAnimType.Items[7] := TextByKey('animate-kind-morph3'); cbAnimType.Items[8] := TextByKey('animate-kind-morph4'); cbAnimType.ItemIndex := 0; mnuLowQuality.Caption := TextByKey('common-lowquality'); mnuMediumQuality.Caption := TextByKey('common-mediumquality'); mnuHighQuality.Caption := TextByKey('common-highquality'); case AnimPrevQual of 0: begin PreviewDensity := prevLowQuality; mnuLowQuality.Checked := True; end; 1: begin PreviewDensity := prevMediumQuality; mnuMediumQuality.Checked := True; end; 2: begin PreviewDensity := prevHighQuality; mnuHighQuality.Checked := True; end; end; AnimCp := TControlPoint.Create; PreviewCp := TControlPoint.Create; AnimRender := TRenderer.Create; DrawPreviewProc := procedure begin PreviewCp.Copy(AnimCp); PreviewCp.sample_density := PreviewDensity; PreviewCp.AdjustScale(AnimPreview.Width, AnimPreview.Height); AnimRender.Stop; AnimRender.SetCP(PreviewCp); AnimRender.Render; AnimPreview.Picture.Graphic := AnimRender.GetImage; Application.ProcessMessages; end; end; procedure TAnimateForm.FormDestroy(Sender: TObject); begin AnimCp.Free; PreviewCp.Free; AnimRender.Free; end; procedure TAnimateForm.FormShow(Sender: TObject); var Registry: TRegistry; begin { Read position from registry } Registry := TRegistry.Create; try Registry.RootKey := HKEY_CURRENT_USER; if Registry.OpenKey('\Software\' + APP_NAME + '\Forms\Animate', False) then begin if Registry.ValueExists('Left') then self.Left := Registry.ReadInteger('Left'); if Registry.ValueExists('Top') then self.Top := Registry.ReadInteger('Top'); if Registry.ValueExists('ResetLocation') then chkResetLocation.Checked := Registry.ReadBool('ResetLocation'); if Registry.ValueExists('ListFlames') then chkListFlames.Checked := Registry.ReadBool('ListFlames'); if Registry.ValueExists('RenderFrames') then chkRender.Checked := Registry.ReadBool('RenderFrames'); if Registry.ValueExists('AnimationType') then cbAnimType.ItemIndex := Registry.ReadInteger('AnimationType'); Registry.CloseKey; end; finally Registry.Free; end; cbFrameExt.ItemIndex := defFrameExt; txtDensity.Text := FloatToStr(renderDensity); txtFilterRadius.Text := FloatToStr(Round6(renderFilterRadius)); seOversample.Value := renderOversample; edPrefix.Text := defAnimPrefix; seFPS.Value := AnimFPS; edOutFlame.Text := ParamFolder + MainCp.name + ' (animated).flame'; chkRenderClick(Sender); cbAnimTypeChange(Sender); UpdateControls; end; procedure TAnimateForm.mnuPreviewQualityClick(Sender: TObject); begin if TMenuItem(Sender).Checked then exit; // prevent unneseccary updating TMenuItem(Sender).Checked := True; case TMenuItem(Sender).Tag of 0: PreviewDensity := prevMediumQuality; 1: PreviewDensity := prevMediumQuality; 2: PreviewDensity := prevHighQuality; end; AnimPrevQual := TMenuItem(Sender).Tag; DrawPreviewProc; end; procedure TAnimateForm.sbFilterRadiusDownClick(Sender: TObject); var n: double; begin try n := StrToFloat(txtFilterRadius.Text); n := Round6(n - 0.05); if (n > 0) then txtFilterRadius.Text := FloatToStr(n); except raise Exception.Create(TextByKey('render-status-invalidfilterradius')); end; end; procedure TAnimateForm.sbFilterRadiusUpClick(Sender: TObject); var n: double; begin try n := StrToFloat(txtFilterRadius.Text); txtFilterRadius.Text := Format('%.3g', [n + 0.05]); except raise Exception.Create(TextByKey('render-status-invalidfilterradius')); end; end; procedure TAnimateForm.seFrameHeightChange(Sender: TObject); begin try FrameHeight := seFrameHeight.Value; if (cbAspectRatio.ItemIndex > 0) and seFrameHeight.Focused then begin FrameWidth := Round(FrameHeight * FrameRatio); seFrameWidth.Value := FrameWidth; end; except end; end; procedure TAnimateForm.seFrameWidthChange(Sender: TObject); begin try FrameWidth := seFrameWidth.Value; if (cbAspectRatio.ItemIndex > 0) and seFrameWidth.Focused then begin FrameHeight := Round(FrameWidth / FrameRatio); seFrameHeight.Value := FrameHeight; end; except end; end; procedure TAnimateForm.tbPlayClick(Sender: TObject); var i: byte; begin StopAnimate := False; cbFlamesChange(Sender); ChangeControlState(false); if chkInvertBG.Checked then for i := 0 to 2 do StartBG[i] := AnimCp.background[i]; AnimProc := DrawPreviewProc; DoAnimate; AnimStatus.SimpleText := ''; ChangeControlState(true); end; procedure TAnimateForm.tbStopClick(Sender: TObject); begin StopAnimate := True; ChangeControlState(true); end; procedure TAnimateForm.ChangeControlState(const activate: boolean); begin gbFrame.Enabled := activate; gbAnimType.Enabled := activate; btSaveAnimation.Enabled := activate; tbPlay.Enabled := activate; tbStop.Enabled := not activate; end; procedure TAnimateForm.UpdateControls; begin FillFlameList; cbFlamesChange(self); end; end.