{ 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.
