{
     Apophysis Copyright (C) 2001-2004 Mark Townsend
     Apophysis Copyright (C) 2005-2006 Ronald Hordijk, Piotr Borys, Peter Sdobnov
     Apophysis Copyright (C) 2007-2008 Piotr Borys, Peter Sdobnov
     
     Apophysis "3D hack" Copyright (C) 2007-2008 Peter Sdobnov
     Apophysis "7X" Copyright (C) 2009-2010 Georg Kiehne

     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation; either version 2 of the License, or
     (at your option) any later version.

     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.

     You should have received a copy of the GNU General Public License
     along with this program; if not, write to the Free Software
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
}

//{$D-,L-,O+,Q-,R-,Y-,S-}

unit Main;

//{$define VAR_STR}

{$ifdef VER240}
  // we need to update TMS Scripter to the XE3 version...
  {$Define DisableScripting}
{$endif}

interface

uses
  Windows, Forms, Dialogs, Menus, Controls, ComCtrls,
  ToolWin, StdCtrls, Classes, Messages, ExtCtrls, ImgList,
  Jpeg, SyncObjs, SysUtils, ClipBrd, Graphics, Math,
  ExtDlgs, AppEvnts, ShellAPI, Registry, Curves,
  Global, Xform, XFormMan, ControlPoint, CMap,
  RenderThread, RenderingCommon, RenderingInterface, (*ParameterIO,*)
  LibXmlParser, LibXmlComps, PngImage, XPMan,
  StrUtils, LoadTracker, CheckLst,
  CommandLine, RegularExpressionsCore, MissingPlugin, Base64, Translation,
  RegexHelper;//, WinInet;

const
  PixelCountMax = 32768;
  RS_A1 = 0;
  RS_DR = 1;
  RS_XO = 2;
  RS_VO = 3;

  randFilename = 'Apophysis7X.rand';
  undoFilename = 'Apophysis7X.undo';
  templateFilename = 'Apophysis7X.temp';
  templatePath = '\templates';
  scriptPath = '\scripts';

type
  TMouseMoveState = (msUsual, msZoomWindow, msZoomOutWindow, msZoomWindowMove,
                     msZoomOutWindowMove, msDrag, msDragMove, msRotate, msRotateMove, msPitchYaw, msHeight);

type
  TWin32Version = (wvUnknown, wvWin95, wvWin98, wvWinNT, wvWin2000, wvWinXP, wvWinVista, wvWin7, wvWinFutureFromOuterSpace);

type
  TThumbnailThread = class(TThread)
    private
      ThumbnailSize : integer;
      Flames : TStringList;
      FileName : string;
      Initialized : boolean;
      
    public
      constructor Create(SourceFile : string; FlameNames : TstringList);
      destructor Destroy; override;
      procedure Execute; override;
  end;

type
  pRGBTripleArray = ^TRGBTripleArray;
  TRGBTripleArray = array[0..PixelCountMax - 1] of TRGBTriple;
  TMatrix = array[0..1, 0..1] of double;

  TMainForm = class(TForm)
    Buttons: TImageList;
    SmallImages: TImageList;
    MainMenu: TMainMenu;
    MainFile: TMenuItem;
    mnuSaveUPR: TMenuItem;
    N1: TMenuItem;
    mnuRandomBatch: TMenuItem;
    FileExitSep: TMenuItem;
    mnuExit: TMenuItem;
    MainEdit: TMenuItem;
    mnuCopyUPR: TMenuItem;
    mnuEditor: TMenuItem;
    mnuRandom: TMenuItem;
    mnuNormalWeights: TMenuItem;
    mnuEqualize: TMenuItem;
    mnuRWeights: TMenuItem;
    mnuOptions: TMenuItem;
    MainHelp: TMenuItem;
    mnuHelpTopics: TMenuItem;
    OpenDialog: TOpenDialog;
    ListPopUp: TPopupMenu;
    mnuItemDelete: TMenuItem;
    mnuListRename: TMenuItem;
    DisplayPopup: TPopupMenu;
    mnuPopFullscreen: TMenuItem;
    RedrawTimer: TTimer;
    mnuVar: TMenuItem;
    mnuVRandom: TMenuItem;
    N3: TMenuItem;
    mnuOpen: TMenuItem;
    mnuSaveAs: TMenuItem;
    N8: TMenuItem;
    mnuGrad: TMenuItem;
    mnuSmoothGradient: TMenuItem;
    mnuView: TMenuItem;
    mnuToolbar: TMenuItem;
    mnuStatusBar: TMenuItem;
    BackPanel: TPanel;
    mnuFileContents: TMenuItem;
    mnuUndo: TMenuItem;
    mnuRedo: TMenuItem;
    N5: TMenuItem;
    SaveDialog: TSaveDialog;
    F1: TMenuItem;
    N11: TMenuItem;
    mnuAbout: TMenuItem;
    mnuFullScreen: TMenuItem;
    mnuRender: TMenuItem;
    mnuMutate: TMenuItem;
    mnuAdjust: TMenuItem;
    mnuOpenGradient: TMenuItem;
    mnuResetLocation: TMenuItem;
    N4: TMenuItem;
    N14: TMenuItem;
    mnuSaveUndo: TMenuItem;
    N2: TMenuItem;
    mnuPopResetLocation: TMenuItem;
    N6: TMenuItem;
    mnuPopUndo: TMenuItem;
    N16: TMenuItem;
    mnuPopRedo: TMenuItem;
    mnuCalculateColors: TMenuItem;
    mnuRandomizeColorValues: TMenuItem;
    N7: TMenuItem;
    N18: TMenuItem;
    N19: TMenuItem;
    mnuScript: TMenuItem;
    mnuRun: TMenuItem;
    mnuEditScript: TMenuItem;
    N15: TMenuItem;
    mnuStop: TMenuItem;
    mnuOpenScript: TMenuItem;
    mnuImportGimp: TMenuItem;
    N9: TMenuItem;
    N10: TMenuItem;
    mnuManageFavorites: TMenuItem;
    mnuImageSize: TMenuItem;
    N13: TMenuItem;
    ApplicationEvents: TApplicationEvents;
    mnuPaste: TMenuItem;
    mnuCopy: TMenuItem;
    N20: TMenuItem;
    mnuExportFLame: TMenuItem;
    mnuPostSheep: TMenuItem;
    N21: TMenuItem;
    mnuFlamepdf: TMenuItem;
    mnuimage: TMenuItem;
    mnuSaveAllAs: TMenuItem;
    View1: TMenuItem;
    mnuRenderAll: TMenuItem;
    mnuBuiltinVars: TMenuItem;
    mnuPluginVars: TMenuItem;
    Thumbnails: TImageList;
    Image1: TImage;
    Splitter: TSplitter;
    SmallThumbnails: TImageList;
    ListBackPanel: TPanel;
    Shape1: TShape;
    ListView: TListView;
    ListView1: TListView;
    cbMain: TCoolBar;
    ToolBar: TToolBar;
    ToolButton8: TToolButton;
    btnOpen: TToolButton;
    btnSave: TToolButton;
    ToolButton10: TToolButton;
    btnRender: TToolButton;
    tbRenderAll: TToolButton;
    ToolButton9: TToolButton;
    btnViewList: TToolButton;
    btnViewIcons: TToolButton;
    ToolButton2: TToolButton;
    btnUndo: TToolButton;
    btnRedo: TToolButton;
    ToolButton1: TToolButton;
    btnReset: TToolButton;
    btnFullScreen: TToolButton;
    ToolButton3: TToolButton;
    tbQualityBox: TComboBoxEx;
    New1: TMenuItem;
    ColorDialog: TColorDialog;
    mnuResetUI: TMenuItem;
    ToolButton4: TToolButton;
    ToolButton5: TToolButton;
    ToolButton6: TToolButton;
    ToolButton7: TToolButton;
    ToolButton11: TToolButton;
    ToolButton12: TToolButton;
    ToolButton13: TToolButton;
    ToolButton14: TToolButton;
    ToolButton15: TToolButton;
    tbShowAlpha: TToolButton;
    ToolButton16: TToolButton;
    ToolButton17: TToolButton;
    btnRunScript: TToolButton;
    btnStopScript: TToolButton;
    ToolButton18: TToolButton;
    tbDraw: TToolButton;
    ToolButton20: TToolButton;
    ToolButton21: TToolButton;
    ToolButton22: TToolButton;
    AutoSaveTimer: TTimer;
    Restorelastautosave1: TMenuItem;
    tbGuides: TToolButton;
    mnuTurnFlameToScript: TMenuItem;
    N12: TMenuItem;
    mnuReportFlame: TMenuItem;
    mnuMessages: TMenuItem;
    BottomDock: TPanel;
    StatusBar: TStatusBar;
    Image: TImage;
    pnlLSPFrame: TPanel;
    LoadSaveProgress: TProgressBar;
    mnuResumeRender: TMenuItem;
    mnuManual: TMenuItem;
    ToolButton19: TToolButton;
    mnuCurves: TMenuItem;
    N17: TMenuItem;
    procedure mnuManualClick(Sender: TObject);
    procedure mnuReportFlameClick(Sender: TObject);
    procedure mnuTurnFlameToScriptClick(Sender: TObject);
    procedure tbzoomoutwindowClick(Sender: TObject);
    procedure mnuimageClick(Sender: TObject);
    procedure mnuExitClick(Sender: TObject);
    procedure mnuSaveUPRClick(Sender: TObject);
    procedure ListViewChange(Sender: TObject; Item: TListItem;
      Change: TItemChange);
    procedure FormCreate(Sender: TObject);
    procedure mnuRandomClick(Sender: TObject);
    procedure mnuEqualizeClick(Sender: TObject);
    procedure mnuEditorClick(Sender: TObject);
    procedure mnuRWeightsClick(Sender: TObject);
    procedure mnuRandomBatchClick(Sender: TObject);
    procedure FormKeyPress(Sender: TObject; var Key: Char);
    procedure FormKeyUpDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure mnuOptionsClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure mnuHelpTopicsClick(Sender: TObject);
    procedure mnuRefreshClick(Sender: TObject);
    procedure mnuNormalWeightsClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure mnuCopyUPRClick(Sender: TObject);
    procedure mnuItemDeleteClick(Sender: TObject);
    procedure ListViewEdited(Sender: TObject; Item: TListItem;
      var S: string);
    procedure mnuListRenameClick(Sender: TObject);
    procedure BackPanelResize(Sender: TObject);
    procedure mnuNextClick(Sender: TObject);
    procedure mnuPreviousClick(Sender: TObject);
    procedure RedrawTimerTimer(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure MainFileClick(Sender: TObject);
    procedure MainViewClick(Sender: TObject);
    procedure MainToolsClick(Sender: TObject);
    procedure MainHelpClick(Sender: TObject);
    procedure mnuVRandomClick(Sender: TObject);
    procedure mnuSaveAsClick(Sender: TObject);
    procedure mnuOpenClick(Sender: TObject);
    procedure mnuGradClick(Sender: TObject);
    procedure mnuSmoothGradientClick(Sender: TObject);
    procedure mnuToolbarClick(Sender: TObject);
    procedure mnuStatusBarClick(Sender: TObject);
    procedure mnuFileContentsClick(Sender: TObject);
    procedure mnuUndoClick(Sender: TObject);
    procedure mnuRedoClick(Sender: TObject);
    procedure Undo;
    procedure Redo;
    procedure mnuExportBitmapClick(Sender: TObject);
    procedure mnuFullScreenClick(Sender: TObject);
    procedure mnuRenderClick(Sender: TObject);
    procedure mnuMutateClick(Sender: TObject);
    procedure mnuAdjustClick(Sender: TObject);
    procedure mnuResetLocationClick(Sender: TObject);
    procedure mnuAboutClick(Sender: TObject);
    procedure mnuOpenGradientClick(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormActivate(Sender: TObject);
    procedure FormDeactivate(Sender: TObject);
    procedure mnuCalculateColorsClick(Sender: TObject);
    procedure mnuRandomizeColorValuesClick(Sender: TObject);
    procedure mnuEditScriptClick(Sender: TObject);
    procedure btnRunClick(Sender: TObject);
    procedure mnuRunClick(Sender: TObject);
    procedure mnuOpenScriptClick(Sender: TObject);
    procedure mnuStopClick(Sender: TObject);
    procedure mnuImportGimpClick(Sender: TObject);
    procedure mnuManageFavoritesClick(Sender: TObject);
    procedure mnuShowFullClick(Sender: TObject);
    procedure mnuImageSizeClick(Sender: TObject);
    procedure ApplicationEventsActivate(Sender: TObject);
    procedure mnuPasteClick(Sender: TObject);
    procedure mnuCopyClick(Sender: TObject);
    procedure mnuExportFlameClick(Sender: TObject);

    procedure ListXmlScannerStartTag(Sender: TObject; TagName: string;
      Attributes: TAttrList);
    procedure XMLScannerStartTag(Sender: TObject; TagName: string;
      Attributes: TAttrList);
    procedure XMLScannerEmptyTag(Sender: TObject; TagName: string;
      Attributes: TAttrList);
    procedure mnuFlamepdfClick(Sender: TObject);
    procedure ImageMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure ImageMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure ImageMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure tbzoomwindowClick(Sender: TObject);
    procedure tbDragClick(Sender: TObject);
    procedure tbRotateClick(Sender: TObject);
    procedure mnuSaveAllAsClick(Sender: TObject);
    procedure tbQualityBoxKeyPress(Sender: TObject; var Key: Char);
    procedure tbQualityBoxSet(Sender: TObject);
    procedure ImageDblClick(Sender: TObject);
    procedure tbShowAlphaClick(Sender: TObject);
    procedure tbShowTraceClick(Sender: TObject);
    procedure XmlScannerContent(Sender: TObject; Content: String);
    procedure mnuRenderAllClick(Sender: TObject);
    procedure ListViewChanging(Sender: TObject; Item: TListItem;
      Change: TItemChange; var AllowChange: Boolean);
    procedure ListViewInfoTip(Sender: TObject; Item: TListItem;
      var InfoTip: String);
    procedure btnViewIconsClick(Sender: TObject);
    procedure btnViewListClick(Sender: TObject);
    procedure ListView1Click(Sender: TObject);
    procedure XmlScannerEndTag(Sender: TObject; TagName: String);
    procedure ToolButton7Click(Sender: TObject);
    procedure RebuildListView();
    procedure ToolButton8Click(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure mnuResetUIClick(Sender: TObject);
    procedure AutoSaveTimerTimer(Sender: TObject);
    procedure Restorelastautosave1Click(Sender: TObject);
    procedure tbGuidesClick(Sender: TObject);
    procedure ToolButton19Click(Sender: TObject);
    procedure mnuTraceClick(Sender: TObject);

  private
    SubstSource: TStringList;
    SubstTarget: TStringList;

    Renderer: TRenderThread;
    FNrThreads: integer;

    FMouseMoveState: TMouseMoveState;
    FSelectRect, FClickRect: TRect;
    DrawSelection: boolean;
    FRotateAngle: double;
    FClickAngle: double;
    FViewImage: TPngObject;
    FViewPos, FViewOldPos: TSPoint;
    FViewScale: double;
    FClickPitch, FNewPitch: double;
    FClickYaw, FNewYaw: double;
    FShiftState: TShiftState;
    DoNotAskAboutChange: boolean;
    ParseHandledPluginList : boolean;

    // For parsing:
    FinalXformLoaded: boolean;
    ActiveXformSet: integer;
    XMLPaletteFormat: string;
    XMLPaletteCount: integer;

    camDragMode, camDragged, camMM: boolean;
    camDragPos, camDragOld: TPoint;
    camDragValueX, camDragValueY: double;

    procedure CreateSubstMap;
    procedure InsertStrings;
    procedure DrawImageView;
    procedure DrawZoomWindow;
    procedure DrawRotatelines(Angle: double);
    procedure DrawPitchYawLines(YawAngle: double; PitchAngle:double);

    procedure FillVariantMenu;
    procedure VariantMenuClick(Sender: TObject);

    procedure FavoriteClick(Sender: TObject);
    procedure ScriptItemClick(Sender: TObject);
    procedure HandleThreadCompletion(var Message: TMessage);
      message WM_THREAD_COMPLETE;
    procedure HandleThreadTermination(var Message: TMessage);
      message WM_THREAD_TERMINATE;

  public
    { Public declarations }
    UndoIndex, UndoMax: integer;
    Center: array[0..1] of double;
    MainZoom: double;
    StartTime: TDateTime;
    AnimPal: TColorMap;
    PrevListItem: TListItem;
    LockListChangeUpdate: boolean;
    CurrentFileName: string;
    UsedThumbnails: TImageList;
    ParseLoadingBatch : boolean;
    SurpressHandleMissingPlugins : boolean;
    LastCaptionSel, LastCaptionFoc: string;
    LastDecision: boolean;

    VarMenus: array of TMenuItem;

    ListXmlScanner : TEasyXmlScanner;
    XmlScanner : TXmlScanner;

    function ReadWithSubst(Attributes: TAttrList; attrname: string): string;
    procedure InvokeLoadXML(xmltext:string);
    procedure LoadXMLFlame(filename, name: string);
    procedure DisableFavorites;
    procedure EnableFavorites;
    procedure ParseXML(var cp1: TControlPoint; const params: string; const ignoreErrors : boolean);
    function SaveFlame(cp1: TControlPoint; title, filename: string): boolean;
    function SaveXMLFlame(const cp1: TControlPoint; title, filename: string): boolean;
    procedure DisplayHint(Sender: TObject);
    procedure OnProgress(prog: double);
    procedure ResizeImage;
    procedure DrawPreview;
    procedure DrawFlame;
    procedure UpdateUndo;
    procedure LoadUndoFlame(index: integer; filename: string);
    procedure SmoothPalette;
    procedure RandomizeCP(var cp1: TControlPoint; alg: integer = 0);
    function UPRString(cp1: TControlPoint; Entry: string): string;
    function SaveGradient(Gradient, Title, FileName: string): boolean;
    function GradientFromPalette(const pal: TColorMap; const title: string): string;
    procedure StopThread;
    procedure UpdateWindows;
    procedure ResetLocation;
    procedure RandomBatch;
    procedure GetScripts;
    function ApplicationOnHelp(Command: Word; Data: Integer; var CallHelp: Boolean): Boolean;
    function SystemErrorMessage: string;
    function SystemErrorMessage2(errno:cardinal): string;
    function RetrieveXML(cp : TControlPoint):string;
  end;

procedure ListXML(FileName: string; sel: integer);
function EntryExists(En, Fl: string): boolean;
function XMLEntryExists(title, filename: string): boolean;
//procedure ComputeWeights(var cp1: TControlPoint; Triangles: TTriangles; t: integer);
function DeleteEntry(Entry, FileName: string): boolean;
function CleanIdentifier(ident: string): string;
function CleanUPRTitle(ident: string): string;
function GradientString(c: TColorMap): string;
//function PackVariations: int64;
//procedure UnpackVariations(v: int64);
//procedure NormalizeWeights(var cp: TControlPoint);
//procedure EqualizeWeights(var cp: TControlPoint);
procedure MultMatrix(var s: TMatrix; const m: TMatrix);
procedure ListFlames(FileName: string; sel: integer);
procedure ListIFS(FileName: string; sel: integer);
procedure NormalizeVariations(var cp1: TControlPoint);
function GetWinVersion: TWin32Version;
function LoadXMLFlameText(filename, name: string) : string;

var
  MainForm: TMainForm;
  pname, ptime: String;
  nxform: integer;
  TbBreakWidth: integer;

  EnumPlugins: Boolean;
  MainCp: TControlPoint;
  ParseCp: TControlPoint;
  CurrentFlame: string;
  ThumbnailSize:integer;
  UpdateList:TStringList;
  UpdateError:boolean;
  AboutToExit:boolean;

  AppVersionString:string; //APP_NAME+'.'+APP_VERSION;

implementation

uses
  Editor, Options, Settings, Template,
  FullScreen, FormRender, Mutate, Adjust, Browser, Save, About, CmapData,
  {$ifdef DisableScripting}
  {$else}
    ScriptForm, FormFavorites,
  {$endif}
  FormExport, RndFlame, Tracer, Types, SplashForm, varGenericPlugin;

{$R *.DFM}

procedure AssignBitmapProperly(var Bitmap:TBitmap; Source:TBitmap);
begin
  Bitmap.Dormant;
  Bitmap.FreeImage;
  Bitmap.Width := 0;
  Bitmap.Assign(Source);
end;

procedure FreeBitmapProperly(var Bitmap:TBitmap);
begin
  try
   Bitmap.Dormant;
   Bitmap.FreeImage;
  finally
   Bitmap.Free;
  end;
end;

procedure NormalizeVariations(var cp1: TControlPoint);
var
  totvar: double;
  i, j: integer;
begin
  for i := 0 to NXFORMS - 1 do
  begin
    totvar := 0;
    for j := 0 to NRVAR - 1 do
    begin
      if cp1.xform[i].GetVariation(j) < 0 then
        cp1.xform[i].SetVariation(j, cp1.xform[i].GetVariation(j) * -1);
      totvar := totvar + cp1.xform[i].GetVariation(j);
    end;
    if totVar = 0 then
    begin
      cp1.xform[i].SetVariation(0, 1)
    end
    else
      for j := 0 to NRVAR - 1 do begin
        if totVar <> 0 then
          cp1.xform[i].SetVariation(j, cp1.xform[i].GetVariation(j) / totvar);
      end;
  end;
end;

function FlameInClipboard: boolean;
var
  flamestr: string;
  isstart, isend: integer;
begin
  { returns true if a flame in clipboard - can be tricked }
  result := false;
  if Clipboard.HasFormat(CF_TEXT) then
  begin
    flamestr := Clipboard.AsText;
    isstart := Pos('<flame', flamestr);
    isend := Pos('</flame>', flamestr);
    if (isstart > 0) and (isend > 0) and (isstart < isend) then Result := true;
  end
end;

procedure MultMatrix(var s: TMatrix; const m: TMatrix);
var
  a, b, c, d, e, f, g, h: double;
begin
  a := s[0, 0];
  b := s[0, 1];
  c := s[1, 0];
  d := s[1, 1];
  e := m[0, 0];
  f := m[0, 1];
  g := m[1, 0];
  h := m[1, 1];
{
    [a, b][e ,f]   [a*e+b*g, a*f+b*h]
    [    ][    ] = [                ]
    [c, d][g, h]   [c*e+d*g, c*f+d*h]
}
  s[0, 0] := a * e + b * g;
  s[0, 1] := a * f + b * h;
  s[1, 0] := c * e + d * g;
  s[1, 1] := c * f + d * h;

end;

(*
function PackVariations: int64;
{ Packs the variation options into an integer with Linear as lowest bit }
var
  i: integer;
begin
  result := 0;
  for i := NRVAR-1 downto 0 do
  begin
    result := (result shl 1) or integer(Variations[i]);
  end;
end;

procedure UnpackVariations(v: int64);
{ Unpacks the variation options form an integer }
var
  i: integer;
begin
  for i := 0 to NRVAR - 1 do
    Variations[i] := boolean(v shr i and 1);
end;
*)

function GetWinVersion: TWin32Version;
{ Returns current version of a host Win32 platform }
begin
  Result := wvUnknown;
  if Win32Platform = VER_PLATFORM_WIN32_WINDOWS then
    if (Win32MajorVersion > 4) or ((Win32MajorVersion = 4) and (Win32MinorVersion > 0)) then
      Result := wvWin98
    else
      Result := wvWin95
  else
    if Win32MajorVersion <= 4 then
      Result := wvWinNT
    else if Win32MajorVersion = 5 then
      if Win32MinorVersion = 0 then
        Result := wvWin2000
      else if Win32MinorVersion >= 1 then
        Result := wvWinXP
    else if Win32MajorVersion = 6 then
      if Win32MinorVersion = 0 then
        Result := wvWinVista
      else if Win32MinorVersion >= 1 then
        Result := wvWin7
    else if Win32MajorVersion >= 7 then
      Result := wvWinFutureFromOuterSpace;
end;

{ ************************************* Help ********************************* }

procedure ShowHelp(Pt: TPoint; ContextId: Integer);
//var
  //Popup: THHPopup;
begin
 (*  -X- context help not longer supported
  FillChar(Popup, SizeOf(Popup), 0);
  Popup.cbStruct := SizeOf(Popup);
  Popup.hinst := 0;
  Popup.idString := ContextId;
  Popup.pszText := nil;
  GetCursorPos(Pt);
  Popup.pt := Pt;
  Popup.clrForeGround := TColorRef(-1);
  Popup.clrBackground := TColorRef(-1);
  Popup.rcMargins := Rect(-1, -1, -1, -1);
  Popup.pszFont := '';
  HtmlHelp(0, PChar(AppPath + 'Apophysis7x.chm::/Popups.txt'), HH_DISPLAY_TEXT_POPUP, DWORD(@Popup));
  *)
end;

procedure TMainForm.RebuildListView;
var
  i:integer;
  item:TListItem;
begin
    ListView.Items.Clear; 

  /// backup in old lv
    for i := 0 to ListView1.Items.Count-1 do begin
      item := ListView.Items.Add;
      item.Caption := ListView1.Items[i].Caption;
    end;

    // rebuild new lv
    ListView1.Items.Clear;

    for i := 0 to ListView.Items.Count-1 do begin
      item := ListView1.Items.Add;
      item.Caption := ListView.Items[i].Caption;
      if (not ClassicListMode) then item.ImageIndex := i;
    end;

    ListView.Items.Clear;


end;

procedure TMainForm.InsertStrings;
begin
  mnuCopy.Caption := TextByKey('common-copy');
	mnuPaste.Caption := TextByKey('common-paste');
	mnuItemDelete.Caption := TextByKey('common-delete');
	mnuListRename.Caption := TextByKey('common-rename');
	mnuUndo.Caption := TextByKey('common-undo');
	mnuPopUndo.Caption := TextByKey('common-undo');
	btnUndo.Hint := TextByKey('common-undo');
	mnuRedo.Caption := TextByKey('common-redo');
	mnuPopRedo.Caption := TextByKey('common-redo');
	btnRedo.Hint := TextByKey('common-redo');
	MainFile.Caption := TextByKey('main-menu-file-title');
	New1.Caption := TextByKey('main-menu-file-new');
	ToolButton8.Hint := TextByKey('main-menu-file-new');
	mnuOpen.Caption := TextByKey('main-menu-file-open');
	btnOpen.Hint := TextByKey('main-menu-file-open');
	RestoreLastAutosave1.Caption := TextByKey('main-menu-file-restoreautosave');
	mnuSaveAs.Caption := TextByKey('main-menu-file-saveparams');
	btnSave.Hint := TextByKey('main-menu-file-saveparams');
	mnuSaveAllAs.Caption := TextByKey('main-menu-file-saveallparams');
	mnuSmoothGradient.Caption := TextByKey('main-menu-file-smoothpalette');
	mnuOpenGradient.Caption := TextByKey('main-menu-file-gradientbrowser');
	mnuSaveUPR.Caption := TextByKey('main-menu-file-exportupr');
	mnuExportFlame.Caption := TextByKey('main-menu-file-exportflame');
	mnuImportGimp.Caption := TextByKey('main-menu-file-importgimp');
	mnuPostSheep.Caption := TextByKey('main-menu-file-submitsheep');
	mnuRandomBatch.Caption := TextByKey('main-menu-file-randombatch');
	mnuExit.Caption := TextByKey('main-menu-file-exit');
	MainEdit.Caption := TextByKey('main-menu-edit-title');
	mnuSaveUndo.Caption := TextByKey('main-menu-edit-saveundo');
	mnuCopyUPR.Caption := TextByKey('main-menu-edit-copyasupr');
	View1.Caption := TextByKey('main-menu-view-title');
	mnuFullScreen.Caption := TextByKey('main-menu-view-fullscreen');
	mnuPopFullscreen.Caption := TextByKey('main-menu-view-fullscreen');
	btnFullScreen.Hint := TextByKey('main-menu-view-fullscreen');
	mnuEditor.Caption := TextByKey('main-menu-view-editor');
	ToolButton5.Hint := TextByKey('main-menu-view-editor');
	mnuAdjust.Caption := TextByKey('main-menu-view-adjustment');
	ToolButton6.Hint := TextByKey('main-menu-view-adjustment');
	mnuGrad.Caption := TextByKey('main-menu-view-gradient');
	ToolButton7.Hint := TextByKey('main-menu-view-gradient');
	mnuMutate.Caption := TextByKey('main-menu-view-mutation');
	ToolButton11.Hint := TextByKey('main-menu-view-mutation');
	mnuImageSize.Caption := TextByKey('main-menu-view-imagesize');
	ToolButton12.Hint := TextByKey('main-menu-view-imagesize');
	mnuMessages.Caption := TextByKey('main-menu-view-messages');
	toolButton13.Hint := TextByKey('main-menu-view-messages');
  ToolButton19.Hint := TextByKey('main-menu-view-curves');
  mnuCurves.Caption := TextByKey('main-menu-view-curves');
	F1.Caption := TextByKey('main-menu-flame-title');
	mnuResetLocation.Caption := TextByKey('main-menu-flame-reset');
	mnuPopResetLocation.Caption := TextByKey('main-menu-flame-reset');
	btnReset.Hint := TextByKey('main-menu-flame-reset');
	mnuRandom.Caption := TextByKey('main-menu-flame-randomize');
	mnuRWeights.Caption := TextByKey('main-menu-flame-randomweights');
	mnuEqualize.Caption := TextByKey('main-menu-flame-equalweights');
	mnuNormalWeights.Caption := TextByKey('main-menu-flame-computeweights');
	mnuCalculateColors.Caption := TextByKey('main-menu-flame-calculatecolors');
	mnuRandomizeColorValues.Caption := TextByKey('main-menu-flame-randomizecolors');
	mnuRender.Caption := TextByKey('main-menu-flame-rendertodisk');
	btnRender.Hint := TextByKey('main-menu-flame-rendertodisk');
	mnuRenderAll.Caption := TextByKey('main-menu-flame-renderallflames');
	tbRenderAll.Hint := TextByKey('main-menu-flame-renderallflames');
	mnuReportFlame.Caption := TextByKey('main-menu-flame-generatereport');
	mnuVar.Caption := TextByKey('main-menu-variation-title');
	mnuVRandom.Caption := TextByKey('main-menu-variation-random');
	mnuBuiltinVars.Caption := TextByKey('main-menu-variation-builtin');
	mnuPluginVars.Caption := TextByKey('main-menu-variation-plugins');
	mnuScript.Caption := TextByKey('main-menu-script-title');
	mnuRun.Caption := TextByKey('main-menu-script-run');
	btnRunScript.Hint := TextByKey('main-menu-script-run');
	mnuStop.Caption := TextByKey('main-menu-script-stop');
	btnStopScript.Hint := TextByKey('main-menu-script-stop');
	mnuOpenScript.Caption := TextByKey('main-menu-script-open');
	mnuEditScript.Caption := TextByKey('main-menu-script-edit');
	ToolButton17.Hint := TextByKey('main-menu-script-edit');
	mnuManageFavorites.Caption := TextByKey('main-menu-script-managefaves');
	mnuTurnFlameToScript.Caption := TextByKey('main-menu-script-flametoscript');
	mnuView.Caption := TextByKey('main-menu-options-title');
	mnuToolbar.Caption := TextByKey('main-menu-options-togglemaintoolbar');
	mnuStatusBar.Caption := TextByKey('main-menu-options-togglestatusbar');
	mnuFileContents.Caption := TextByKey('main-menu-options-togglefilelist');
	mnuResetUI.Caption := TextByKey('main-menu-options-resetfilelistwidth');
	mnuOptions.Caption := TextByKey('main-menu-options-showoptions');
	ToolButton14.Hint := TextByKey('main-menu-options-showoptions');
	MainHelp.Caption := TextByKey('main-menu-help-title');
	mnuHelpTopics.Caption := TextByKey('main-menu-help-contents');
	mnuFlamePDF.Caption := TextByKey('main-menu-help-aboutalgorithm');
	mnuAbout.Caption := TextByKey('main-menu-help-aboutapophysis');
	btnViewList.Hint := TextByKey('main-toolbar-listviewmode-classic');
	btnViewIcons.Hint := TextByKey('main-toolbar-listviewmode-icons');
	tbShowAlpha.Hint := TextByKey('main-toolbar-togglealpha');
	tbGuides.Hint := TextByKey('main-toolbar-toggleguides');
	tbDraw.Hint := TextByKey('main-toolbar-modemove');
	ToolButton20.Hint := TextByKey('main-toolbar-moderotate');
	ToolButton21.Hint := TextByKey('main-toolbar-modezoomin');
	ToolButton22.Hint := TextByKey('main-toolbar-modezoomout');
  ListView1.Columns[0].Caption := TextByKey('save-name');
  mnuResumeRender.Caption := TextByKey('main-menu-flame-resumeunfinished');
end;

procedure TMainForm.InvokeLoadXML(xmltext:string);
begin
  ParseXML(MainCP, PCHAR(xmltext), false);
end;

function TMainForm.ApplicationOnHelp(Command: Word; Data: Integer; var CallHelp: Boolean): Boolean;
var
  Pos: TPoint;
begin
  Pos.x := 0;
  Pos.y := 0;

  CallHelp := False;
  Result := True;
  case Command of
    HELP_SETPOPUP_POS: Pos := SmallPointToPoint(TSmallPoint(Data));
    HELP_CONTEXTPOPUP: ShowHelp(Pos, Data);
  else Result := False;
  end;
end;

{ **************************************************************************** }

procedure TMainForm.StopThread;
begin
  RedrawTimer.Enabled := False;
  if Assigned(Renderer) then begin
    assert(Renderer.Suspended = false);
    Renderer.Terminate;
    Renderer.WaitFor;
  end;
end;

procedure EqualizeVars(const x: integer);
var
  i: integer;
begin
  for i := 0 to Transforms - 1 do
    MainCp.xform[x].SetVariation(i, 1.0 / NRVAR);
end;

procedure NormalVars(const x: integer);
var
  i: integer;
  td: double;
begin
  td := 0.0;
  for i := 0 to 6 do
    td := td + Maincp.xform[x].GetVariation(i);
  if (td < 0.001) then
    EqualizeVars(x)
  else
    for i := 0 to 6 do
      MainCp.xform[x].SetVariation(i, MainCp.xform[x].GetVariation(i) / td);
end;

procedure RandomVariation(cp: TControlPoint);
{ Randomise variation parameters }
var
  a, b, i, j: integer;
begin
  inc(MainSeed);
  RandSeed := MainSeed;
  for i := 0 to cp.NumXForms - 1 do
  begin
    for j := 0 to NRVAR - 1 do
      cp.xform[i].SetVariation(j, 0);
    repeat
      a := random(NRVAR);
    until Variations[a];
    repeat
      b := random(NRVAR);
    until Variations[b];
    if (a = b) then
    begin
      cp.xform[i].SetVariation(a, 1);
    end
    else
    begin
      cp.xform[i].SetVariation(a, random);
      cp.xform[i].SetVariation(b, 1 - cp.xform[i].GetVariation(a));
    end;
  end;
end;

procedure SetVariation(cp: TControlPoint);
{ Set the current Variation }
var
  i, j: integer;
begin
  if Variation = vRandom then
  begin
    RandomVariation(cp);
  end
  else
    for i := 0 to cp.NumXForms - 1 do
    begin
      for j := 0 to NRVAR - 1 do
        cp.xform[i].SetVariation(j, 0);
      cp.xform[i].SetVariation(integer(Variation), 1);
    end;
end;

procedure TMainForm.RandomizeCP(var cp1: TControlPoint; alg: integer = 0);
(*
var
  vrnd, Min, Max, i, j, rnd: integer;
  Triangles: TTriangles;
  cmap: TColorMap;
  r, s, theta, phi: double;
  skip: boolean;
*)
var
  sourceCP: TControlPoint;
begin
  if assigned(MainCP) then
    sourceCP := MainCP.Clone
  else
    SourceCP := nil;

  if assigned(cp1) then begin
    cp1.Free;
    cp1 := nil;
  end;
  cp1 := RandomFlame(sourceCP, alg);

  if assigned(sourceCP) then
    sourceCP.Free;

(*
  Min := randMinTransforms;
  Max := randMaxTransforms;
  case randGradient of
    0:
      begin
        cp1.CmapIndex := Random(NRCMAPS);
        GetCMap(cmap_index, 1, cp1.cmap);
        cmap_index := cp1.cmapindex;
      end;
    1: cmap := DefaultPalette;
    2: cmap := MainCp.cmap;
    3: cmap := GradientForm.RandomGradient;
  end;
  inc(MainSeed);
  RandSeed := MainSeed;
  transforms := random(Max - (Min - 1)) + Min;
  repeat
    try
      inc(MainSeed);
      RandSeed := MainSeed;
      cp1.clear;
      cp1.RandomCP(transforms, transforms, false);
      cp1.SetVariation(Variation);
      inc(MainSeed);
      RandSeed := MainSeed;

      case alg of
        1: rnd := 0;
        2: rnd := 7;
        3: rnd := 9;
      else
        if (Variation = vLinear) or (Variation = vRandom) then
          rnd := random(10)
        else
          rnd := 9;
      end;
      case rnd of
        0..6:
          begin
            for i := 0 to Transforms - 1 do
            begin
              if Random(10) < 9 then
                cp1.xform[i].c[0, 0] := 1
              else
                cp1.xform[i].c[0, 0] := -1;
              cp1.xform[i].c[0, 1] := 0;
              cp1.xform[i].c[1, 0] := 0;
              cp1.xform[i].c[1, 1] := 1;
              cp1.xform[i].c[2, 0] := 0;
              cp1.xform[i].c[2, 1] := 0;
              cp1.xform[i].color := 0;
              cp1.xform[i].symmetry := 0;
              cp1.xform[i].vars[0] := 1;
              for j := 1 to NVARS - 1 do
                cp1.xform[i].vars[j] := 0;
              Translate(cp1.xform[i], random * 2 - 1, random * 2 - 1);
              Rotate(cp1.xform[i], random * 360);
              if i > 0 then Scale(cp1.xform[i], random * 0.8 + 0.2)
              else Scale(cp1.xform[i], random * 0.4 + 0.6);
              if Random(2) = 0 then
                Multiply(cp1.xform[i], 1, random - 0.5, random - 0.5, 1);
            end;
            SetVariation(cp1);
          end;
        7, 8:
          begin
          { From the source to Chaos: The Software }
            for i := 0 to Transforms - 1 do
            begin
              r := random * 2 - 1;
              if ((0 <= r) and (r < 0.2)) then
                r := r + 0.2;
              if ((r > -0.2) and (r <= 0)) then
                r := r - 0.2;
              s := random * 2 - 1;
              if ((0 <= s) and (s < 0.2)) then
                s := s + 0.2;
              if ((s > -0.2) and (s <= 0)) then
                s := s - -0.2;
              theta := PI * random;
              phi := (2 + random) * PI / 4;
              cp1.xform[i].c[0][0] := r * cos(theta);
              cp1.xform[i].c[1][0] := s * (cos(theta) * cos(phi) - sin(theta));
              cp1.xform[i].c[0][1] := r * sin(theta);
              cp1.xform[i].c[1][1] := s * (sin(theta) * cos(phi) + cos(theta));
            { the next bit didn't translate so well, so I fudge it}
              cp1.xform[i].c[2][0] := random * 2 - 1;
              cp1.xform[i].c[2][1] := random * 2 - 1;
            end;
            for i := 0 to NXFORMS - 1 do
              cp1.xform[i].density := 0;
            for i := 0 to Transforms - 1 do
              cp1.xform[i].density := 1 / Transforms;
            SetVariation(cp1);
          end;
        9: begin
            for i := 0 to NXFORMS - 1 do
              cp1.xform[i].density := 0;
            for i := 0 to Transforms - 1 do
              cp1.xform[i].density := 1 / Transforms;
          end;
      end; // case
      MainForm.TrianglesFromCp(cp1, Triangles);
      vrnd := Random(2);
      if vrnd > 0 then
        ComputeWeights(cp1, Triangles, transforms)
      else
        EqualizeWeights(cp1);
    except on E: EmathError do
      begin
        Continue;
      end;
    end;
    for i := 0 to Transforms - 1 do
      cp1.xform[i].color := i / (transforms - 1);
    if cp1.xform[0].density = 1 then Continue;
    case SymmetryType of
      { Bilateral }
      1: add_symmetry_to_control_point(cp1, -1);
      { Rotational }
      2: add_symmetry_to_control_point(cp1, SymmetryOrder);
      { Rotational and Reflective }
      3: add_symmetry_to_control_point(cp1, -SymmetryOrder);
    end;
    { elimate flames with transforms that aren't affine }
    skip := false;
    for i := 0 to Transforms - 1 do
      if not transform_affine(Triangles[i], Triangles) then
        skip := True;
    if skip then continue;
  until not cp1.BlowsUP(5000) and (cp1.xform[0].density <> 0);
  cp1.brightness := defBrightness;
  cp1.gamma := defGamma;
  cp1.vibrancy := defVibrancy;
  cp1.sample_density := defSampleDensity;
  cp1.spatial_oversample := defOversample;
  cp1.spatial_filter_radius := defFilterRadius;
  cp1.cmapIndex := MainCp.cmapindex;
  if not KeepBackground then begin
    cp1.background[0] := 0;
    cp1.background[1] := 0;
    cp1.background[2] := 0;
  end;
  if randGradient = 0 then
  else cp1.cmap := cmap;
  cp1.zoom := 0;
  cp1.Nick := SheepNick;
  cp1.URl := SheepURL;
*)
end;

function TMainForm.GradientFromPalette(const pal: TColorMap; const title: string): string;
var
  c, i, j: integer;
  strings: TStringList;
begin
  strings := TStringList.Create;
  try
    strings.add('gradient:');
    strings.add(' title="' + CleanUPRTitle(title) + '" smooth=no');
    for i := 0 to 255 do
    begin
      j := round(i * (399 / 255));
      c := pal[i][2] shl 16 + pal[i][1] shl 8 + pal[i][0];
      strings.Add(' index=' + IntToStr(j) + ' color=' + intToStr(c));
    end;
    result := strings.text;
  finally
    strings.free;
  end;
end;

function CleanIdentifier(ident: string): string;
{ Strips unwanted characters from an identifier}
var
  i: integer;
begin
  for i := 0 to Length(ident) do
  begin
    if ident[i] = #32 then
      ident[i] := '_'
    else if ident[i] = '}' then
      ident[i] := '_'
    else if ident[i] = '{' then
      ident[i] := '_';
  end;
  Result := ident;
end;

procedure TMainForm.OnProgress(prog: double);
var
  Elapsed, Remaining: TDateTime;
  IntProg: Integer;
begin
  IntProg := (round(prog * 100));
  //pnlLSPFrame.Visible := true;
  LoadSaveProgress.Position := IntProg;
  if (IntProg = 100) then LoadSaveProgress.Position := 0;
  Elapsed := Now - StartTime;
  StatusBar.Panels[1].Text := Format(TextByKey('render-status-elapsed') + ' %2.2d:%2.2d:%2.2d.%2.2d',
    [Trunc(Elapsed * 24),
    Trunc((Elapsed * 24 - Trunc(Elapsed * 24)) * 60),
      Trunc((Elapsed * 24 * 60 - Trunc(Elapsed * 24 * 60)) * 60),
      Trunc((Elapsed * 24 * 60 * 60 - Trunc(Elapsed * 24 * 60 * 60)) * 100)]);
  if prog > 0 then
    Remaining := Elapsed/prog - Elapsed
  else
    Remaining := 0;

  StatusBar.Panels[2].Text := Format(TextByKey('render-status-remaining') + ' %2.2d:%2.2d:%2.2d.%2.2d',
    [Trunc(Remaining * 24),
    Trunc((Remaining * 24 - Trunc(Remaining * 24)) * 60),
      Trunc((Remaining * 24 * 60 - Trunc(Remaining * 24 * 60)) * 60),
      Trunc((Remaining * 24 * 60 * 60 - Trunc(Remaining * 24 * 60 * 60)) * 100)]);
  StatusBar.Panels[3].Text := MainCp.name;
  Application.ProcessMessages;
end;

procedure TMainForm.UpdateUndo;
begin
  MainCp.FillUsedPlugins;
  SaveFlame(MainCp, Format('%.4d-', [UndoIndex]) + MainCp.name,
    GetEnvVarValue('APPDATA') + '\' + undoFilename);
  Inc(UndoIndex);
  UndoMax := UndoIndex; //Inc(UndoMax);
  mnuSaveUndo.Enabled := true;
  mnuUndo.Enabled := True;
  mnuPopUndo.Enabled := True;
  mnuRedo.Enabled := false;
  mnuPopRedo.Enabled := false;
  btnUndo.enabled := true;
  btnRedo.Enabled := false;
  EditForm.mnuUndo.Enabled := True;
  EditForm.mnuRedo.Enabled := false;
  EditForm.tbUndo.enabled := true;
  EditForm.tbRedo.enabled := false;
  AdjustForm.btnUndo.enabled := true;
  AdjustForm.btnRedo.enabled := false;
end;

function GradientEntries(gFilename: string): string;
var
  i, p: integer;
  Title: string;
  FileStrings: TStringList;
  NewStrings: TStringList;
begin
  FileStrings := TStringList.Create;
  NewStrings := TStringList.Create;
  NewStrings.Text := '';
  FileStrings.LoadFromFile(gFilename);
  try
    if (Pos('{', FileStrings.Text) <> 0) then
    begin
      for i := 0 to FileStrings.Count - 1 do
      begin
        p := Pos('{', FileStrings[i]);
        if (p <> 0) then
        begin
          Title := Trim(Copy(FileStrings[i], 1, p - 1));
          if (Title <> '') and (LowerCase(Title) <> 'comment') then
          begin { Otherwise bad format }
            NewStrings.Add(Title);
          end;
        end;
      end;
      GradientEntries := NewStrings.Text;
    end;
  finally
    FileStrings.Free;
    NewStrings.Free;
  end;
end;

{ ********************************* File ************************************* }

function EntryExists(En, Fl: string): boolean;
{ Searches for existing identifier in parameter files }
var
  FStrings: TStringList;
  i: integer;
begin
  Result := False;
  if FileExists(Fl) then
  begin
    FStrings := TStringList.Create;
    try
      FStrings.LoadFromFile(Fl);
      for i := 0 to FStrings.Count - 1 do
        if Pos(LowerCase(En) + ' {', Lowercase(FStrings[i])) <> 0 then
          Result := True;
    finally
      FStrings.Free;
    end
  end
  else
    Result := False;
end;

function CleanEntry(ident: string): string;
{ Strips unwanted characters from an identifier}
var
  i: integer;
begin
  for i := 1 to Length(ident) do
  begin
    if ident[i] = #32 then
      ident[i] := '_'
    else if ident[i] = '}' then
      ident[i] := '_'
    else if ident[i] = '{' then
      ident[i] := '_';
  end;
  Result := ident;
end;

function CleanXMLName(ident: string): string;
var
  i: integer;
begin
  for i := 1 to Length(ident) do
  begin
    if ident[i] = '*' then
      ident[i] := '_'
    else if ident[i] = '"' then
      ident[i] := #39;
  end;
  Result := ident;
end;


function CleanUPRTitle(ident: string): string;
{ Strips braces but leave spaces }
var
  i: integer;
begin
  for i := 1 to Length(ident) do
  begin
    if ident[i] = '}' then
      ident[i] := '_'
    else if ident[i] = '{' then
      ident[i] := '_';
  end;
  Result := ident;
end;

function DeleteEntry(Entry, FileName: string): boolean;
{ Deletes an entry from a multi-entry file }
var
  Strings: TStringList;
  p, i: integer;
begin
  Result := True;
  Strings := TStringList.Create;
  try
    i := 0;
    Strings.LoadFromFile(FileName);
    while Pos(Entry + ' ', Trim(Strings[i])) <> 1 do
    begin
      inc(i);
    end;
    repeat
      p := Pos('}', Strings[i]);
      Strings.Delete(i);
    until p <> 0;
    if (i < Strings.Count) and (Trim(Strings[i]) = '') then Strings.Delete(i);
    Strings.SaveToFile(FileName);
  finally
    Strings.Free;
  end;
end;

function SaveUPR(Entry, FileName: string): boolean;
{ Saves UF parameter to end of file }
var
  UPRFile: TextFile;
begin
  Result := True;
  try
    AssignFile(UPRFile, FileName);
    if FileExists(FileName) then
    begin
      if EntryExists(Entry, FileName) then DeleteEntry(Entry, FileName);
      Append(UPRFile);
    end
    else
      ReWrite(UPRFile);
    WriteLn(UPRFile, MainForm.UPRString(MainCp, Entry));
    CloseFile(UPRFile);
  except on E: EInOutError do
    begin
      Application.MessageBox(PChar(Format(TextByKey('common-genericsavefailure'), [FileName])), 'Apophysis', 16);
      Result := False;
    end;
  end;
end;

function IFSToString(cp: TControlPoint; Title: string): string;
{ Creates a string containing a formated IFS parameter set }
var
  i: integer;
  a, b, c, d, e, f, p: double;
  Strings: TStringList;
begin
  Strings := TStringList.Create;
  try
    Strings.Add(CleanEntry(Title) + ' {');
    for i := 0 to Transforms - 1 do
    begin
      a := cp.xform[i].c[0][0];
      b := cp.xform[i].c[0][1];
      c := cp.xform[i].c[1][0];
      d := cp.xform[i].c[1][1];
      e := cp.xform[i].c[2][0];
      f := cp.xform[i].c[2][1];
      p := cp.xform[i].density;
      Strings.Add(Format('%.6g %.6g %.6g %.6g %.6g %.6g %.6g',
        [a, b, c, d, e, f, p]));
    end;
    Strings.Add('}');
    IFSToString := Strings.Text;
  finally
    Strings.Free;
  end;
end;

function GetTitle(str: string): string;
var
  p: integer;
begin
  str := Trim(str);
  p := Pos(' ', str);
  GetTitle := Trim(Copy(str, 1, p));
end;

function GetComment(str: string): string;
{ Extracts comment form line of IFS file }
var
  p: integer;
begin
  str := Trim(str);
  p := Pos(';', str);
  if p <> 0 then
    GetComment := Trim(Copy(str, p + 1, Length(str) - p))
  else
    GetComment := '';
end;

function GetParameters(str: string; var a, b, c, d, e, f, p: double): boolean;
var
  Tokens: TStringList;
begin
  GetParameters := False;
  Tokens := TStringList.Create;
  try
    try
      GetTokens(str, tokens);
      if Tokens.Count >= 7 then {enough tokens}
      begin
        a := StrToFloat(Tokens[0]);
        b := StrToFloat(Tokens[1]);
        c := StrToFloat(Tokens[2]);
        d := StrToFloat(Tokens[3]);
        e := StrToFloat(Tokens[4]);
        f := StrToFloat(Tokens[5]);
        p := StrToFloat(Tokens[6]);
        Result := True;
      end;
    except on E: EConvertError do
      begin
        Result := False
      end;
    end;
  finally
    Tokens.Free;
  end;
end;

function StringToIFS(strng: string): boolean;
{ Loads an IFS parameter set from string}
var
  Strings: TStringList;
  Comments: TStringList;
  i, sTransforms: integer;
  cmnt, sTitle: string;
  a, b, c, d: double;
  e, f, p: double;
begin
  MainCp.clear;
  StringToIFS := True;
  sTransforms := 0;
  Strings := TStringList.Create;
  Comments := TStringList.Create;
  try
    try
      Strings.Text := strng;
      if Pos('}', Strings.Text) = 0 then
        raise EFormatInvalid.Create('No closing brace');
      if Pos('{', Strings[0]) = 0 then
        raise EFormatInvalid.Create('No opening brace.');
      {To Do ... !!!!}
      sTitle := GetTitle(Strings[0]);
      if sTitle = '' then raise EFormatInvalid.Create('No identifier.');
      cmnt := GetComment(Strings[0]);
      if cmnt <> '' then Comments.Add(cmnt);
      i := 1;
      try
        repeat
          cmnt := GetComment(Strings[i]);
          if cmnt <> '' then Comments.Add(cmnt);
          if (Pos(';', Trim(Strings[i])) <> 1) and (Trim(Strings[i]) <> '') then
            if GetParameters(Strings[i], a, b, c, d, e, f, p) then
            begin
              MainCp.xform[sTransforms].c[0][0] := a;
              MainCp.xform[sTransforms].c[0][1] := c;
              MainCp.xform[sTransforms].c[1][0] := b;
              MainCp.xform[sTransforms].c[1][1] := d;
              MainCp.xform[sTransforms].c[2][0] := e;
              MainCp.xform[sTransforms].c[2][1] := f;
              MainCp.xform[sTransforms].density := p;
              inc(sTransforms);
            end
            else
              EFormatInvalid.Create('Insufficient parameters.');
          inc(i);
        until (Pos('}', Strings[i]) <> 0) or (sTransforms = NXFORMS);
      except on E: EMathError do
      end;
      if sTransforms < 2 then
        raise EFormatInvalid.Create('Insufficient parameters.');
      MainCp.name := sTitle;
      Transforms := sTransforms;
      for i := 1 to Transforms - 1 do
        MainCp.xform[i].color := 0;
      MainCp.xform[0].color := 1;

    except on E: EFormatInvalid do
      begin
        Application.MessageBox(PChar(TextByKey('common-invalidformat')), PChar('Apophysis'), 16);
      end;
    end;
  finally
    Strings.Free;
    Comments.Free;
  end;
end;


function SaveIFS(cp: TControlPoint; Title, FileName: string): boolean;
{ Saves IFS parameters to end of file }
var
  a, b, c: double;
  d, e, f, p: double;
  m: integer;
  IFile: TextFile;
begin
  Result := True;
  try
    AssignFile(IFile, FileName);
    if FileExists(FileName) then
    begin
      if EntryExists(Title, FileName) then DeleteEntry(Title, FileName);
      Append(IFile);
    end
    else
      ReWrite(IFile);
    WriteLn(IFile, Title + ' {');
    for m := 0 to Transforms - 1 do
    begin
      a := cp.xform[m].c[0][0];
      c := cp.xform[m].c[0][1];
      b := cp.xform[m].c[1][0];
      d := cp.xform[m].c[1][1];
      e := cp.xform[m].c[2][0];
      f := cp.xform[m].c[2][1];
      p := cp.xform[m].density;
      Write(IFile, Format('%.6g %.6g %.6g %.6g %.6g %.6g %.6g',
        [a, b, c, d, e, f, p]));
      WriteLn(IFile, '');
    end;
    WriteLn(IFile, '}');
    WriteLn(IFile, ' ');
    CloseFile(IFile);
  except on E: EInOutError do
    begin
      Application.MessageBox(PChar(Format(TextByKey('common-genericsavefailure'), [FileName])), 'Apophysis', 16);
      Result := False;
    end;
  end;
end;

function TMainForm.SaveFlame(cp1: TControlPoint; title, filename: string): boolean;
{ Saves Flame parameters to end of file }
var
  IFile: TextFile;
  sl: TStringList;
  i: integer;
begin
  Result := True;
  try
    AssignFile(IFile, filename);
    if FileExists(filename) then
    begin
      if EntryExists(title, filename) then DeleteEntry(title, fileName);
      Append(IFile);
    end
    else ReWrite(IFile);

    sl := TStringList.Create;
    try
      cp1.SaveToStringList(sl);
      WriteLn(IFile, title + ' {');
      write(IFile, sl.Text);
      WriteLn(IFile, 'palette:');
      for i := 0 to 255 do
      begin
        WriteLn(IFile, IntToStr(cp1.cmap[i][0]) + ' ' +
                       IntToStr(cp1.cmap[i][1]) + ' ' +
                       IntToStr(cp1.cmap[i][2]))
      end;
      WriteLn(IFile, ' }');
    finally
      sl.free
    end;
    WriteLn(IFile, ' ');
    CloseFile(IFile);

  except on EInOutError do
    begin
      Application.MessageBox(PChar(Format(TextByKey('common-genericsavefailure'), [FileName])), 'Apophysis', 16);
      Result := False;
    end;
  end;
end;

function ColorToXmlCompact(cp1: TControlPoint): string;
var
  i: integer;
begin
  Result := '   <palette count="256" format="RGB">';
  for i := 0 to 255 do  begin
    if ((i and 7) = 0) then Result := Result + #13#10 + '      ';
    Result := Result + IntToHex(cp1.cmap[i, 0],2)
                     + IntToHex(cp1.cmap[i, 1],2)
                     + IntToHex(cp1.cmap[i, 2],2);
  end;
  Result := Result + #13#10 + '   </palette>';
end;


function ColorToXml(cp1: TControlPoint): string;
var
  i: integer;
begin
  Result := '';
  for i := 0 to 255 do  begin
    Result := Result + '   <color index="' + IntToStr(i) +
      '" rgb="' + IntToStr(cp1.cmap[i, 0]) + ' ' +
                  IntToStr(cp1.cmap[i, 1]) + ' ' +
                  IntToStr(cp1.cmap[i, 2]) + '"/>' + #13#10;
  end;
end;


function FlameToXMLAS(const cp1: TControlPoint; title: string; exporting: boolean): string;
var
  t, i{, j}: integer;
  FileList: TStringList;
  x, y: double;
  parameters: string;
  curves, str: string;
begin
  FileList := TStringList.create;
  x := cp1.center[0];
  y := cp1.center[1];

//  if cp1.cmapindex >= 0 then pal := pal + 'gradient="' + IntToStr(cp1.cmapindex) + '" ';

  try
    parameters := 'version="' + AppVersionString + '" ';
    if cp1.time <> 0 then
      parameters := parameters + format('time="%g" ', [cp1.time]);

    parameters := parameters +
      'size="' + IntToStr(cp1.width) + ' ' + IntToStr(cp1.height) +
      format('" center="%g %g" ', [x, y]) +
      format('scale="%g" ', [cp1.pixels_per_unit]);

    if cp1.FAngle <> 0 then
      parameters := parameters + format('angle="%g" ', [cp1.FAngle]) +
                                 format('rotate="%g" ', [-180 * cp1.FAngle/Pi]);
    if cp1.zoom <> 0 then
      parameters := parameters + format('zoom="%g" ', [cp1.zoom]);

// 3d
    if cp1.cameraPitch <> 0 then
      parameters := parameters + format('cam_pitch="%g" ', [cp1.cameraPitch]);
    if cp1.cameraYaw <> 0 then
      parameters := parameters + format('cam_yaw="%g" ', [cp1.cameraYaw]);
    if cp1.cameraPersp <> 0 then
      parameters := parameters + format('cam_perspective="%g" ', [cp1.cameraPersp]);
    if cp1.cameraZpos <> 0 then
      parameters := parameters + format('cam_zpos="%g" ', [cp1.cameraZpos]);
    if cp1.cameraDOF <> 0 then
      parameters := parameters + format('cam_dof="%g" ', [cp1.cameraDOF]);
//
    parameters := parameters + format(
             'oversample="%d" filter="%g" quality="%g" ',
             [cp1.spatial_oversample,
              cp1.spatial_filter_radius,
              cp1.sample_density]
             );
    if cp1.nbatches <> 1 then parameters := parameters + 'batches="' + IntToStr(cp1.nbatches) + '" ';

    parameters := parameters +
      format('background="%g %g %g" ', [cp1.background[0] / 255, cp1.background[1] / 255, cp1.background[2] / 255]) +
      format('brightness="%g" ', [cp1.brightness]) +
      format('gamma="%g" ', [cp1.gamma]);

    if cp1.vibrancy <> 1 then
      parameters := parameters + format('vibrancy="%g" ', [cp1.vibrancy]);

    if cp1.gamma_threshold <> 0 then
      parameters := parameters + format('gamma_threshold="%g" ', [cp1.gamma_threshold]);

    if cp1.soloXform >= 0 then
      parameters := parameters + format('soloxform="%d" ', [cp1.soloXform]);

    parameters := parameters +
      format('estimator_radius="%g" ', [cp1.estimator]) +
      format('estimator_minimum="%g" ', [cp1.estimator_min]) +
      format('estimator_curve="%g" ', [cp1.estimator_curve]);
    if (cp1.enable_de) then
      parameters := parameters + ('enable_de="1" ')
    else parameters := parameters + ('enable_de="0" ');

    str := '';
    for i := 0 to cp1.used_plugins.Count-1 do begin
      str := str + cp1.used_plugins[i];
      if (i = cp1.used_plugins.Count-1) then break;
      str := str + ' ';
    end;
    parameters := parameters + format('plugins="%s" new_linear="1" ', [str]);

    for i := 0 to 3 do
    begin
      curves := curves + FloatToStr(cp1.curvePoints[i][0].x) + ' ';
      curves := curves + FloatToStr(cp1.curvePoints[i][0].y) + ' ';
      curves := curves + FloatToStr(cp1.curveWeights[i][0]) + ' ';

      curves := curves + FloatToStr(cp1.curvePoints[i][1].x) + ' ';
      curves := curves + FloatToStr(cp1.curvePoints[i][1].y) + ' ';
      curves := curves + FloatToStr(cp1.curveWeights[i][1]) + ' ';

      curves := curves + FloatToStr(cp1.curvePoints[i][2].x) + ' ';
      curves := curves + FloatToStr(cp1.curvePoints[i][2].y) + ' ';
      curves := curves + FloatToStr(cp1.curveWeights[i][2]) + ' ';

      curves := curves + FloatToStr(cp1.curvePoints[i][3].x) + ' ';
      curves := curves + FloatToStr(cp1.curvePoints[i][3].y) + ' ';
      curves := curves + FloatToStr(cp1.curveWeights[i][3]) + ' ';
    end;

    curves := trim(curves);
    parameters := parameters + format('curves="%s" ', [curves]);

    FileList.Add('<flame name="' + title + '" ' + parameters + '>');
   { Write transform parameters }
    t := cp1.NumXForms;
    for i := 0 to t - 1 do
      FileList.Add(cp1.xform[i].ToXMLString);
    if cp1.HasFinalXForm then
    begin
      // 'enabled' flag disabled in this release
      FileList.Add(cp1.xform[t].FinalToXMLString(cp1.finalXformEnabled));
    end;

    { Write palette data }
    if exporting or OldPaletteFormat then
      FileList.Add(ColorToXml(cp1))
    else
      FileList.Add(ColorToXmlCompact(cp1));

    FileList.Add('</flame>');
    result := FileList.text;
  finally
    FileList.free
  end;
end;

function GetThumbnailBase64(const cp1: TControlPoint) : string;
var
  st: TMemoryStream;
  tempcp : TControlPoint;
  render : TRenderer;
  buffer : array of byte;
  base64 : string;
  size   : integer;
  bmp    : TJPegImage;
  w, h, r: double;
begin
  w := cp1.Width; h := cp1.Height; r := w / h;
  if (w < h) then begin
    w := r * ThumbnailSize;
    h := ThumbnailSize;
  end else if (w > h) then begin
    h := ThumbnailSize / r;
    w := ThumbnailSize;
  end else begin
    w := ThumbnailSize;
    h := ThumbnailSize;
  end;

  render := TRenderer.Create;
  tempcp := TControlPoint.create;
  tempcp.Copy(cp1);

  tempcp.AdjustScale(round(w), round(h));
  tempcp.Width := round(w);
  tempcp.Height := round(h);
  tempcp.spatial_oversample := defOversample;
  tempcp.spatial_filter_radius := defFilterRadius;
  tempcp.sample_density := 10;

  render.SetCP(tempcp);
  render.Render;

  st := TMemoryStream.Create;
  bmp := TJpegImage.Create;
  bmp.Assign(render.GetImage);
  bmp.SaveToStream(st);
  size := st.Size;
  SetLength(buffer, size);
  st.Seek(0, soBeginning);

  st.ReadBuffer(buffer[0], length(buffer));
  base64 := B64Encode(TBinArray(buffer), length(buffer));

  tempcp.Free;
  render.Free;
  st.Free;
  bmp.Free;

  result := base64;
end;

function FlameToXML(const cp1: TControlPoint; exporting, embedthumb: boolean): String;
var
  t, i{, j}, pos: integer;
  FileList: TStringList;
  x, y: double;
  parameters: string;
  curves, str, buf, xdata: string;
begin
  FileList := TStringList.create;
  x := cp1.center[0];
  y := cp1.center[1];

//  if cp1.cmapindex >= 0 then pal := pal + 'gradient="' + IntToStr(cp1.cmapindex) + '" ';

  try
    parameters := 'version="' + AppVersionString + '" ';
    if cp1.time <> 0 then
      parameters := parameters + format('time="%g" ', [cp1.time]);

    parameters := parameters +
      'size="' + IntToStr(cp1.width) + ' ' + IntToStr(cp1.height) +
      format('" center="%g %g" ', [x, y]) +
      format('scale="%g" ', [cp1.pixels_per_unit]);

    if cp1.FAngle <> 0 then
      parameters := parameters + format('angle="%g" ', [cp1.FAngle]) +  // !?!?!?
                                 format('rotate="%g" ', [-180 * cp1.FAngle/Pi]);
    if cp1.zoom <> 0 then
      parameters := parameters + format('zoom="%g" ', [cp1.zoom]);

// 3d
    if cp1.cameraPitch <> 0 then
      parameters := parameters + format('cam_pitch="%g" ', [cp1.cameraPitch]);
    if cp1.cameraYaw <> 0 then
      parameters := parameters + format('cam_yaw="%g" ', [cp1.cameraYaw]);
    if cp1.cameraPersp <> 0 then
      parameters := parameters + format('cam_perspective="%g" ', [cp1.cameraPersp]);
    if cp1.cameraZpos <> 0 then
      parameters := parameters + format('cam_zpos="%g" ', [cp1.cameraZpos]);
    if cp1.cameraDOF <> 0 then
      parameters := parameters + format('cam_dof="%g" ', [cp1.cameraDOF]);
//
    parameters := parameters + format(
             'oversample="%d" filter="%g" quality="%g" ',
             [cp1.spatial_oversample,
              cp1.spatial_filter_radius,
              cp1.sample_density]
             );
    if cp1.nbatches <> 1 then parameters := parameters + 'batches="' + IntToStr(cp1.nbatches) + '" ';

    parameters := parameters +
      format('background="%g %g %g" ', [cp1.background[0] / 255, cp1.background[1] / 255, cp1.background[2] / 255]) +
      format('brightness="%g" ', [cp1.brightness]) +
      format('gamma="%g" ', [cp1.gamma]);

    if cp1.vibrancy <> 1 then
      parameters := parameters + format('vibrancy="%g" ', [cp1.vibrancy]);

    if cp1.gamma_threshold <> 0 then
      parameters := parameters + format('gamma_threshold="%g" ', [cp1.gamma_threshold]);

    if cp1.soloXform >= 0 then
      parameters := parameters + format('soloxform="%d" ', [cp1.soloXform]);

    //
    parameters := parameters +
      format('estimator_radius="%g" ', [cp1.estimator]) +
      format('estimator_minimum="%g" ', [cp1.estimator_min]) +
      format('estimator_curve="%g" ', [cp1.estimator_curve]);
    if exporting then parameters := parameters +
      format('temporal_samples="%d" ', [cp1.jitters]);
    if (cp1.enable_de) then
      parameters := parameters + ('enable_de="1" ')
    else parameters := parameters + ('enable_de="0" ');

    str := '';
    for i := 0 to cp1.used_plugins.Count-1 do begin
      str := str + cp1.used_plugins[i];
      if (i = cp1.used_plugins.Count-1) then break;
      str := str + ' ';
    end;
    parameters := parameters + format('plugins="%s" new_linear="1" ', [str]);

    for i := 0 to 3 do
    begin
      curves := curves + FloatToStr(cp1.curvePoints[i][0].x) + ' ';
      curves := curves + FloatToStr(cp1.curvePoints[i][0].y) + ' ';
      curves := curves + FloatToStr(cp1.curveWeights[i][0]) + ' ';

      curves := curves + FloatToStr(cp1.curvePoints[i][1].x) + ' ';
      curves := curves + FloatToStr(cp1.curvePoints[i][1].y) + ' ';
      curves := curves + FloatToStr(cp1.curveWeights[i][1]) + ' ';

      curves := curves + FloatToStr(cp1.curvePoints[i][2].x) + ' ';
      curves := curves + FloatToStr(cp1.curvePoints[i][2].y) + ' ';
      curves := curves + FloatToStr(cp1.curveWeights[i][2]) + ' ';

      curves := curves + FloatToStr(cp1.curvePoints[i][3].x) + ' ';
      curves := curves + FloatToStr(cp1.curvePoints[i][3].y) + ' ';
      curves := curves + FloatToStr(cp1.curveWeights[i][3]) + ' ';
    end;

    curves := trim(curves);
    parameters := parameters + format('curves="%s" ', [curves]);

    FileList.Add('<flame name="' + CleanXMLName(cp1.name) + '" ' + parameters + '>');
   { Write transform parameters }
    t := cp1.NumXForms;
    for i := 0 to t - 1 do
      FileList.Add(cp1.xform[i].ToXMLString);
    if cp1.HasFinalXForm then
    begin
      // 'enabled' flag disabled in this release
      FileList.Add(cp1.xform[t].FinalToXMLString(cp1.finalXformEnabled));
    end;

    if (embedthumb and EmbedThumbnails) then begin
      xdata := GetThumbnailBase64(cp1);
      buf := '';
      for i := 1 to length(xdata) do begin
        buf := buf + xdata[i];
        if (length(buf) = 150) then begin
          FileList.Add('   <xdata content="' + buf + '" />');
          buf := '';
        end;
      end;
      if (Length(buf) > 0) then FileList.Add('   <xdata content="' + buf + '" />');
    end;

    { Write palette data }
    if exporting or OldPaletteFormat then
      FileList.Add(ColorToXml(cp1))
    else
      FileList.Add(ColorToXmlCompact(cp1));

    FileList.Add('</flame>');
    result := FileList.text;
  finally
    FileList.free
  end;
end;

function RemoveExt(filename: string): string;
var
  ext: string;
  p: integer;
begin
  filename := ExtractFileName(filename);
  ext := ExtractFileExt(filename);
  p := Pos(ext, filename);
  Result := Copy(filename, 0, p - 1);
end;

function XMLEntryExists(title, filename: string): boolean;
var
  FileList: TStringList;
begin

  Result := false;
  if FileExists(filename) then
  begin
    FileList := TStringList.Create;
    try
      FileList.LoadFromFile(filename);
      if pos('<flame name="' + title + '"', FileList.Text) <> 0 then Result := true;
    finally
      FileList.Free;
    end
  end else
    result := false;
end;

procedure DeleteXMLEntry(title, filename: string);
var
  Strings: TStringList;
  p, i: integer;
begin
  Strings := TStringList.Create;
  try
    i := 0;
    Strings.LoadFromFile(FileName);
    while Pos('name="' + title + '"', Trim(Strings[i])) = 0 do
      inc(i);

    p := 0;
    while p = 0 do
    begin
      p := Pos('</flame>', Strings[i]);
      Strings.Delete(i);
    end;
    Strings.SaveToFile(FileName);
  finally
    Strings.Free;
  end;
end;


function TMainForm.SaveXMLFlame(const cp1: TControlPoint; title, filename: string): boolean;
{ Saves Flame parameters to end of file }
var
  Tag: string;
  IFile: File;
  FileList: TStringList;
  RB: RawByteString;

  i, p: integer;
  bakname: string;
begin
  Tag := RemoveExt(filename);
  Result := True;
  try
    if FileExists(filename) then
    begin
      bakname := ChangeFileExt(filename, '.bak');
      if FileExists(bakname) then DeleteFile(bakname);
      RenameFile(filename, bakname);

      FileList := TStringList.create;
      try
        FileList.LoadFromFile(bakname);

        if Pos('<flame name="' + title + '"', FileList.Text) <> 0 then
        begin
          i := 0;
          while Pos('<flame name="' + title + '"', Trim(FileList[i])) = 0 do
            inc(i);

          p := 0;
          while p = 0 do
          begin
            p := Pos('</flame>', FileList[i]);
            FileList.Delete(i);
          end;
        end;

//      FileList := TStringList.create;
//      try
//        FileList.LoadFromFile(filename);

        // fix first line
        if (FileList.Count > 0) then begin
          FileList[0] := '<flames name="' + Tag + '">';
        end;

        if FileList.Count > 2 then
        begin
          if pos('<flame ', FileList.text) <> 0 then
            repeat
              FileList.Delete(FileList.Count - 1);
            until (Pos('</flame>', FileList[FileList.count - 1]) <> 0)
          else
            repeat
              FileList.Delete(FileList.Count - 1);
            until (Pos('<' + Tag + '>', FileList[FileList.count - 1]) <> 0) or
                  (Pos('</flames>', FileList[FileList.count - 1]) <> 0);
        end else
        begin
          FileList.Delete(FileList.Count - 1);
        end;

        FileList.Add(Trim(FlameToXML(cp1, false, true)));
        FileList.Add('</flames>');
        FileList.SaveToFile(filename);

      finally
        if FileExists(bakname) and not FileExists(filename) then
          RenameFile(bakname, filename);

        FileList.Free;
      end;
    end
    else
    begin
    // New file ... easy
      FileList := TStringList.Create;
      FileList.Text := '<flames name="' + Tag + '">' + #$0D#$0A +
        FlameToXML(cp1, false, true) + #$0D#$0A + '</flames>';
      FileList.SaveToFile(filename, TEncoding.UTF8);
      FileList.Destroy;
    end;
  except 
    begin
      Application.MessageBox(PChar(Format(TextByKey('common-genericsavefailure'), [FileName])), 'Apophysis', 16);
      Result := False;
    end;
  end;
end;

function TMainForm.SaveGradient(Gradient, Title, FileName: string): boolean;
{ Saves gradient parameters to end of file }
var
  IFile: TextFile;
begin
  Result := True;
  try
    AssignFile(IFile, FileName);
    if FileExists(FileName) then
    begin
      if EntryExists(Title, FileName) then DeleteEntry(Title, FileName);
      Append(IFile);
    end
    else
      ReWrite(IFile);
    Write(IFile, Gradient);
    WriteLn(IFile, ' ');
    CloseFile(IFile);
  except on EInOutError do
    begin
      Application.MessageBox(PChar(Format(TextByKey('common-genericsavefailure'), [FileName])), 'Apophysis', 16);
      Result := False;
    end;
  end;
end;

function RenameIFS(OldIdent: string; var NewIdent: string): boolean;
{ Renames an IFS parameter set in a file }
var
  Strings: TStringList;
  p, i: integer;
  s: string;
begin
  Result := True;
  NewIdent := CleanEntry(NewIdent);
  Strings := TStringList.Create;
  try
    try
      i := 0;
      Strings.LoadFromFile(OpenFile);
      if Pos(OldIdent + ' ', Trim(Strings.Text)) <> 0 then
      begin
        while Pos(OldIdent + ' ', Trim(Strings[i])) <> 1 do
        begin
          inc(i);
        end;
        p := Pos('{', Strings[i]);
        s := Copy(Strings[i], p, Length(Strings[i]) - p + 1);
        Strings[i] := NewIdent + ' ' + s;
        Strings.SaveToFile(OpenFile);
      end
      else
        Result := False;
    except on Exception do Result := False;
    end;
  finally
    Strings.Free;
  end;
end;

function RenameXML(OldIdent: string; var NewIdent: string): boolean;
{ Renames an XML parameter set in a file }
var
  Strings: TStringList;
  i: integer;
  bakname: string;
begin
  Result := True;
  Strings := TStringList.Create;
  try
    try
      i := 0;
      Strings.LoadFromFile(OpenFile);
      if Pos('name="' + OldIdent + '"', Strings.Text) <> 0 then
      begin
        while Pos('name="' + OldIdent + '"', Strings[i]) = 0 do
        begin
          inc(i);
        end;
        Strings[i] := StringReplace(Strings[i], OldIdent, NewIdent, []);

        bakname := ChangeFileExt(OpenFile, '.bak');
        if FileExists(bakname) then DeleteFile(bakname);
        RenameFile(OpenFile, bakname);

        Strings.SaveToFile(OpenFile);
      end
      else
        Result := False;
    except on Exception do Result := False;
    end;
  finally
    Strings.Free;
  end;
end;


procedure ListIFS(FileName: string; sel: integer);
{ List identifiers in file }
var
  i, p: integer;
  Title: string;
  ListItem: TListItem;
  FStrings: TStringList;
begin
  MainForm.ParseLoadingBatch := true;
  FStrings := TStringList.Create;
  FStrings.LoadFromFile(FileName);
  try
    MainForm.ListView.Items.BeginUpdate;
    MainForm.ListView.Items.Clear;
    if (Pos('{', FStrings.Text) <> 0) then
    begin
      for i := 0 to FStrings.Count - 1 do
      begin
        p := Pos('{', FStrings[i]);
        if (p <> 0) and (Pos('(3D)', FStrings[i]) = 0) then
        begin
          Title := Trim(Copy(FStrings[i], 1, p - 1));
          if Title <> '' then
          begin { Otherwise bad format }
            ListItem := MainForm.ListView.Items.Add;
            Listitem.Caption := Trim(Copy(FStrings[i], 1, p - 1));
          end;
        end;
      end;
    end;
    MainForm.ListView.Items.EndUpdate;
    case sel of
      0: MainForm.ListView.Selected := MainForm.ListView.Items[MainForm.ListView.Items.Count - 1];
      1: MainForm.ListView.Selected := MainForm.ListView.Items[0];
    end;
  finally
    FStrings.Free;
  end;
  MainForm.ParseLoadingBatch := false;
end;

procedure ListFlames(FileName: string; sel: integer);
{ List identifiers in file }
var
  i, p: integer;
  Title: string;
  ListItem: TListItem;
  FStrings: TStringList;
begin
  FStrings := TStringList.Create;
  FStrings.LoadFromFile(FileName);
  try
    MainForm.ListView.Items.BeginUpdate;
    MainForm.ListView.Items.Clear;
    if (Pos('{', FStrings.Text) <> 0) then
    begin
      for i := 0 to FStrings.Count - 1 do
      begin
        p := Pos('{', FStrings[i]);
        if (p <> 0) then
        begin
          Title := Trim(Copy(FStrings[i], 1, p - 1));
          if Title <> '' then
          begin { Otherwise bad format }
            ListItem := MainForm.ListView.Items.Add;
            Listitem.Caption := Trim(Copy(FStrings[i], 1, p - 1));
          end;
        end;
      end;
    end;
    MainForm.ListView.Items.EndUpdate;
    if sel = 1 then MainForm.ListView.Selected := MainForm.ListView.Items[0];
  finally
    FStrings.Free;
  end;
end;

{ ****************************** Display ************************************ }

procedure Trace1(const str: string);
begin
  if TraceLevel >= 1 then
    TraceForm.MainTrace.Lines.Add('. ' + str);
end;

procedure Trace2(const str: string);
begin
  if TraceLevel >= 2 then
    TraceForm.MainTrace.Lines.Add('. . ' + str);
end;

procedure TMainForm.HandleThreadCompletion(var Message: TMessage);
var
  oldscale: double;
begin
  Trace2(MsgComplete + IntToStr(message.LParam));
  if not Assigned(Renderer) then begin
    Trace2(MsgNotAssigned);
    exit;
  end;
  if Renderer.ThreadID <> message.LParam then begin
    Trace2(MsgAnotherRunning);
    exit;
  end;
  Image.Cursor := crDefault;

  if assigned(FViewImage) then begin
    oldscale := FViewImage.Width / Image.Width;
    FViewImage.Free;
  end
  else oldscale := FViewScale;

  FViewImage := Renderer.GetTransparentImage;

  if (FViewImage <> nil) and (FViewImage.Width > 0) then begin
    FViewScale := FViewImage.Width / Image.Width;

    FViewPos.X := FViewScale/oldscale * (FViewPos.X - FViewOldPos.X);
    FViewPos.Y := FViewScale/oldscale * (FViewPos.Y - FViewOldPos.Y);

    DrawImageView;
{
    case FMouseMoveState of
      msZoomWindowMove: FMouseMoveState := msZoomWindow;
      msZoomOutWindowMove: FMouseMoveState := msZoomOutWindow;
//    msDragMove: FMouseMoveState := msDrag;
      msRotateMove: FMouseMoveState := msRotate;
    end;
}
    if FMouseMoveState in [msZoomWindowMove, msZoomOutWindowMove, msRotateMove] then
      DrawSelection := false;

    Trace1(TimeToStr(Now) + ' : Render complete');
    Renderer.ShowSmallStats;
  end
  else Trace2('WARNING: No image rendered!');

  Renderer.WaitFor;
  Trace2('Destroying RenderThread #' + IntToStr(Renderer.ThreadID));
  Renderer.Free;
  Renderer := nil;
  Trace1('');
end;

procedure TMainForm.HandleThreadTermination(var Message: TMessage);
begin
  Trace2(MsgTerminated + IntToStr(message.LParam));
  if not Assigned(Renderer) then begin
    Trace2(MsgNotAssigned);
    exit;
  end;
  if Renderer.ThreadID <> message.LParam then begin
    Trace2(MsgAnotherRunning);
    exit;
  end;
  Image.Cursor := crDefault;
  Trace2('  Render aborted');

  Trace2('Destroying RenderThread #' + IntToStr(Renderer.ThreadID));
  Renderer.Free;
  Renderer := nil;
  Trace1('');
end;

procedure TMainForm.DrawPreview;
var
  cp : TControlPoint;
  Render : TRenderer;
  BM:TBitmap;
begin
  Render := TRenderer.Create;
  bm := TBitmap.Create;
  Render := TRenderer.Create;

  cp := MainCP.Clone;

  cp.sample_density := 1;
  cp.spatial_oversample := 1;
  cp.spatial_filter_radius := 1;

  Render.NrThreads := NrTreads;
  Render.SetCP(cp);
  Render.Render;
  BM.Assign(Render.GetImage);
  Image.Picture.Graphic := bm;
end;

procedure TMainForm.DrawFlame;
var
  GlobalMemoryInfo: TMemoryStatus; // holds the global memory status information
  RenderCP: TControlPoint;
  Mem, ApproxMem: cardinal;
  bs: integer;
begin
  RedrawTimer.Enabled := False;
  if Assigned(Renderer) then begin
    assert(Renderer.Suspended = false);

    Trace2('Killing previous RenderThread #' + inttostr(Renderer.ThreadID));
    Renderer.Terminate;
    Renderer.WaitFor;
    Trace2('Destroying RenderThread #' + IntToStr(Renderer.ThreadID));

    Renderer.Free;
    Renderer := nil;
  end;

  if not Assigned(Renderer) then
  begin
    if EditForm.Visible and ((MainCP.Width / MainCP.Height) <> (EditForm.cp.Width / EditForm.cp.Height))
      then EditForm.UpdateDisplay(true); // preview only?
    if AdjustForm.Visible then AdjustForm.UpdateDisplay(true); // preview only!

    RenderCP := MainCP.Clone;
    RenderCp.AdjustScale(Image.width, Image.height);

    // following needed ?
//    cp.Zoom := Zoom;
//    cp.center[0] := center[0];
//    cp.center[1] := center[1];

    RenderCP.sample_density := defSampleDensity;
    // oversample and filter are just slowing us down here...
    RenderCP.spatial_oversample := 1; // defOversample;
    RenderCP.spatial_filter_radius := 0.001; {?} //defFilterRadius;
    RenderCP.Transparency := true; // always generate transparency here

    GlobalMemoryInfo.dwLength := SizeOf(GlobalMemoryInfo);
    GlobalMemoryStatus(GlobalMemoryInfo);
    Mem := GlobalMemoryInfo.dwAvailPhys;

    if (singleBuffer) then bs := 16
    else bs := 32;

//    if Output.Lines.Count >= 1000 then Output.Lines.Clear;
    Trace1('--- Previewing "' + RenderCP.name + '" ---');
    Trace1(Format('  Available memory: %f Mb', [Mem / (1024*1024)]));
    ApproxMem := int64(RenderCp.Width) * int64(RenderCp.Height) {* sqr(Oversample)}
                 * (bs + 4 + 4); // +4 for temp image(s)...?
    assert(MainPreviewScale <> 0);
    if ApproxMem * sqr(MainPreviewScale) < Mem then begin
      if ExtendMainPreview then begin
        RenderCP.sample_density := RenderCP.sample_density / sqr(MainPreviewScale);
        RenderCP.Width := round(RenderCp.Width * MainPreviewScale);
        RenderCP.Height := round(RenderCp.Height * MainPreviewScale);
      end;
    end
    else Trace1('WARNING: Not enough memory for extended preview!');
    if ApproxMem > Mem then
      Trace1('OUTRAGEOUS: Not enough memory even for normal preview! :-(');
    Trace1(Format('  Size: %dx%d, Quality: %f',
                  [RenderCP.Width, RenderCP.Height, RenderCP.sample_density]));
    FViewOldPos.x := FViewPos.x;
    FViewOldPos.y := FViewPos.y;
    StartTime := Now;
    try
      Renderer := TRenderThread.Create;
      Renderer.TargetHandle := MainForm.Handle;
      if TraceLevel > 0 then Renderer.Output := TraceForm.MainTrace.Lines;
      Renderer.OnProgress := OnProgress;
      Renderer.SetCP(RenderCP);
      Renderer.NrThreads := FNrThreads;

      Trace2('Starting RenderThread #' + inttostr(Renderer.ThreadID));
      Renderer.Resume;

      Image.Cursor := crAppStart;
    except
      Trace1('ERROR: Cannot start renderer!');
    end;
    RenderCP.Free;

  end;
end;

{ ************************** IFS and triangle stuff ************************* }

function FlameToString(Title: string): string;
{ Creates a string containing the formated flame parameter set }
var
  I: integer;
  sl, Strings: TStringList;
begin
  Strings := TStringList.Create;
  sl := TStringList.Create;
  try
    Strings.Add(CleanEntry(Title) + ' {');
    MainCp.SaveToStringList(sl);
    Strings.Add(sl.text);
    Strings.Add('palette:');
    for i := 0 to 255 do
    begin
      Strings.Add(IntToStr(MainCp.cmap[i][0]) + ' ' +
        IntToStr(MainCp.cmap[i][1]) + ' ' +
        IntToStr(MainCp.cmap[i][2]))
    end;
    Strings.Add('}');
    Result := Strings.Text;
  finally
    sl.Free;
    Strings.Free;
  end;
end;

procedure TMainForm.RandomBatch;
{ Write a series of random ifs to a file }
var
  i: integer;
  F: TextFile;
  b, RandFile: string;
begin
  b := IntToStr(BatchSize);
  inc(MainSeed);
  RandSeed := MainSeed;
  try
    AssignFile(F, GetEnvVarValue('APPDATA') + '\' + randFilename);
    OpenFile := GetEnvVarValue('APPDATA') + '\' + randFilename;
    ReWrite(F);
    WriteLn(F, '<random_batch>');
    for i := 0 to BatchSize - 1 do
    begin
      inc(RandomIndex);
      Statusbar.SimpleText := Format(TextByKey('main-status-batchgenerate'), [(i+1), b]);
      RandSeed := MainSeed;
      if randGradient = 0 then cmap_index := random(NRCMAPS);
      inc(MainSeed);
      RandSeed := MainSeed;
      RandomizeCP(MainCp);
      MainCp.CalcBoundbox;

(*     Title := RandomPrefix + RandomDate + '-' +
        IntToStr(RandomIndex);
  *)
      MainCp.name := RandomPrefix + RandomDate + '-' +
        IntToStr(RandomIndex);
      Write(F, FlameToXML(MainCp, False, false));
//      Write(F, FlameToString(Title));
//      WriteLn(F, ' ');
    end;
    Write(F, '</random_batch>');
    CloseFile(F);
  except
    on EInOutError do Application.MessageBox(PChar(TextByKey('main-status-batcherror')), PChar('Apophysis'), 16);
  end;
  RandFile := GetEnvVarValue('APPDATA') + '\' + randFilename;
  MainCp.name := '';
end;

{ ******************************** Menu ************************************ }

function LoadXMLFlameText(filename, name: string) : string;
var
  i, p: integer;
  FileStrings: TStringList;
  ParamStrings: TStringList;
  Tokens: TStringList;
  time: integer;
begin
  time := -1;
  FileStrings := TStringList.Create;
  ParamStrings := TStringList.Create;
  Result := '';

  if pos('*untitled', name) <> 0 then
  begin
    Tokens := TStringList.Create;
    GetTokens(name, tokens);
    time := StrToInt(tokens[1]);
    Tokens.free;
  end;
  try
    if UpperCase(ExtractFileExt(filename)) = '.PNG' then
    else
      FileStrings.LoadFromFile(filename);

    for i := 0 to FileStrings.Count - 1 do
    begin
      pname := '';
      ptime := '';
      p := Pos('<flame ', LowerCase(FileStrings[i]));
      if (p <> 0) then
      begin
        MainForm.ListXMLScanner.LoadFromBuffer(TCharType(TStringType(FileStrings[i])));
        MainForm.ListXMLScanner.Execute;
        if pname <> '' then
        begin
          if (Trim(pname) = Trim(name)) then
          begin
            ParamStrings.Add(FileStrings[i]);
            Break;
          end;
        end
        else
        begin
          if ptime <> '' then
          begin
            if StrToInt(ptime) = time then
            begin
              ParamStrings.Add(FileStrings[i]);
              Break;
            end;
          end;
        end;
      end;
    end;
    repeat
      inc(i);
      ParamStrings.Add(FileStrings[i]);
    until pos('</flame>', Lowercase(FileStrings[i])) <> 0;

    Result := ParamStrings.Text;

  finally
    FileStrings.free;
    ParamStrings.free;
  end;
end;

procedure AddThumbnail(renderer : TRenderer; width, height : double);
var
  Bmp: TBitmap;
  x, y : double;
begin
  Bmp := TBitmap.Create;
  Bmp.PixelFormat := pf24bit;
  Bmp.HandleType := bmDIB;
  Bmp.Width := ThumbnailSize;
  Bmp.Height := ThumbnailSize;

  x := ThumbnailSize / 2;
  y := ThumbnailSize / 2;

  x := x - width / 2;
  y := y - height / 2;

  with Bmp.Canvas do begin
    Brush.Color := GetSysColor(5); // window background
    FillRect(Rect(0, 0, Bmp.Width, Bmp.Height));
    Draw(round(x), round(y), renderer.GetImage);
  end;

  MainForm.UsedThumbnails.Add(bmp, nil);
  
  if (Bmp <> nil) then Bmp.Free;
end;

function ScanVariations(name:string):boolean;
var
  i,count:integer;
  vname:string;
begin
  count:=NrVar;
  for i:=0 to count - 1 do
  begin
    vname := VarNames(i);
    if (vname = name) then
    begin
      Result := true;
      exit;
    end;
  end;
  for i := 0 to MainForm.SubstSource.Count - 1 do
  begin
    vname := MainForm.SubstSource[i];
    if (vname = name) then
    begin
      Result := true;
      exit;
    end;
  end;
  Result := false;
end;
function ScanVariables(name:string):boolean;
var
  i,count:integer;
begin
  count:=GetNrVariableNames;
  for i:=0 to count - 1 do
  begin
    if (GetVariableNameAt(i) = name) then
    begin
      Result := true;
      exit;
    end;
  end;
  for i := 0 to MainForm.SubstSource.Count - 1 do
  begin
    if (MainForm.SubstSource[i] = name) then
    begin
      Result := true;
      exit;
    end;
  end;
  Result := false;
end;

procedure TMainForm.mnuOpenClick(Sender: TObject);
var
  fn:string;
begin
{$ifdef DisableScripting}
{$else}
  ScriptEditor.Stopped := True;
{$endif}
  OpenDialog.Filter := TextByKey('common-filter-flamefiles') + '|*.flame;*.xml|' + TextByKey('common-filter-allfiles') + '|*.*';
  OpenDialog.InitialDir := ParamFolder;
  OpenDialog.FileName := '';
  if OpenSaveFileDialog(MainForm, '.flame', OpenDialog.Filter, OpenDialog.InitialDir, TextByKey('common-browse'), fn, true, false, false, true) then
  //if OpenDialog.Execute then
  begin
    OpenDialog.FileName := fn;
    MainForm.CurrentFileName := OpenDialog.FileName;
    LastOpenFile := OpenDialog.FileName;
    Maincp.name := '';
    ParamFolder := ExtractFilePath(OpenDialog.FileName);
    ListView.ReadOnly := False;
    mnuListRename.Enabled := True;
    mnuItemDelete.Enabled := True;
    OpenFile := OpenDialog.FileName;
    //MainForm.Caption := AppVersionString + ' - ' + OpenFile; // --Z--
    if APP_BUILD = '' then MainForm.Caption := AppVersionString + ' - ' + openFile
    else MainForm.Caption := AppVersionString + ' ' + APP_BUILD + ' - ' + openFile;
    OpenFileType := ftXML;
    (*if UpperCase(ExtractFileExt(OpenDialog.FileName)) = '.IFS' then
    begin
      OpenFileType := ftIfs;
      Variation := vLinear;
      VarMenus[0].Checked := True;
    end;
    if (UpperCase(ExtractFileExt(OpenDialog.FileName)) = '.FLA') or
      (UpperCase(ExtractFileExt(OpenDialog.FileName)) = '.APO') then
      OpenFileType := ftFla;   *)
    if OpenFileType = ftXML then
      ListXML(OpenDialog.FileName, 1)
    else
      ListIFS(OpenDialog.FileName, 1)
  end;
end;

procedure TMainForm.mnuNextClick(Sender: TObject);
begin
  with ListView do
    if Items.Count <> 0 then
      Selected := Items[(Selected.Index + 1) mod Items.Count];
end;

procedure TMainForm.mnuPreviousClick(Sender: TObject);
var
  i: integer;
begin
  with ListView do
    if Items.Count <> 0 then
    begin
      i := Selected.Index - 1;
      if i < 0 then i := Items.Count - 1;
      Selected := Items[i];
    end;
end;

procedure TMainForm.mnuListRenameClick(Sender: TObject);
begin
  if ListView1.SelCount <> 0 then
    ListView1.Items[ListView1.Selected.Index].EditCaption;
end;

procedure TMainForm.mnuCopyUPRClick(Sender: TObject);
begin
  Clipboard.SetTextBuf(PChar(UPRString(MainCp, Maincp.name)));
end;

procedure TMainForm.mnuItemDeleteClick(Sender: TObject);
var
  c: boolean;
begin
  if ListView1.SelCount <> 0 then
  begin
    if ConfirmDelete then
      c := Application.MessageBox(
        PChar(Format(TextByKey('common-confirmdelete'), [ListView1.Selected.Caption])), 'Apophysis', 36) = IDYES
    else
      c := True;
    if c then
      if (*ListView1.Focused and*) (ListView1.SelCount <> 0) then
      begin
        Application.ProcessMessages;
        if OpenFileType = ftXML then
          DeleteXMLEntry(ListView1.Selected.Caption, OpenFile)
        else
          DeleteEntry(ListView1.Selected.Caption, OpenFile);
        if (ListView1.Selected.Index >= 0) and (ListView1.Selected.Index < UsedThumbnails.Count) and (not ClassicListMode) then
          UsedThumbnails.Delete(ListView1.Selected.Index);
        ListView1.Items.Delete(ListView1.Selected.Index);
        Application.ProcessMessages;
        ListView1.Selected := ListView1.ItemFocused;
        //RebuildListView;
        ListXML(OpenFile, ListView1.ItemIndex);
      end;
  end;
//end;
end;

procedure TMainForm.mnuOptionsClick(Sender: TObject);
begin
  OptionsForm.ShowModal;
  // --Z--
  StopThread;
  RedrawTimer.Enabled := True;
  tbQualityBox.Text := FloatToStr(defSampleDensity);
  tbShowAlpha.Down := ShowTransparency;
  DrawImageView;
  UpdateWindows;
end;

procedure TMainForm.mnuRefreshClick(Sender: TObject);
begin
  RedrawTimer.enabled := true;
end;

procedure TMainForm.mnuNormalWeightsClick(Sender: TObject);
begin
  StopThread;
  UpdateUndo;
// TODO: ...something
//  ComputeWeights(MainCp, MainTriangles, transforms);
  RedrawTimer.Enabled := True;
  UpdateWindows;
end;

procedure TMainForm.mnuRWeightsClick(Sender: TObject);
begin
  StopThread;
  UpdateUndo;
  inc(MainSeed);
  RandSeed := MainSeed;
  MainCp.RandomizeWeights;
  RedrawTimer.Enabled := True;
  UpdateWindows;
end;

procedure TMainForm.mnuRandomBatchClick(Sender: TObject);
begin
{$ifdef DisableScripting}
{$else}
  ScriptEditor.Stopped := True;
{$endif}
  inc(MainSeed);
  RandSeed := MainSeed;
  RandomBatch;
  OpenFile := GetEnvVarValue('APPDATA') + '\' + randFilename;
  OpenFileType := ftXML;
  MainForm.Caption := AppVersionString + ' - ' + TextByKey('main-common-randombatch');
  ListXML(OpenFile, 1);
  //ListView.SetFocus;
  if batchsize = 1 then DrawFlame;
end;

function GradientString(c: TColorMap): string;
var
  strings: TStringList;
  i, j, cl: integer;
begin
  strings := TStringList.Create;
  for i := 0 to 255 do
  begin
    j := round(i * (399 / 255));
    cl := (c[i][2] shl 16) + (c[i][1] shl 8) + (c[i][0]);
    strings.Add(' index=' + IntToStr(j) + ' color=' + intToStr(cl));
  end;
  Result := Strings.Text;
  strings.Free;
end;

function TMainForm.UPRString(cp1: TControlPoint; Entry: string): string;
{ Returns a string containing an Ultra Fractal parameter set for copying
  or saving to file }
var
  IterDensity, m, i, j: integer;
  scale, a, b, c, d, e, f, p, v: double;
  GradStrings, Strings: TStringList;
  rept, cby, smap, sol: string;
  uprcenter: array[0..1] of double; // camera center
  Backcolor: longint;
  xf_str: string;
begin
  cp1.Prepare;
  uprcenter[0] := cp1.Center[0];
  uprcenter[1] := cp1.Center[1];
  cp1.Width := UPRWidth;
  cp1.Height := UPRHeight;
  scale := power(2, cp1.zoom) * CalcUPRMagn(cp1);
  cp1.center[0] := uprCenter[0];
  cp1.center[1] := uprCenter[1];
  smap := 'no';
  sol := 'no';
  rept := '';
  cby := 'Hit Frequency';
  Strings := TStringList.Create;
  GradStrings := TStringList.Create;
  try
    Strings.Add(CleanEntry(Entry) + ' {');
    Strings.Add('fractal:');
    Strings.Add('  title="' + CleanUPRTitle(Entry) +
      '" width=' + IntToStr(UPRWidth) + ' height=' + IntToStr(UPRHeight) + ' layers=1');
    Strings.Add('layer:');
    Strings.Add('  method=linear caption="Background" opacity=100 mergemode=normal');
    Strings.Add('mapping:');
    Strings.Add('  center=' + floatToStr(cp1.center[0]) + '/' + floatToStr(-cp1.center[1]) +
      ' magn=' + FloatToStr(scale));
    Strings.Add('formula:');
    Strings.Add('  maxiter=1 filename="' + UPRFormulaFile + '" entry="' + UPRFormulaIdent + '"');
    Strings.Add('inside:');
    Strings.Add('  transfer=none');
    Strings.Add('outside:');
    Strings.Add('  transfer=linear repeat=no ' + 'filename="' + UPRColoringFile + '" entry="'
      + UPRColoringIdent + '"');
    if (UPRAdjustDensity) and (scale > 1) then
      IterDensity := Trunc(UPRSampleDensity * scale * scale)
    else
      IterDensity := UPRSampleDensity;
    Strings.Add('  p_iter_density=' + IntToStr(IterDensity) + ' p_spat_filt_rad=' +
      Format('%.3g', [UPRFilterRadius]) + ' p_oversample=' + IntToStr(UPROversample));
    backcolor := 255 shl 24 + cp1.background[0] shl 16 + cp1.background[1] shl 8 + cp1.background[2];
    Strings.Add('  p_bk_color=' + IntToStr(Backcolor) + ' p_contrast=1' +
      ' p_brightness=' + FloatToStr(cp1.Brightness) + ' p_gamma=' + FloatToStr(cp1.Gamma));
    Strings.Add('  p_white_level=200 p_xforms=' + inttostr(Transforms));
    for m := 0 to Transforms do
    begin
      a := cp1.xform[m].c[0][0];
      c := cp1.xform[m].c[0][1];
      b := cp1.xform[m].c[1][0];
      d := cp1.xform[m].c[1][1];
      e := cp1.xform[m].c[2][0];
      f := cp1.xform[m].c[2][1];
      p := cp1.xform[m].Density;
      if m < Transforms then xf_str := 'p_xf' + inttostr(m)
      else begin
        if cp1.HasFinalXForm = false then break;
        xf_str := 'p_finalxf';
      end;
      Strings.Add('  ' + xf_str + '_p=' + Format('%.6g ', [p]));
      Strings.Add('  ' + xf_str + '_c=' + floatTostr(cp1.xform[m].color));
      Strings.Add('  ' + xf_str + '_sym=' + floatTostr(cp1.xform[m].symmetry));
      Strings.Add('  ' + xf_str + '_cfa=' + Format('%.6g ', [a]) +
        xf_str + '_cfb=' + Format('%.6g ', [b]) +
        xf_str + '_cfc=' + Format('%.6g ', [c]) +
        xf_str + '_cfd=' + Format('%.6g ', [d]));
      Strings.Add('  ' + xf_str + '_cfe=' + Format('%.6g ', [e]) +
        ' ' + xf_str + '_cff=' + Format('%.6g ', [f]));
      for i := 0 to NRVAR-1 do
        if cp1.xform[m].GetVariation(i) <> 0 then begin
          Strings.Add('  ' + xf_str + '_var_' + VarNames(i) + '=' +
            floatToStr(cp1.xform[m].GetVariation(i)));
        for j:= 0 to GetNrVariableNames - 1 do begin
{$ifndef VAR_STR}
          cp1.xform[m].GetVariable(GetVariableNameAt(j), v);
          Strings.Add('  ' + xf_str + '_par_' + GetVariableNameAt(j) + '=' + floatToStr(v));
{$else}
          Strings.Add('  ' + xf_str + '_par_' +
                      GetVariableNameAt(j) + '=' + cp1.xform[m].GetVariableStr(GetVariableNameAt(j)));
{$endif}
        end;
      end;
    end;
    Strings.Add('gradient:');
    Strings.Add(GradientString(cp1.cmap));
    Strings.Add('}');
    UPRString := Strings.Text;
  finally
    GradStrings.Free;
    Strings.Free;
  end;
end;

procedure TMainForm.mnuRandomClick(Sender: TObject);
begin
  StopThread;
  UpdateUndo;
  inc(MainSeed);
  RandomizeCP(MainCp);
  inc(RandomIndex);
  MainCp.name := RandomPrefix + RandomDate + '-' +
    IntToStr(RandomIndex);
  Transforms := MainCp.TrianglesFromCP(MainTriangles);

  if AdjustForm.visible then AdjustForm.UpdateDisplay;

  StatusBar.Panels[3].text := maincp.name;
  ResetLocation;
  RedrawTimer.Enabled := true;
  UpdateWindows;
end;

procedure TMainForm.mnuEqualizeClick(Sender: TObject);
begin
  StopThread;
  UpdateUndo;
  MainCP.EqualizeWeights;
  RedrawTimer.Enabled := True;
  UpdateWindows;
end;

procedure TMainForm.mnuEditorClick(Sender: TObject);
begin
  EditForm.Show;
end;

procedure TMainForm.mnuExitClick(Sender: TObject);
begin
{$ifdef DisableScripting}
{$else}
  ScriptEditor.Stopped := True;
{$endif}
  Close;
end;

procedure TMainForm.mnuSaveUPRClick(Sender: TObject);
{ Write a UPR to a file }
begin
  SaveForm.SaveType := stExportUPR;
  SaveForm.Filename := UPRPath;
  SaveForm.Title := maincp.name;
  if SaveForm.ShowModal = mrOK then
  begin
    UPRPath := SaveForm.FileName;
    SaveUPR(SaveForm.Title, SaveForm.Filename);
  end;
end;

procedure TMainForm.mnuSaveAsClick(Sender: TObject);
{ Save parameters to a file }
begin
  SaveForm.SaveType := stSaveParameters;
  SaveForm.Filename := SavePath;
  SaveForm.Title := maincp.name;
  if SaveForm.ShowModal = mrOK then
  begin
    maincp.name := SaveForm.Title;
    SavePath := SaveForm.Filename;
    if ExtractFileExt(SavePath) = '' then
      SavePath := SavePath + '.flame';
    SaveXMLFlame(maincp, maincp.name, SavePath);
    StatusBar.Panels[3].Text := maincp.name;
    if (SavePath = OpenFile) then ListXML(OpenDialog.FileName, 0);
  end;
end;

procedure TMainForm.mnuSaveAllAsClick(Sender: TObject);
{ Save all parameters to a file }
var
  i, current: integer;
  currentXML : string;
begin
  SaveForm.SaveType := stSaveAllParameters;
  SaveForm.Filename := SavePath;
  if SaveForm.ShowModal = mrOK then
  begin
    SavePath := SaveForm.Filename;
    if ExtractFileExt(SavePath) = '' then 
      SavePath := SavePath + '.flame';
    current := ListView1.ItemIndex;
    currentXML := Trim(FlameToXML(Maincp, false, true));
    for i := 0 to ListView1.Items.Count-1 do
    begin
      // -X- what if there are unsaved changes at the current CP?
      if (i = current) then begin
        ParseXML(maincp, PCHAR(currentXML), true);
        SaveXMLFlame(maincp, maincp.name, SavePath);
      end else begin
        LoadXMLFlame(OpenFile, ListView1.Items.Item[i].Caption);
        SaveXMLFlame(maincp, maincp.name, SavePath);
      end;
    end;
    ListXML(SavePath, 2);
    if (current < 0) then current := 0;
    ListView1.Selected := ListView1.Items[current];
    LoadXMLFlame(SavePath, ListView1.Selected.caption);
  end;
end;

function GradTitle(str: string): string;
var
  p: integer;
begin
  p := pos('{', str);
  GradTitle := Trim(copy(str, 1, p - 1));
end;

procedure TMainForm.DisplayHint(Sender: TObject);
var
  T: TComponent;
begin
  T := MainForm.FindComponent('StatusBar');
  if T <> nil then
    if Application.Hint = '' then
    begin
      TStatusBar(T).SimpleText := '';
      TStatusBar(T).SimplePanel := False;
      TStatusBar(T).Refresh;
    end
    else
      TStatusBar(T).SimpleText := Application.Hint;
end;

procedure TMainForm.MainFileClick(Sender: TObject);
begin
{$ifdef DisableScripting}
{$else}
  ScriptEditor.Stopped := True;
{$endif}
end;

procedure TMainForm.MainViewClick(Sender: TObject);
begin
{$ifdef DisableScripting}
{$else}
  ScriptEditor.Stopped := True;
{$endif}
end;

procedure TMainForm.MainToolsClick(Sender: TObject);
begin
{$ifdef DisableScripting}
{$else}
  ScriptEditor.Stopped := True;
{$endif}
end;

procedure TMainForm.MainHelpClick(Sender: TObject);
begin
end;

{ ********************************* Form ************************************ }
procedure TMainForm.FavoriteClick(Sender: TObject);
var
  i: integer;
  s: string;
begin
{$ifdef DisableScripting}
{$else}
  i := TMenuItem(Sender).Tag;
  Script := favorites[i];
  ScriptEditor.Editor.Lines.LoadFromFile(Script);

  s := ExtractFileName(Script);
  s := Copy(s, 0, length(s) - Length(ExtractFileExt(s)));
  mnuRun.Caption := Format(TextByKey('main-menu-script-run2'), [s]);//'Run "' + s + '"';
  btnRunScript.Hint := Format(TextByKey('main-menu-script-run2'), [s]);//'Run Script (F8)|Runs the ' + s + ' script.';
  //ScriptEditor.Caption := s;
  ScriptEditor.RunScript;

{$endif}
end;

procedure TMainForm.ScriptItemClick(Sender: TObject);
var
  s: string;
begin
{$ifdef DisableScripting}
{$else}
  Script := ExtractFilePath(Application.ExeName) + scriptPath + '\' + TMenuItem(Sender).Hint + '.asc';
  ScriptEditor.Editor.Lines.LoadFromFile(Script);
  s := ExtractFileName(Script);
  s := Copy(s, 0, length(s) - Length(ExtractFileExt(s)));
  mnuRun.Caption := Format(TextByKey('main-menu-script-run2'), [s]);//'Run "' + s + '"';
  btnRunScript.Hint := Format(TextByKey('main-menu-script-run2'), [s]);//'Run Script (F8)|Runs the ' + s + ' script.';
  //ScriptEditor.Caption := s;
  ScriptEditor.RunScript;
{$endif}
end;

procedure TMainForm.GetScripts;
var
  NewItem: TMenuItem;
  NewItem2 : TMenuItem;
  searchResult: TSearchRec;
  i: integer;
  s: string;
  sl: TStringList;
  path : string;
begin
  sl := TStringList.Create;
  s := TextByKey('main-menu-script-directory');

  NewItem := mnuScript.Find(TextByKey('main-menu-script-directory'));
  if (NewItem <> nil) then mnuScript.Remove(NewItem);
  NewItem := mnuScript.Find(TextByKey('main-menu-script-more'));
  if (NewItem <> nil) then mnuScript.Remove(NewItem);

  {$ifdef DisableScripting}
  {$else}
  if FileExists(ExtractFilePath(Application.ExeName) + scriptFavsFilename) then begin
    Favorites.LoadFromFile(AppPath + scriptFavsFilename);
    if Trim(Favorites.Text) <> '' then begin
      if Favorites.count <> 0 then
      begin
        NewItem := TMenuItem.Create(self);
        NewItem.Caption := '-';
        mnuScript.Add(NewItem);
        for i := 0 to Favorites.Count - 1 do
        begin
          if FileExists(Favorites[i]) then
          begin
            NewItem := TMenuItem.Create(Self);
            if i < 12 then
              NewItem.ShortCut := TextToShortCut('Ctrl+F' + IntToStr(i + 1));
            NewItem.Tag := i;
            s := ExtractFileName(Favorites[i]);
            s := Copy(s, 0, length(s) - Length(ExtractFileExt(s)));
            NewItem.Caption := s;
            //NewItem.Hint := 'Loads and runs the ' + s + ' script.';
            NewItem.OnClick := FavoriteClick;
            OnClick := FavoriteClick;
            mnuScript.Add(NewItem);
            sl.Add(s);
          end;
        end;
        s := TextByKey('main-menu-script-more');
      end;
    end;
  end;

  // Try to find regular files matching *.asc in the scripts dir
  path := ExtractFilePath(Application.ExeName) + scriptPath + '\*.asc';
  if FindFirst(path, faAnyFile, searchResult) = 0 then begin
      NewItem := TMenuItem.Create(Self);
      NewItem.Caption := '-';
      mnuScript.Add(NewItem);
      NewItem := TMenuItem.Create(Self);
      NewItem.Caption := s;
      repeat
        NewItem2 := TMenuItem.Create(Self);
        s := searchResult.Name;
        s := Copy(s, 0, length(s) - Length(ExtractFileExt(s)));
        NewItem2.Caption := s;
        NewItem2.Hint := s;
        NewItem2.OnClick := ScriptItemClick;
        if (sl.IndexOf(s) < 0) then NewItem.Add(NewItem2);
      until (FindNext(searchResult) <> 0);
      FindClose(searchResult);
      mnuScript.Add(NewItem);
  end;

  // -X- Copypaste code...me lazy
  path := ExtractFilePath(Application.ExeName) + scriptPath + '\*.aposcript';
  if FindFirst(path, faAnyFile, searchResult) = 0 then begin
      NewItem := TMenuItem.Create(Self);
      NewItem.Caption := '-';
      mnuScript.Add(NewItem);
      NewItem := TMenuItem.Create(Self);
      NewItem.Caption := s;
      repeat
        NewItem2 := TMenuItem.Create(Self);
        s := searchResult.Name;
        s := Copy(s, 0, length(s) - Length(ExtractFileExt(s)));
        NewItem2.Caption := s;
        NewItem2.Hint := s;
        NewItem2.OnClick := ScriptItemClick;
        if (sl.IndexOf(s) < 0) then NewItem.Add(NewItem2);
      until (FindNext(searchResult) <> 0);
      FindClose(searchResult);
      mnuScript.Add(NewItem);
  end;

  {$endif}
end;

procedure TMainForm.FormCreate(Sender: TObject);
var
  dte: string;
  cmdl : TCommandLine;
begin
  //KnownPlugins := TList.Create;

  FNrThreads := 1;
  AppVersionString:=APP_NAME;

  SubstSource := TStringList.Create;
  SubstTarget := TStringList.Create;

  CreateSubstMap;
  TbBreakWidth := 802;

  {$ifdef DisableScripting}
  mnuScript.Visible := false;
  {btnRunScript.Visible := false;
  btnStopScript.Visible := false;
  ToolButton17.Visible := false;
  ToolButton18.Visible := false;}

  ToolBar.Perform(CM_CONTROLCHANGE, WPARAM(btnRunScript), 0);
  ToolBar.Perform(CM_CONTROLCHANGE, WPARAM(btnStopScript), 0);
  ToolBar.Perform(CM_CONTROLCHANGE, WPARAM(ToolButton17), 0);
  ToolBar.Perform(CM_CONTROLCHANGE, WPARAM(ToolButton18), 0);
  TbBreakWidth := TbBreakWidth - (3 * 26 + 1 * 8);
  {$endif}

  ListXmlScanner := TEasyXmlScanner.Create(nil);
  XmlScanner := TXmlScanner.Create(nil);

  MainForm.ListXmlScanner.Normalize := False;
  MainForm.ListXmlScanner.OnStartTag := ListXmlScannerStartTag;

  MainForm.XmlScanner.Normalize := False;
  MainForm.XmlScanner.OnContent := XmlScannerContent;
  MainForm.XmlScanner.OnEmptyTag := XMLScannerEmptyTag;
  MainForm.XmlScanner.OnEndTag := XmlScannerEndTag;
  MainForm.XmlScanner.OnStartTag := XMLScannerStartTag;

  ReadSettings;

  InternalBitsPerSample := 0;
  renderBitsPerSample := 0;

  // Re-save...
  SaveSettings;

  LoadLanguage(LanguageFile);
  InsertStrings;

  AvailableLanguages := TStringList.Create;
  AvailableLanguages.Add('');
  ListLanguages;

  cmdl := TCommandLine.Create;
  cmdl.Load;

  if (NXFORMS > 100) then AppVersionString := AppVersionString + ' (' + TextByKey('main-common-title-t500') + ')'
  else if (NXFORMS < 100) or (cmdl.Lite) then AppVersionString := AppVersionString + ' (' + TextByKey('main-common-title-lite') + ')';

  SplashWindow.SetInfo(TextByKey('splash-loadingui'));
  LockListChangeUpdate := false;
  Screen.Cursors[crEditArrow]  := LoadCursor(HInstance, 'ARROW_WHITE');
  Screen.Cursors[crEditMove]   := LoadCursor(HInstance, 'MOVE_WB');
  Screen.Cursors[crEditRotate] := LoadCursor(HInstance, 'ROTATE_WB');
  Screen.Cursors[crEditScale]  := LoadCursor(HInstance, 'SCALE_WB');
  Caption := AppVersionString + APP_BUILD;

  mnuExportFLame.Enabled := FileExists(flam3Path);

  FMouseMoveState := msDrag;
  LimitVibrancy := False;
  Favorites := TStringList.Create;
  GetScripts;
  Randomize;
  MainSeed := Random(123456789);
  maincp := TControlPoint.Create;
  ParseCp := TControlPoint.create;
  OpenFileType := ftXML;
  Application.OnHint := DisplayHint;
  AppPath := ExtractFilePath(Application.ExeName);
  CanDrawOnResize := False;

  SplashWindow.SetInfo(TextByKey('splash-loadingsettings'));

  Dte := FormatDateTime('yymmdd', Now);
  if Dte <> RandomDate then
    RandomIndex := 0;
  RandomDate := Dte;
  mnuExit.ShortCut := TextToShortCut('Alt+F4');

  SplashWindow.SetInfo(TextByKey('splash-loadingplugins'));
  FillVariantMenu;

  tbQualityBox.Text := FloatToStr(defSampleDensity);
  tbShowAlpha.Down := ShowTransparency;
  DrawSelection := true;
  FViewScale := 1;
  ThumbnailSize := 128;
  UsedThumbnails := Thumbnails;
  if (UseSmallThumbnails) then begin
    ThumbnailSize := 96;
    UsedThumbnails := SmallThumbnails;
  end;

  LoadThumbnailPlaceholder(ThumbnailSize);

  ListView1.LargeImages := UsedThumbnails;
  ListBackPanel.Width := ThumbnailSize + 90;
  Splitter.Left := ListBackPanel.Width;

  if not cmdl.Lite then begin
  if ClassicListMode = true then
    btnViewListClick(nil)
  else
    btnViewIconsClick(nil);
  end else begin
  ListView1.ViewStyle := vsReport;
  ToolBar.Perform(CM_CONTROLCHANGE, WPARAM(btnViewList), 0);
  ToolBar.Perform(CM_CONTROLCHANGE, WPARAM(btnViewIcons), 0);
  ToolBar.Perform(CM_CONTROLCHANGE, WPARAM(ToolButton9), 0);
  TbBreakWidth := TbBreakWidth - (2 * 26 + 1 * 8);
  end;

end;

procedure TMainForm.FormShow(Sender: TObject);
var
  Registry: TRegistry;
  i: integer;
  a,b,c,d:integer;
  hnd,hr:Cardinal;
  index: integer;
  mins:integer;
  cmdl : TCommandLine;
  fn, flameXML : string;
  openScript : string;
begin
  tbGuides.Down := EnableGuides;
  DoNotAskAboutChange := true;
  { Read position from registry }
  Registry := TRegistry.Create;
  try
    Registry.RootKey := HKEY_CURRENT_USER;
    if Registry.OpenKey('\Software\' + APP_NAME + '\Forms\Main', False) then
    begin
      if Registry.ValueExists('Left') then
        MainForm.Left := Registry.ReadInteger('Left');
      if Registry.ValueExists('Top') then
        MainForm.Top := Registry.ReadInteger('Top');
      if Registry.ValueExists('Width') then
        MainForm.Width := Registry.ReadInteger('Width');
      if Registry.ValueExists('Height') then
        MainForm.Height := Registry.ReadInteger('Height');
    end;
    Registry.CloseKey;
  finally
    Registry.Free;
  end;
  { Synchronize menus etc..}
  // should be defaults....
  SplashWindow.SetInfo(TextByKey('splash-initrenderer'));
  UndoIndex := 0;
  UndoMax := 0;
  index := 1;
  ListView.RowSelect := True;
  inc(MainSeed);
  RandSeed := MainSeed;
  Variation := vRandom;
  Maincp.brightness := defBrightness;
  maincp.gamma := defGamma;
  maincp.vibrancy := defVibrancy;
  maincp.sample_density := defSampleDensity;
  maincp.spatial_oversample := defOversample;
  maincp.spatial_filter_radius := defFilterRadius;
  maincp.gammaThreshRelative := defGammaThreshold;
  inc(MainSeed);
  RandSeed := MainSeed;

// somehow this doesn't work:
//  Image.Width := BackPanel.Width - 2;
//  Image.Height := BackPanel.Height - 2;

// so we'll do it 'bad' way ;-)
  Image.Align := alNone;

  SplashWindow.SetInfo(TextByKey('splash-initcolormap'));
  if FileExists(AppPath + 'default.map') then
  begin
    DefaultPalette := GradientBrowser.LoadFractintMap(AppPath + 'default.map');
    maincp.cmap := DefaultPalette;
  end
  else
  begin
    cmap_index := random(NRCMAPS);
    GetCMap(cmap_index, 1, maincp.cmap);
    DefaultPalette := maincp.cmap;
  end;
  if FileExists(GetEnvVarValue('APPDATA') + '\' + randFilename) then
    DeleteFile(GetEnvVarValue('APPDATA') + '\' + randFilename);

  cmdl := TCommandLine.Create;
  cmdl.Load;

  openScript := '';

  // get filename from command line argument
  SplashWindow.SetInfo(TextByKey('splash-initbatch'));
  if ParamCount > 0 then
    openFile := ParamStr(1)
  else
    openFile := defFlameFile;

  if ((openFile = '') or (not FileExists(openFile))) and (RememberLastOpenFile) then begin
    openFile := LastOpenFile;
    index := LastOpenFileEntry;
  end;

  if FileExists(openFile) and ((LowerCase(ExtractFileExt(OpenFile)) <> '.asc') or (LowerCase(ExtractFileExt(OpenFile)) <> '.aposcript')) then begin
    LastOpenFile := openFile;
    LastOpenFileEntry := index;
  end;

  if (openFile = '') or (not FileExists(openFile)) and ((LowerCase(ExtractFileExt(OpenFile)) <> '.asc') or (LowerCase(ExtractFileExt(OpenFile)) <> '.aposcript')) then
  begin
    MainCp.Width := Image.Width;
    MainCp.Height := Image.Height;
    RandomBatch;
    if APP_BUILD = '' then MainForm.Caption := AppVersionString + ' - ' + TextByKey('main-common-randombatch')
    else MainForm.Caption := AppVersionString + ' ' + APP_BUILD + ' - ' + TextByKey('main-common-randombatch');
    OpenFile := GetEnvVarValue('APPDATA') + '\' + randFilename;
    ListXML(OpenFile, 1);
    OpenFileType := ftXML;
    if batchsize = 1 then DrawFlame;
  end
  else
  begin
    (*if (LowerCase(ExtractFileExt(OpenFile)) = '.apo') or (LowerCase(ExtractFileExt(OpenFile)) = '.fla') then
    begin
      ListFlames(OpenFile, index);
      OpenFileType := ftFla;
    end else*) if (LowerCase(ExtractFileExt(OpenFile)) = '.asc') or (LowerCase(ExtractFileExt(OpenFile)) = '.aposcript')  then
    begin
      openScript := OpenFile;
      RandomBatch;
      if APP_BUILD = '' then MainForm.Caption := AppVersionString + ' - ' + TextByKey('main-common-randombatch')
      else MainForm.Caption := AppVersionString + ' ' + APP_BUILD + ' - ' + TextByKey('main-common-randombatch');
      OpenFile := GetEnvVarValue('APPDATA') + '\' + randFilename;
      ListXML(OpenFile, 1);
      OpenFileType := ftXML;
      if batchsize = 1 then DrawFlame;
    end else begin
      ListXML(OpenFile, index);
      OpenFileType := ftXML;
      MainForm.ListView1.Selected := MainForm.ListView1.Items[index - 1];
    end;
    if APP_BUILD = '' then MainForm.Caption := AppVersionString + ' - ' + openFile
    else MainForm.Caption := AppVersionString + ' ' + APP_BUILD + ' - ' + openFile;
//    MainForm.Caption := AppVersionString + ' - ' + openFile;
  end;
  //ListView.SetFocus;
  CanDrawOnResize := True;
  Statusbar.Panels[3].Text := maincp.name;
{
  gradientForm.cmbPalette.Items.clear;
  for i := 0 to NRCMAPS -1 do
    gradientForm.cmbPalette.Items.Add(cMapnames[i]);
  GradientForm.cmbPalette.ItemIndex := 0;
}
  AdjustForm.cmbPalette.Items.clear;
  for i := 0 to NRCMAPS -1 do
    AdjustForm.cmbPalette.Items.Add(cMapnames[i]);
  AdjustForm.cmbPalette.ItemIndex := 0;
//  AdjustForm.cmbPalette.Items.clear;

  ExportDialog.cmbDepth.ItemIndex := 2;
  DoNotAskAboutChange := false;

  if (AutoSaveFreq = 0) then mins := 1
  else if (AutoSaveFreq = 1) then mins := 2
  else if (AutoSaveFreq = 2) then mins := 5
  else if (AutoSaveFreq = 3) then mins := 10
  else begin
    mins := 5;
    AutoSaveFreq := 2;
    AutoSaveEnabled := false;
  end;

  AutoSaveTimer.Interval := 60 * 1000 * mins;
  AutoSaveTimer.Enabled := AutoSaveEnabled;

  // loading done..now do what is told by cmdline  ...
  if (cmdl.CreateFromTemplate) then begin
    if FileExists(cmdl.TemplateFile) then begin
      fn:=cmdl.TemplateFile;
      flameXML := LoadXMLFlameText(fn, cmdl.TemplateName);
      UpdateUndo;
{$ifdef DisableScripting}
{$else}
  ScriptEditor.Stopped := True;
{$endif}
      StopThread;
      InvokeLoadXML(flameXML);
      Transforms := MainCp.TrianglesFromCP(MainTriangles);
      Statusbar.Panels[3].Text := MainCp.name;
      ResizeImage;
      RedrawTimer.Enabled := True;
      Application.ProcessMessages;
      UpdateWindows;
      AdjustForm.TemplateRandomizeGradient;
    end;
  end;

  // .. and run autoexec.asc
{$ifdef DisableScripting}
{$else}
  SplashWindow.SetInfo(TextByKey('splash-execstartupscript'));
  if (FileExists(AppPath + 'autoexec.asc')) then begin
    ScriptEditor.LoadRunAndClear(AppPath + 'autoexec.asc');
    mnuRun.Caption := TextByKey('main-menu-script-run');
    btnRunScript.Hint := TextByKey('main-menu-script-run');
  end;

  if (openScript <> '') then begin
    ScriptEditor.LoadScriptFile(openScript);
    ScriptEditor.Show;
  end;
{$endif}

  //FNrThreads := Nrtreads;

  SplashWindow.Hide;
  SplashWindow.Free;
end;

function TMainForm.SystemErrorMessage: string;
var 
  P: PChar;
begin
  if FormatMessage(Format_Message_Allocate_Buffer + Format_Message_From_System,
                   nil,
                   GetLastError,
                   0,
                   @P,
                   0,
                   nil) <> 0 then
  begin
    Result := P;
    LocalFree(Integer(P))
  end 
  else 
    Result := '';
end;
function TMainForm.SystemErrorMessage2(errno:cardinal): string;
var 
  P: PChar;
begin
  if FormatMessage(Format_Message_Allocate_Buffer + Format_Message_From_System,
                   nil,
                   errno,
                   0,
                   @P,
                   0,
                   nil) <> 0 then
  begin
    Result := P;
    LocalFree(Integer(P))
  end 
  else 
    Result := '';
end;

procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
var
  Registry: TRegistry;
begin
  if ConfirmExit and (UndoIndex <> 0) then
    if Application.MessageBox(PChar(TextByKey('common-confirmexit')), 'Apophysis', MB_ICONWARNING or MB_YESNO) <> IDYES then
    begin
      Action := caNone;
      exit;
    end;

{$ifdef DisableScripting}
{$else}
  ScriptEditor.Stopped := True;
{$endif}
  HtmlHelp(0, nil, HH_CLOSE_ALL, 0);
  { To capture secondary window positions }
  if EditForm.visible then EditForm.Close;
  if AdjustForm.visible then AdjustForm.close;
  if GradientBrowser.visible then GradientBrowser.close;
  if MutateForm.visible then MutateForm.Close;
//  if GradientForm.visible then GradientForm.Close;
{$ifdef DisableScripting}
{$else}
  if ScriptEditor.visible then ScriptEditor.Close;
{$endif}

  { Stop the render thread }
  if RenderForm.Visible then RenderForm.Close;
  if assigned(Renderer) then Renderer.Terminate;
  if assigned(Renderer) then Renderer.WaitFor;
  { Write position to registry }
  Registry := TRegistry.Create;
  try
    Registry.RootKey := HKEY_CURRENT_USER;
    if Registry.OpenKey('\Software\' + APP_NAME + '\Forms\Main', True) then
    begin
      if MainForm.WindowState <> wsMaximized then begin
        Registry.WriteInteger('Top', MainForm.Top);
        Registry.WriteInteger('Left', MainForm.Left);
        Registry.WriteInteger('Width', MainForm.Width);
        Registry.WriteInteger('Height', MainForm.Height);
      end;
    end;
  finally
    Registry.Free;
  end;
  Application.ProcessMessages;
  CanDrawOnResize := False;
  if FileExists(GetEnvVarValue('APPDATA') + '\' + randFilename) then
    DeleteFile(GetEnvVarValue('APPDATA') + '\' + randFilename);
  if FileExists(GetEnvVarValue('APPDATA') + '\' + undoFilename) then
    DeleteFile(GetEnvVarValue('APPDATA') + '\' + undoFilename);
  SaveSettings;
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  if assigned(Renderer) then Renderer.Terminate;
  if assigned(Renderer) then Renderer.WaitFor;
  if assigned(Renderer) then Renderer.Free;
  if assigned(FViewImage) then FViewImage.Free;
  MainCP.free;
  ParseCp.free;
  Favorites.Free;
end;

procedure TMainForm.FormKeyPress(Sender: TObject; var Key: Char);
var
  scale: double;
begin
  if Key = #27 then begin
    case FMouseMoveState of
      msZoomWindowMove:
        FMouseMoveState := msZoomWindow;
      msZoomOutWindowMove:
        FMouseMoveState := msZoomOutWindow;
      msDragMove:
        begin
          FMouseMoveState := msDrag;

          scale := FViewScale * Image.Width / FViewImage.Width;
          FViewPos.X := FViewPos.X - (FClickRect.Right - FClickRect.Left) / scale;
          FViewPos.Y := FViewPos.Y - (FClickRect.Bottom - FClickRect.Top) / scale;
        end;
      msRotateMove:
        FMouseMoveState := msRotate;
    end;
    DrawImageView;
  end;
{$ifdef DisableScripting}
{$else}
  ScriptEditor.Stopped := True;
{$endif}
end;

{ ****************************** Misc controls ****************************** }

procedure TMainForm.BackPanelResize(Sender: TObject);
begin
  StopThread;
  if CanDrawOnResize then
    reDrawTimer.Enabled := True;

  ResizeImage;  
  DrawImageView;
end;

procedure TMainForm.LoadXMLFlame(filename, name: string);
var
  i, p: integer;
  FileStrings: TStringList;
  ParamStrings: TStringList;
  Tokens: TStringList;
  time: integer;
  ax,bx,cx,dx:integer;
  hwn,hr:cardinal;
  px:pansichar;
begin
  time := -1;
  FileStrings := TStringList.Create;
  ParamStrings := TStringList.Create;

  if pos('*untitled', name) <> 0 then
  begin
    Tokens := TStringList.Create;
    GetTokens(name, tokens);
    time := StrToInt(tokens[1]);
    Tokens.free;
  end;
  try
    FileStrings.LoadFromFile(filename);
    for i := 0 to FileStrings.Count - 1 do
    begin
      pname := '';
      ptime := '';
      p := Pos('<flame ', LowerCase(FileStrings[i]));
      if (p <> 0) then
      begin
        MainForm.ListXMLScanner.LoadFromBuffer(TCharType(TStringType(FileStrings[i])));
        MainForm.ListXMLScanner.Execute;
        if pname <> '' then
        begin
          if (Trim(pname) = Trim(name)) then
          begin
            ParamStrings.Add(FileStrings[i]);
            Break;
          end;
        end
        else
        begin
          if ptime='' then ptime:='0'; //hack
          if StrToInt(ptime) = time then
          begin
            ParamStrings.Add(FileStrings[i]);
            Break;
          end;
        end;
      end;
    end;
    repeat
      inc(i);
      ParamStrings.Add(FileStrings[i]);
    until pos('</flame>', Lowercase(FileStrings[i])) <> 0;

{$ifdef DisableScripting}
{$else}
  ScriptEditor.Stopped := True;
{$endif}
    StopThread;
    ParseXML(MainCp,PAramStrings.Text, true);

    mnuSaveUndo.Enabled := false;
    mnuUndo.Enabled := False;
    mnuPopUndo.Enabled := False;
    mnuRedo.enabled := False;
    mnuPopRedo.enabled := False;
    EditForm.mnuUndo.Enabled := False;
    EditForm.mnuRedo.enabled := False;
    EditForm.tbUndo.enabled := false;
    EditForm.tbRedo.enabled := false;
    AdjustForm.btnUndo.enabled := false;
    AdjustForm.btnRedo.enabled := false;
    btnUndo.Enabled := false;
    btnRedo.enabled := false;

    Transforms := MainCp.TrianglesFromCP(MainTriangles);

    UndoIndex := 0;
    UndoMax := 0;
    if fileExists(GetEnvVarValue('APPDATA') + '\' + undoFilename) then
      DeleteFile(GetEnvVarValue('APPDATA') + '\' + undoFilename);
    Statusbar.Panels[3].Text := Maincp.name;
    RedrawTimer.Enabled := True;
    Application.ProcessMessages;

    EditForm.SelectedTriangle := 0; // (?)

    UpdateWindows;
  finally
    FileStrings.free;
    ParamStrings.free;
  end;
end;

procedure TMainForm.ResizeImage;
var
  pw, ph: integer;
begin
  pw := BackPanel.Width - 2;
  ph := BackPanel.Height - 2;
  begin
    if (MainCP.Width / MainCP.Height) > (pw / ph) then
    begin
      Image.Width := pw;
      Image.Height := round(MainCP.Height / MainCP.Width * pw);
      Image.Left := 1;
      Image.Top := (ph - Image.Height) div 2;
    end
    else begin
      Image.Height := ph;
      Image.Width := round(MainCP.Width / MainCP.Height * ph);
      Image.Top := 1;
      Image.Left := (pw - Image.Width) div 2;
    end;
  end;
  //MainCP.AdjustScale(Image.Width, Image.Height);
end;

procedure TMainForm.ListViewChange(Sender: TObject; Item: TListItem;
  Change: TItemChange);
var
  FStrings: TStringList;
  IFSStrings: TStringList;
  EntryStrings, Tokens: TStringList;
  SavedPal: Boolean;
  i, j: integer;
  floatcolor: double;
  s: string;
  Palette: TcolorMap;
  name:string;
begin
  if (ListView1.SelCount <> 0) and
    (Trim(ListView1.Selected.Caption) <> Trim(maincp.name)) then
  begin
    LastOpenFileEntry := ListView1.Selected.Index + 1;
    RedrawTimer.Enabled := False; //?
    StopThread;

    if OpenFileType = ftXML then
    begin
      name:=ListView1.Selected.caption;
      ParseLoadingBatch := false;
      LoadXMLFlame(OpenFile, name);
      AnnoyUser;
    end
    else
    begin

      SavedPal := false;
{$ifdef DisableScripting}
{$else}
  ScriptEditor.Stopped := True;
{$endif}
      FStrings := TStringList.Create;
      IFSStrings := TStringList.Create;
      Tokens := TStringList.Create;
      EntryStrings := TStringList.Create;
      try
        FStrings.LoadFromFile(OpenFile);
        for i := 0 to FStrings.count - 1 do
          if Pos(ListView1.Selected.Caption + ' {', Trim(FStrings[i])) = 1 then
            break;
        IFSStrings.Add(FStrings[i]);
        repeat
          inc(i);
          IFSStrings.Add(FStrings[i]);
        until Pos('}', FStrings[i]) <> 0;
        maincp.Clear; // initialize control point for new flame;
        maincp.background[0] := 0;
        maincp.background[1] := 0;
        maincp.background[2] := 0;
        maincp.sample_density := defSampleDensity;
        maincp.spatial_oversample := defOversample;
        maincp.spatial_filter_radius := defFilterRadius;
        if OpenFileType = ftFla then
        begin
          for i := 0 to FStrings.count - 1 do
          begin
            if Pos(ListView1.Selected.Caption + ' {', Trim(FStrings[i])) = 1 then
              break;
          end;
          inc(i);
          while (Pos('}', FStrings[i]) = 0) and (Pos('palette:', FStrings[i]) = 0) do
          begin
            EntryStrings.Add(FStrings[i]);
            inc(i);
          end;
          if Pos('palette:', FStrings[i]) = 1 then
          begin
            SavedPal := True;
            inc(i);
            for j := 0 to 255 do begin
              s := FStrings[i];
              GetTokens(s, tokens);
              floatcolor := StrToFloat(Tokens[0]);
              Palette[j][0] := round(floatcolor);
              floatcolor := StrToFloat(Tokens[1]);
              Palette[j][1] := round(floatcolor);
              floatcolor := StrToFloat(Tokens[2]);
              Palette[j][2] := round(floatcolor);
              inc(i);
            end;
          end;
          FlameString := EntryStrings.Text;
          maincp.ParseString(FlameString);
          Transforms := MainCP.NumXForms;
        end
        else
        begin
        { Open *.ifs File }
          Variation := vLinear;
          VarMenus[0].Checked := True;
          StringToIFS(IFSStrings.Text);
          SetVariation(maincp);
          maincp.CalcBoundBox;
        end;
//        Zoom := maincp.zoom;
        Center[0] := maincp.Center[0];
        Center[1] := maincp.Center[1];
//        MainCP.NormalizeWeights;
        mnuSaveUndo.Enabled := false;
        mnuUndo.Enabled := False;
        mnuPopUndo.Enabled := False;
        mnuRedo.enabled := False;
        mnuPopRedo.enabled := False;
        EditForm.mnuUndo.Enabled := False;
        EditForm.mnuRedo.enabled := False;
        EditForm.tbUndo.enabled := false;
        EditForm.tbRedo.enabled := false;
        AdjustForm.btnUndo.enabled := false;
        AdjustForm.btnRedo.enabled := false;
        btnUndo.Enabled := false;
        btnRedo.enabled := false;
        Transforms := MainCp.TrianglesFromCP(MainTriangles);
      // Fix Apophysis 1.0 parameters with negative color parameteres!
        for i := 0 to Transforms - 1 do
          if maincp.xform[i].color < 0 then maincp.xform[i].color := 0;
        if SavedPal then maincp.cmap := Palette;
        UndoIndex := 0;
        UndoMax := 0;
        if fileExists(GetEnvVarValue('APPDATA') + '\' + undoFilename) then
          DeleteFile(GetEnvVarValue('APPDATA') + '\' + undoFilename);
        maincp.name := ListView.Selected.Caption;
        Statusbar.Panels[3].Text := maincp.name;
        RedrawTimer.Enabled := True;
        Application.ProcessMessages;
        UpdateWindows;
      finally
        IFSStrings.Free;
        FStrings.Free;
        Tokens.free;
        EntryStrings.free;
      end;
    end;
    {if ResizeOnLoad then}
    ResizeImage;
    PrevListItem := Item;
  end;
end;

procedure TMainForm.UpdateWindows;
begin
  if AdjustForm.visible then AdjustForm.UpdateDisplay;
  if EditForm.visible then EditForm.UpdateDisplay;
  if MutateForm.visible then MutateForm.UpdateDisplay;
  if CurvesForm.Visible then CurvesForm.SetCp(MainCp);
  
end;

procedure TMainForm.LoadUndoFlame(index: integer; filename: string);
var
  FStrings: TStringList;
  IFSStrings: TStringList;
  EntryStrings, Tokens: TStringList;
  SavedPal: Boolean;
  i, j: integer;
  s: string;
  Palette: TColorMap;
begin
{$ifdef DisableScripting}
{$else}
  ScriptEditor.Stopped := True;
{$endif}
  FStrings := TStringList.Create;
  IFSStrings := TStringList.Create;
  Tokens := TStringList.Create;
  EntryStrings := TStringList.Create;
  try
    FStrings.LoadFromFile(filename);
    for i := 0 to FStrings.count - 1 do
      if Pos(Format('%.4d-', [UndoIndex]), Trim(FStrings[i])) = 1 then
        break;
    IFSStrings.Add(FStrings[i]);
    repeat
      inc(i);
      IFSStrings.Add(FStrings[i]);
    until Pos('}', FStrings[i]) <> 0;
    for i := 0 to FStrings.count - 1 do
    begin
      if Pos(Format('%.4d-', [UndoIndex]), Trim(Lowercase(FStrings[i]))) = 1 then
        break;
    end;
    inc(i);
    while (Pos('}', FStrings[i]) = 0) and (Pos('palette:', FStrings[i]) = 0) do
    begin
      EntryStrings.Add(FStrings[i]);
      inc(i);
    end;
    SavedPal := false;
    if Pos('palette:', FStrings[i]) = 1 then
    begin
      SavedPal := True;
      inc(i);
      for j := 0 to 255 do begin
        s := FStrings[i];
        GetTokens(s, tokens);
        Palette[j][0] := StrToInt(Tokens[0]);
        Palette[j][1] := StrToInt(Tokens[1]);
        Palette[j][2] := StrToInt(Tokens[2]);
        inc(i);
      end;
    end;
    maincp.Clear;
    FlameString := EntryStrings.Text;
    maincp.zoom := 0;
    maincp.center[0] := 0;
    maincp.center[0] := 0;
    maincp.ParseString(FlameString);
    maincp.sample_density := defSampleDensity;
    Center[0] := maincp.Center[0];
    Center[1] := maincp.Center[1];
//    cp.CalcBoundbox;
//    MainCP.NormalizeWeights;
    Transforms := MainCp.TrianglesFromCP(MainTriangles);
    // Trim undo index from title
    maincp.name := Copy(Fstrings[0], 6, length(Fstrings[0]) - 7);

    if SavedPal then maincp.cmap := palette;
    if AdjustForm.visible then AdjustForm.UpdateDisplay;

    RedrawTimer.Enabled := True;
    UpdateWindows;
  finally
    IFSStrings.Free;
    FStrings.Free;
    Tokens.free;
    EntryStrings.free;
  end;
end;

procedure TMainForm.ResetLocation;
begin
  maincp.zoom := 0;
  //maincp.FAngle := 0;
  //maincp.Width := Image.Width;
  //maincp.Height := Image.Height;
  maincp.CalcBoundBox;
  center[0] := maincp.center[0];
  center[1] := maincp.center[1];
end;


procedure TMainForm.ListViewEdited(Sender: TObject; Item: TListItem;
  var S: string);
begin
  if s <> Item.Caption then

    if OpenFIleType = ftXML then
    begin
      if not RenameXML(Item.Caption, s) then
        s := Item.Caption;
    end
    else
      if not RenameIFS(Item.Caption, s) then
        s := Item.Caption

end;

procedure TMainForm.RedrawTimerTimer(Sender: TObject);
{ Draw flame when timer fires. This seems to stop a lot of errors }
begin
  if FMouseMoveState in [msZoomWindowMove, msZoomOutWindowMove, msDragMove, msRotateMove] then exit;

  RedrawTimer.enabled := False;
  DrawFlame;
end;

procedure TMainForm.mnuVRandomClick(Sender: TObject);
begin
  mnuVRandom.Checked := True;
  StopThread;
  UpdateUndo;
  inc(MainSeed);
  RandSeed := MainSeed;
  repeat
    Variation := vRandom;
    SetVariation(maincp);
  until not maincp.blowsup(1000);
  inc(randomindex);
  MainCp.name := RandomPrefix + RandomDate + '-' +
    IntToStr(RandomIndex);
  ResetLocation;
  RedrawTimer.Enabled := True;
  UpdateWindows;
end;

procedure TMainForm.mnuGradClick(Sender: TObject);
begin
  AdjustForm.UpdateDisplay;
  AdjustForm.PageControl.TabIndex:=2;
  AdjustForm.Show;
end;

///////////////////////////////////////////////////////////////////////////////
procedure TMainForm.mnuimageClick(Sender: TObject);
begin
  //frmImageColoring.Show;
end;

procedure swapcolor(var clist: array of cardinal; i, j: integer);
var
  t: cardinal;
begin
  t := clist[j];
  clist[j] := clist[i];
  clist[i] := t;
end;

function diffcolor(clist: array of cardinal; i, j: integer): cardinal;
var
  r1, g1, b1, r2, g2, b2: byte;
begin
  r1 := clist[j] and 255;
  g1 := clist[j] shr 8 and 255;
  b1 := clist[j] shr 16 and 255;
  r2 := clist[i] and 255;
  g2 := clist[i] shr 8 and 255;
  b2 := clist[i] shr 16 and 255;
  Result := abs((r1 - r2) * (r1 - r2)) + abs((g1 - g2) * (g1 - g2)) +
    abs((b1 - b2) * (b1 - b2));
end;

procedure TMainForm.mnuSmoothGradientClick(Sender: TObject);
begin
  SmoothPalette;
end;

procedure TMainForm.SmoothPalette;
{ From Draves' Smooth palette Gimp plug-in }
var
  Bitmap: TBitMap;
  JPEG: TJPEGImage;
  pal: TColorMap;
  strings: TStringlist;
  ident, FileName: string;
  len, len_best, as_is, swapd: cardinal;
  cmap_best, original, clist: array[0..255] of cardinal;
  p, total, j, rand, tryit, i0, i1, x, y, i, iw, ih: integer;
  fn:string;
begin
  Total := Trunc(NumTries * TryLength / 100);
  p := 0;
  Bitmap := TBitmap.Create;
  JPEG := TJPEGImage.Create;
  strings := TStringList.Create;
  try
    begin
      inc(MainSeed);
      RandSeed := MainSeed;
      OpenDialog.Filter := Format('%s|*.bmp;*.dib;*.jpg;*.jpeg|%s|*.bmp;*.dib|%s|*.jpg;*.jpeg|%s|*.*',
        [TextByKey('common-filter-allimages'), TextByKey('common-filter-bitmap'),
         TextByKey('common-filter-jpeg'), TextByKey('common-filter-allfiles')]);
      OpenDialog.InitialDir := ImageFolder;
      OpenDialog.Title := TextByKey('common-browse');
      OpenDialog.FileName := '';
      if OpenSaveFileDialog(MainForm, OpenDialog.DefaultExt, OpenDialog.Filter, OpenDialog.InitialDir, TextByKey('common-browse'), fn, true, false, false, true) then
      //if OpenDialog.Execute then
      begin
        OpenDialog.FileName := fn;
        ImageFolder := ExtractFilePath(OpenDialog.FileName);
        Application.ProcessMessages;
        len_best := 0;
        if (UpperCase(ExtractFileExt(Opendialog.FileName)) = '.BMP')
          or (UpperCase(ExtractFileExt(Opendialog.FileName)) = '.DIB') then
          Bitmap.LoadFromFile(Opendialog.FileName);
        if (UpperCase(ExtractFileExt(Opendialog.FileName)) = '.JPG')
          or (UpperCase(ExtractFileExt(Opendialog.FileName)) = '.JPEG') then
        begin
          JPEG.LoadFromFile(Opendialog.FileName);
          with Bitmap do
          begin
            Width := JPEG.Width;
            Height := JPEG.Height;
            Canvas.Draw(0, 0, JPEG);
          end;
        end;
        iw := Bitmap.Width;
        ih := Bitmap.Height;
        for i := 0 to 255 do
        begin
          { Pick colors from 256 random pixels in the image }
          x := random(iw);
          y := random(ih);
          clist[i] := Bitmap.canvas.Pixels[x, y];
        end;
        original := clist;
        cmap_best := clist;
        for tryit := 1 to NumTries do
        begin
          clist := original;
          // scramble
          for i := 0 to 255 do
          begin
            rand := random(256);
            swapcolor(clist, i, rand);
          end;
          // measure
          len := 0;
          for i := 0 to 255 do
            len := len + diffcolor(clist, i, i + 1);
          // improve
          for i := 1 to TryLength do
          begin
            inc(p);
            //StatusBar.SimpleText := Format(StringReplace(TextByKey('main-status-calculatingpalette'), '%)', '%%)', [rfReplaceAll, rfIgnoreCase]), [(p div total)]);
            i0 := 1 + random(254);
            i1 := 1 + random(254);
            if ((i0 - i1) = 1) then
            begin
              as_is := diffcolor(clist, i1 - 1, i1) + diffcolor(clist, i0, i0 + 1);
              swapd := diffcolor(clist, i1 - 1, i0) + diffcolor(clist, i1, i0 + 1);
            end
            else if ((i1 - i0) = 1) then
            begin
              as_is := diffcolor(clist, i0 - 1, i0) + diffcolor(clist, i1, i1 + 1);
              swapd := diffcolor(clist, i0 - 1, i1) + diffcolor(clist, i0, i1 + 1);
            end
            else
            begin
              as_is := diffcolor(clist, i0, i0 + 1) + diffcolor(clist, i0, i0 - 1) +
                diffcolor(clist, i1, i1 + 1) + diffcolor(clist, i1, i1 - 1);
              swapd := diffcolor(clist, i1, i0 + 1) + diffcolor(clist, i1, i0 - 1) +
                diffcolor(clist, i0, i1 + 1) + diffcolor(clist, i0, i1 - 1);
            end;
            if (swapd < as_is) then
            begin
              swapcolor(clist, i0, i1);
              len := abs(len + swapd - as_is);
            end;
          end;
          if (tryit = 1) or (len < len_best) then
          begin
            cmap_best := clist;
            len_best := len;
          end;
        end;
        clist := cmap_best;
        // clean
        for i := 1 to 1024 do
        begin
          i0 := 1 + random(254);
          i1 := i0 + 1;
          as_is := diffcolor(clist, i0 - 1, i0) + diffcolor(clist, i1, i1 + 1);
          swapd := diffcolor(clist, i0 - 1, i1) + diffcolor(clist, i0, i1 + 1);
          if (swapd < as_is) then
          begin
            swapcolor(clist, i0, i1);
            len_best := len_best + swapd - as_is;
          end;
        end;
        { Convert to TColorMap, Gradient and save }
        FileName := lowercase(ExtractFileName(Opendialog.FileName));
        ident := CleanEntry(FileName);
        strings.add(ident + ' {');
        strings.add('gradient:');
        strings.add(' title="' + CleanUPRTitle(FileName) + '" smooth=no');
        for i := 0 to 255 do
        begin
          pal[i][0] := clist[i] and 255;
          pal[i][1] := clist[i] shr 8 and 255;
          pal[i][2] := clist[i] shr 16 and 255;
          j := round(i * (399 / 255));
          strings.Add(' index=' + IntToStr(j) + ' color=' + intToStr(clist[i]));
        end;
        strings.Add('}');
        SaveGradient(Strings.Text, Ident, defSmoothPaletteFile);

        StopThread;
        UpdateUndo;
        maincp.cmap := Pal;
        maincp.cmapindex := -1;
        AdjustForm.UpdateDisplay;

        if EditForm.Visible then EditForm.UpdateDisplay;
        if MutateForm.Visible then MutateForm.UpdateDisplay;
        RedrawTimer.enabled := true;

      end;
      StatusBar.SimpleText := '';
    end;
  finally
    Bitmap.Free;
    JPEG.Free;
    strings.Free;
  end;
end;

procedure TMainForm.mnuToolbarClick(Sender: TObject);
begin
  Toolbar.Visible := not Toolbar.Visible;
  mnuToolbar.Checked := Toolbar.visible;
end;

procedure TMainForm.mnuTraceClick(Sender: TObject);
begin
  TraceForm.Show;
end;

procedure TMainForm.mnuStatusBarClick(Sender: TObject);
begin
  Statusbar.Visible := not Statusbar.Visible;
  mnuStatusbar.Checked := Statusbar.visible;
end;

procedure TMainForm.mnuFileContentsClick(Sender: TObject);
begin
  ListBackPanel.Visible := not ListBackPanel.Visible;
  mnuFileContents.Checked := ListView.Visible;
  if ListBackPanel.Visible then Splitter.Width := 4 else Splitter.Width := 0;
end;

procedure TMainForm.Undo;
begin
  if UndoIndex = UndoMax then
    SaveFlame(maincp, Format('%.4d-', [UndoIndex]) + maincp.name,
      GetEnvVarValue('APPDATA') + '\' + undoFilename);
  StopThread;
  Dec(UndoIndex);
  LoadUndoFlame(UndoIndex, GetEnvVarValue('APPDATA') + '\' + undoFilename);
  mnuRedo.Enabled := True;
  mnuPopRedo.Enabled := True;
  btnRedo.Enabled := True;
  EditForm.mnuRedo.Enabled := True;
  EditForm.tbRedo.enabled := true;
  AdjustForm.btnRedo.enabled := true;
  if UndoIndex = 0 then begin
    mnuUndo.Enabled := false;
    mnuPopUndo.Enabled := false;
    btnUndo.Enabled := false;
    EditForm.mnuUndo.Enabled := false;
    EditForm.tbUndo.enabled := false;
    AdjustForm.btnUndo.enabled := false;
  end;
end;

procedure TMainForm.mnuUndoClick(Sender: TObject);
begin
  Undo;
  StatusBar.Panels[3].Text := maincp.name;
end;

procedure TMainForm.Redo;
begin
  StopThread;
  Inc(UndoIndex);

  assert(UndoIndex <= UndoMax, 'Undo list index out of range!');

  LoadUndoFlame(UndoIndex, GetEnvVarValue('APPDATA') + '\' + undoFilename);
  mnuUndo.Enabled := True;
  mnuPopUndo.Enabled := True;
  btnUndo.Enabled := True;
  EditForm.mnuUndo.Enabled := True;
  EditForm.tbUndo.enabled := true;
  AdjustForm.btnUndo.enabled := true;
  if UndoIndex = UndoMax then begin
    mnuRedo.Enabled := false;
    mnuPopRedo.Enabled := false;
    btnRedo.Enabled := false;
    EditForm.mnuRedo.Enabled := false;
    EditForm.tbRedo.enabled := false;
    AdjustForm.btnRedo.enabled := false;
  end;
end;

procedure TMainForm.mnuRedoClick(Sender: TObject);
begin
  Redo;
  StatusBar.Panels[3].Text := maincp.name;
end;

procedure TMainForm.mnuExportBitmapClick(Sender: TObject);
begin
  SaveDialog.DefaultExt := 'bmp';
  SaveDialog.Filter := Format('%s|*.bmp;*.dib|%s|*.*', [TextByKey('common-filter-bitmap'), TextBykey('common-filter-allfiles')]);
  SaveDialog.Filename := maincp.name;
  if SaveDialog.Execute then
    Image.Picture.Bitmap.SaveToFile(SaveDialog.Filename)
end;

procedure TMainForm.mnuFullScreenClick(Sender: TObject);
begin
  FullScreenForm.ActiveForm := Screen.ActiveForm;
  FullScreenForm.Width := Screen.Width;
  FullScreenForm.Height := Screen.Height;
  FullScreenForm.Top := 0;
  FullScreenForm.Left := 0;
  FullScreenForm.cp.Copy(maincp);
  FullScreenForm.cp.cmap := maincp.cmap;
  FullScreenForm.center[0] := center[0];
  FullScreenForm.center[1] := center[1];
  FullScreenForm.Calculate := True;
  FullScreenForm.Show;
end;

procedure TMainForm.mnuRenderClick(Sender: TObject);
var
  Ext: string;
  NewRender: Boolean;
begin
  NewRender := True;

  if Assigned(RenderForm.Renderer) then
    if Application.MessageBox(PChar(TextByKey('render-status-confirmstop')), 'Apophysis', 36) = ID_NO then
      NewRender := false;

  if NewRender then
  begin

    if Assigned(RenderForm.Renderer) then RenderForm.Renderer.Terminate;
    if Assigned(RenderForm.Renderer) then RenderForm.Renderer.WaitFor; // hmm #1
    RenderForm.ResetControls;
    RenderForm.PageCtrl.TabIndex := 0;

    case renderFileFormat of
      1: Ext := '.bmp';
      2: Ext := '.png';
      3: Ext := '.jpg';
    end;

    //RenderForm.caption := 'Render ' + #39 + maincp.name + #39 + ' to Disk';
    RenderForm.Filename := RenderPath + maincp.name + Ext;
    RenderForm.SaveDialog.FileName := RenderPath + maincp.name + Ext;
    RenderForm.txtFilename.Text := ChangeFileExt(RenderForm.SaveDialog.Filename, Ext);

    RenderForm.cp.Copy(MainCP);
    RenderForm.cp.cmap := maincp.cmap;
    RenderForm.zoom := maincp.zoom;
    RenderForm.Center[0] := center[0];
    RenderForm.Center[1] := center[1];
    if Assigned(RenderForm.Renderer) then RenderForm.Renderer.WaitFor; // hmm #2
  end;
  RenderForm.Show;
end;

procedure TMainForm.mnuRenderAllClick(Sender: TObject);
var
  Ext: string;
  NewRender: Boolean;
begin
  NewRender := True;

  if Assigned(RenderForm.Renderer) then
    if Application.MessageBox(PChar(TextByKey('render-status-confirmstop')), 'Apophysis', 36) = ID_NO then
      NewRender := false;

  if NewRender then
  begin

    if Assigned(RenderForm.Renderer) then RenderForm.Renderer.Terminate;
    if Assigned(RenderForm.Renderer) then RenderForm.Renderer.WaitFor; // hmm #1
    RenderForm.ResetControls;
    RenderForm.PageCtrl.TabIndex := 0;

    case renderFileFormat of
      1: Ext := '.bmp';
      2: Ext := '.png';
      3: Ext := '.jpg';
    end;

    //RenderForm.caption := 'Render all flames to disk';
    RenderForm.bRenderAll := true;
    RenderForm.Filename := RenderPath + maincp.name + Ext;
    RenderForm.SaveDialog.FileName := RenderForm.Filename;
    RenderForm.txtFilename.Text := ChangeFileExt(RenderForm.SaveDialog.Filename, Ext);

    RenderForm.cp.Copy(MainCP);
    RenderForm.cp.cmap := maincp.cmap;
    RenderForm.zoom := maincp.zoom;
    RenderForm.Center[0] := center[0];
    RenderForm.Center[1] := center[1];
    if Assigned(RenderForm.Renderer) then RenderForm.Renderer.WaitFor; // hmm #2
  end;
  RenderForm.Show;
end;

procedure TMainForm.mnuMutateClick(Sender: TObject);
begin
  MutateForm.Show;
  MutateForm.UpdateDisplay;
end;

procedure TMainForm.mnuAdjustClick(Sender: TObject);
begin
  AdjustForm.UpdateDisplay;
  AdjustForm.PageControl.TabIndex := 0;
  AdjustForm.Show;
end;

procedure TMainForm.mnuResetLocationClick(Sender: TObject);
var
  scale: double;
  dx, dy, cdx, cdy: double;
  sina, cosa: extended;
begin
  UpdateUndo;

  scale := MainCP.pixels_per_unit / MainCP.Width * power(2, MainCP.zoom);
  cdx := MainCP.center[0];
  cdy := MainCP.center[1];

  ResetLocation;

  cdx := MainCP.center[0] - cdx;
  cdy := MainCP.center[1] - cdy;
  Sincos(MainCP.FAngle, sina, cosa);
  if IsZero(sina) then begin
    dy := cdy*cosa {- cdx*sina};
    dx := (cdx {+ dy*sina})/cosa;
  end
  else begin
    dx := cdy*sina + cdx*cosa;
    dy := (dx*cosa - cdx)/sina;
  end;
  FViewPos.x := FViewPos.x - dx * scale * Image.Width;
  FViewPos.y := FViewPos.y - dy * scale * Image.Width;

  FViewScale := FViewScale * MainCP.pixels_per_unit / MainCP.Width * power(2, MainCP.zoom) / scale;

  DrawImageView;

  RedrawTimer.enabled := true;
  UpdateWindows;
end;

procedure TMainForm.mnuAboutClick(Sender: TObject);
begin
  AboutForm.ShowModal;
end;

procedure TMainForm.mnuOpenGradientClick(Sender: TObject);
begin
  GradientBrowser.Filename := GradientFile;
  GradientBrowser.Show;
end;

procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  if Assigned(RenderForm.Renderer) then
    if Application.MessageBox(PChar(TextByKey('render-status-confirmstop')), 'Apophysis', 36) = ID_NO then
      CanClose := False;

  AboutToExit := CanClose;
end;

procedure TMainForm.FormActivate(Sender: TObject);
begin
  if Assigned(Renderer) then Renderer.Priority := tpNormal;
end;

procedure TMainForm.FormDeactivate(Sender: TObject);
begin
  if Assigned(Renderer) then Renderer.Priority := tpLower;
end;

procedure TMainForm.mnuCalculateColorsClick(Sender: TObject);
var
  i: integer;
begin
  StopThread;
  UpdateUndo;
  for i := 0 to Transforms - 1 do
    maincp.xform[i].color := i / (transforms - 1);
  RedrawTimer.Enabled := True;
  UpdateWindows;
end;

procedure TMainForm.mnuRandomizeColorValuesClick(Sender: TObject);
var
  i: integer;
begin
  inc(MainSeed);
  RandSeed := MainSeed;
  StopThread;
  UpdateUndo;
  for i := 0 to Transforms - 1 do
    maincp.xform[i].color := random;
  RedrawTimer.Enabled := True;
  UpdateWindows;
end;


procedure TMainForm.mnuEditScriptClick(Sender: TObject);
begin
{$ifdef DisableScripting}
{$else}
  ScriptEditor.Show;
{$endif}
end;

procedure TMainForm.btnRunClick(Sender: TObject);
begin
  {$ifdef DisableScripting}
{$else}
  ScriptEditor.RunScript;
{$endif}
end;

procedure TMainForm.mnuRunClick(Sender: TObject);
begin
{$ifdef DisableScripting}
{$else}
  ScriptEditor.RunScript;
{$endif}
end;

procedure TMainForm.mnuOpenScriptClick(Sender: TObject);
begin
{$ifdef DisableScripting}
{$else}
  ScriptEditor.OpenScript;
{$endif}
end;

procedure TMainForm.mnuStopClick(Sender: TObject);
begin
{$ifdef DisableScripting}
{$else}
  ScriptEditor.Stopped := True;
{$endif}
end;

procedure TMainForm.mnuImportGimpClick(Sender: TObject);
var
  flist: tStringList;
begin
  flist := TStringList.Create;
  OpenDialog.Filter := Format('%s|*.*', [TextByKey('common-filter-allfiles')]);
  try
    if OpenDialog.Execute then
    begin
      flist.loadFromFile(OpenDialog.filename);
      maincp.clear;
      maincp.ParseStringList(flist);
      maincp.Width := Image.Width;
      maincp.Height := Image.Height;
      maincp.zoom := 0;
      maincp.CalcBoundBox;
      center[0] := maincp.center[0];
      center[1] := maincp.center[1];
      RedrawTimer.Enabled := True;
      Application.ProcessMessages;
      Transforms := MainCp.TrianglesFromCP(MainTriangles);
      UpdateWindows;
    end;
  finally
    flist.free
  end;
end;

procedure TMainForm.mnuManageFavoritesClick(Sender: TObject);
var
  MenuItem: TMenuItem;
  i: integer;
  s: string;
begin
{$ifdef DisableScripting}
{$else}
  if FavoritesForm.ShowModal = mrOK then
  begin
    if favorites.count <> 0 then
    begin
      mnuScript.Items[7].free; // remember to increment if add any items above
      for i := 0 to Favorites.Count - 1 do
      begin
        s := ExtractFileName(Favorites[i]);
        s := Copy(s, 0, length(s) - Length(ExtractFileExt(s)));
        MenuItem := mnuScript.Find(s);
        if MenuItem <> nil then
          MenuItem.Free;
      end
    end;
    GetScripts;
  end;
{$endif}
end;

procedure TMainForm.DisableFavorites;
var
  MenuItem: TMenuItem;
  i: integer;
  s: string;
begin
  for i := 0 to Favorites.Count - 1 do
  begin
    s := ExtractFileName(Favorites[i]);
    s := Copy(s, 0, length(s) - Length(ExtractFileExt(s)));
    MenuItem := mnuScript.Find(s);
    if MenuItem <> nil then
      MenuItem.Enabled := False;
  end;
end;

procedure TMainForm.EnableFavorites;
var
  MenuItem: TMenuItem;
  i: integer;
  s: string;
begin
  for i := 0 to Favorites.Count - 1 do
  begin
    s := ExtractFileName(Favorites[i]);
    s := Copy(s, 0, length(s) - Length(ExtractFileExt(s)));
    MenuItem := mnuScript.Find(s);
    if MenuItem <> nil then
      MenuItem.Enabled := True;
  end;
end;

procedure TMainForm.mnuShowFullClick(Sender: TObject);
begin
  FullScreenForm.Calculate := False;
  FullScreenForm.Show;
end;

procedure TMainForm.mnuImageSizeClick(Sender: TObject);
begin
//  SizeTool.Show;
  AdjustForm.UpdateDisplay;
  AdjustForm.PageControl.TabIndex:=3;
  AdjustForm.Show;
end;

procedure TMainForm.ApplicationEventsActivate(Sender: TObject);
begin
  if GradientInClipboard then
  begin
//    GradientForm.mnuPaste.enabled := true;
//    GradientForm.btnPaste.enabled := true;
    AdjustForm.mnuPaste.enabled := true;
    AdjustForm.btnPaste.enabled := true;
  end
  else
  begin
//    GradientForm.mnuPaste.enabled := false;
//    GradientForm.btnPaste.enabled := false;
    AdjustForm.mnuPaste.enabled := false;
    AdjustForm.btnPaste.enabled := false;
  end;
  if FlameInClipboard then
  begin
    mnuPaste.enabled := true;
  end
  else
  begin
    mnuPaste.enabled := false;
  end;
end;

procedure TMainForm.ParseXML(var cp1: TControlPoint; const params: string; const ignoreErrors : boolean);
var
  i: integer; temp: string;
  h, s, v: real;
begin
  CurrentFlame := cp1.name;
  nxform := 0;
  FinalXformLoaded := false;
  ActiveXformSet := 0;
  XMLPaletteFormat := '';
  XMLPaletteCount := 0;
  ParseHandledPluginList := false;
  SurpressHandleMissingPlugins := ignoreErrors;
//  Parsecp.cmapindex := -2; // generate palette from cmapindex and hue (apo 1 and earlier)
//  ParseCp.symmetry := 0;
//  ParseCP.finalXformEnabled := false;
  //ParseCP.Clear;

  ParseCp.Free;                    // we're creating this CP from the scratch
  ParseCp := TControlPoint.create; // to reset variables properly (randomize)

  //LoadCpFromXmlCompatible(params, ParseCP, temp);

  XMLScanner.LoadFromBuffer(TCharType(TStringType(params)));
  XMLScanner.Execute;

  cp1.copy(ParseCp);
  if Parsecp.cmapindex = -2 then
  begin
    if cp1.cmapindex < NRCMAPS then
      GetCMap(cp1.cmapindex, 1, cp1.cmap)
    {else
      ShowMessage('Palette index too high')};

    if (cp1.hue_rotation > 0) and (cp1.hue_rotation < 1) then begin
      for i := 0 to 255 do
      begin
        RGBToHSV(cp1.cmap[i][0], cp1.cmap[i][1], cp1.cmap[i][2], h, s, v);
        h := Round(360 + h + (cp1.hue_rotation * 360)) mod 360;
        HSVToRGB(h, s, v, cp1.cmap[i][0], cp1.cmap[i][1], cp1.cmap[i][2]);
      end;
    end;
  end;

  if FinalXformLoaded = false then begin
    cp1{MainCP}.xform[nxform].Clear;
    cp1{MainCP}.xform[nxform].symmetry := 1;
  end;

  if nxform < NXFORMS then
     for i := nxform to NXFORMS - 1 do
      cp1.xform[i].density := 0;

  // Check for symmetry parameter
  if ParseCp.symmetry <> 0 then
  begin
    add_symmetry_to_control_point(cp1, ParseCp.symmetry);
    cp1.symmetry := 0;
  end;

  cp1.FillUsedPlugins;
  ParseHandledPluginList := false;
  SurpressHandleMissingPlugins := false;
end;

procedure TMainForm.mnuPasteClick(Sender: TObject);
begin
  if Clipboard.HasFormat(CF_TEXT) then begin
    UpdateUndo;
{$ifdef DisableScripting}
{$else}
  ScriptEditor.Stopped := True;
{$endif}
    StopThread;
    ParseXML(MainCP, PCHAR(Clipboard.AsText), false);
    AnnoyUser;
    Transforms := MainCp.TrianglesFromCP(MainTriangles);
    Statusbar.Panels[3].Text := MainCp.name;
    {if ResizeOnLoad then}
    ResizeImage;
    RedrawTimer.Enabled := True;
    Application.ProcessMessages;
    UpdateWindows;
  end;
end;

procedure TMainForm.mnuCopyClick(Sender: TObject);
var
  txt: string;
begin
  txt := Trim(FlameToXML(Maincp, false, false));
  Clipboard.SetTextBuf(PChar(txt));
  mnuPaste.enabled := true;

  AdjustForm.mnuPaste.enabled := False;
  AdjustForm.btnPaste.enabled := False;
end;

function WinShellExecute(const Operation, AssociatedFile: string): Boolean;
var
  a1: string;
  r: Cardinal;
begin
  a1 := Operation;
  if a1 = '' then
    a1 := 'open';

  r := ShellExecute(
    application.handle
    , pchar(a1)
    , pchar(AssociatedFile)
    , ''
    , ''
    , SW_SHOWNORMAL
    );
  if (r > 32) then WinShellExecute := true
  else WinShellExecute := false;
end;

procedure WinShellOpen(const AssociatedFile: string);
begin
  WinShellExecute('open', AssociatedFile);
end;


procedure TMainForm.mnuExportFlameClick(Sender: TObject);
var
  FileList: Tstringlist;
  Ext, ex, Path: string;
  cp1: TControlPoint;
begin
  if not FileExists(flam3Path) then
  begin
    Application.MessageBox(PChar(TextByKey('main-status-noflam3')), 'Apophysis', 16);
    exit;
  end;
  case ExportFileFormat of
    1: Ext := 'jpg';
    2: Ext := 'ppm';
    3: Ext := 'png';
  end;
  FileList := TstringList.Create;
  cp1 := TControlPoint.Create;
  cp1.copy(Maincp);
  ExportDialog.ImageWidth := ExportWidth;
  ExportDialog.ImageHeight := ExportHeight;
  ExportDialog.Sample_density := ExportDensity;
  ExportDialog.Filter_Radius := ExportFilter;
  ExportDialog.Oversample := ExportOversample;
  try
    ExportDialog.Filename := RenderPath + Maincp.name + '.' + Ext;
    if ExportDialog.ShowModal = mrOK then
    begin
      ex := ExtractFileExt(ExportDialog.Filename);
      if ExtractFileExt(ExportDialog.Filename) = '.ppm' then
        ExportFileFormat := 2
      else if ExtractFileExt(ExportDialog.Filename) = '.png' then
        ExportFileFormat := 3
      else
        ExportFileFormat := 1;
      case ExportFileFormat of
        1: Ext := 'jpg';
        2: Ext := 'ppm';
        3: Ext := 'png';
      end;
      ExportWidth := ExportDialog.ImageWidth;
      ExportHeight := ExportDialog.ImageHeight;
      ExportDensity := ExportDialog.Sample_density;
      ExportFilter := ExportDialog.Filter_Radius;
      ExportOversample := ExportDialog.Oversample;
      ExportBatches := ExportDialog.Batches;
      ExportEstimator := ExportDialog.Estimator;
      ExportEstimatorMin := ExportDialog.EstimatorMin;
      ExportEstimatorCurve := ExportDialog.EstimatorCurve;
      ExportJitters := ExportDialog.Jitters;
      ExportGammaTreshold := ExportDialog.GammaTreshold;
      cp1.sample_density := ExportDensity;
      cp1.spatial_oversample := ExportOversample;
      cp1.spatial_filter_radius := ExportFilter;
      cp1.nbatches := ExportBatches;
      if (cp1.width <> ExportWidth) or (cp1.Height <> ExportHeight) then
        cp1.AdjustScale(ExportWidth, ExportHeight);
      cp1.estimator := ExportEstimator;
      cp1.estimator_min := ExportEstimatorMin;
      cp1.estimator_curve := ExportEstimatorCurve;
      cp1.jitters := ExportJitters;
      cp1.gamma_threshold := ExportGammaTreshold;
      FileList.Text := FlameToXML(cp1, true, false);
      FileList.SaveToFile(ChangeFileExt(ExportDialog.Filename, '.flame'));
      FileList.Clear;
      FileList.Add('@echo off');
      FileList.Add('set verbose=1');
      FileList.Add('set format=' + Ext);
      if ExportFileFormat = 1 then
        FileList.Add('set jpeg=' + IntToStr(JPEGQuality));
      case ExportDialog.cmbDepth.ItemIndex of
        0: FileList.Add('set bits=16');
        1: FileList.Add('set bits=32');
        2: FileList.Add('set bits=33');
        3: FileList.Add('set bits=64');
      end;
      if ExportDialog.udStrips.Position > 1 then
        FileList.Add('set nstrips=' + IntToStr(ExportDialog.udStrips.Position));
      if (PNGTransparency > 0) then
        FileList.Add('set transparency=1')
      else
        FileList.Add('set transparency=0');
      FileList.Add('set out=' + ExportDialog.Filename);
      FileList.Add('@echo Rendering "' + ExportDialog.Filename + '"');
{
      FileList.Add(ExtractShortPathName(hqiPath) + ' < ' + ExtractShortPathName(ChangeFileExt(ExportDialog.Filename, '.flame')));
      Path := ExtractShortPathName(ExtractFileDir(ExportDialog.Filename) + '\');
}
      FileList.Add('"' + flam3Path + '" < "' + ChangeFileExt(ExportDialog.Filename, '.flame') + '"');
      Path := ExtractFilePath(ExtractFileDir(ExportDialog.Filename) + '\');

      FileList.SaveToFile(ChangeFileExt(ExportDialog.Filename, '.bat'));
      if ExportDialog.chkRender.Checked then
      begin
        SetCurrentDir(Path);
        WinShellOpen(ChangeFileExt(ExportDialog.Filename, '.bat'));
      end;
    end;
  finally
    FileList.Free;
    cp1.free;
  end;

end;

////////////////////////////////////////////////////////////////////////////////

procedure ParseCompactColors(cp: TControlPoint; count: integer; in_data: string; alpha: boolean = true);
  function HexChar(c: Char): Byte;
  begin
    case c of
      '0'..'9':  Result := Byte(c) - Byte('0');
      'a'..'f':  Result := (Byte(c) - Byte('a')) + 10;
      'A'..'F':  Result := (Byte(c) - Byte('A')) + 10;
    else
      Result := 0;
    end;
  end;
var
  i, pos, len: integer;
  c: char;
  data: string;
begin
  // diable generating pallete
  if Parsecp.cmapindex = -2 then
    Parsecp.cmapindex := -1;

  Assert(Count = 256, 'only 256 color gradients are supported at the moment');
  data := '';
  for i := 1 to Length(in_data) do
  begin
    c := in_data[i];
    if CharInSet(c,['0'..'9']+['A'..'F']+['a'..'f']) then data := data + c;
  end;

  if alpha then len := count * 8
  else len := count * 6;

  Assert(len = Length(data), 'color-data size mismatch');

  for i := 0 to Count-1 do begin
    if alpha then pos := i*8 + 2
    else pos := i*6;
    Parsecp.cmap[i][0] := 16 * HexChar(Data[pos + 1]) + HexChar(Data[pos + 2]);
    Parsecp.cmap[i][1] := 16 * HexChar(Data[pos + 3]) + HexChar(Data[pos + 4]);
    Parsecp.cmap[i][2] := 16 * HexChar(Data[pos + 5]) + HexChar(Data[pos + 6]);
  end;
end;

procedure TMainForm.ListXmlScannerStartTag(Sender: TObject;
  TagName: string; Attributes: TAttrList);
begin
  pname := String(Attributes.value(TStringType('name')));
  ptime := String(Attributes.value(TStringType('time')));
end;

procedure TMainForm.XMLScannerStartTag(Sender: TObject; TagName: string;
  Attributes: TAttrList);
var
  Tokens: TStringList;
  v: TStringType;
  ParsePos, i : integer;
begin
  Tokens := TStringList.Create;
 try

  if TagName='xformset' then // unused in this release...
  begin
    v := Attributes.Value(TStringType('enabled'));
    if v <> '' then ParseCP.finalXformEnabled := (StrToInt(String(v)) <> 0)
    else ParseCP.finalXformEnabled := true;

    inc(activeXformSet);
  end
  else if TagName='flame' then
  begin
    BeginParsing;
    
    v := Attributes.value(TStringType('name'));
    if v <> '' then Parsecp.name := String(v) else Parsecp.name := 'untitled';
    v := Attributes.Value('time');
    if v <> '' then Parsecp.Time := StrToFloat(String(v));
    v := Attributes.value('palette');
    if v <> '' then
      Parsecp.cmapindex := StrToInt(String(v))
    else
      Parsecp.cmapindex := -1;
    v := Attributes.value('gradient');
    if v <> '' then
      Parsecp.cmapindex := StrToInt(String(v))
    else
      Parsecp.cmapindex := -1;
    ParseCP.hue_rotation := 1;

    v := Attributes.value('hue');
    if v <> '' then Parsecp.hue_rotation := StrToFloat(String(v));
    v := Attributes.Value('brightness');
    if v <> '' then Parsecp.Brightness := StrToFloat(String(v));
    v := Attributes.Value('gamma');
    if v <> '' then Parsecp.gamma := StrToFloat(String(v));
    v := Attributes.Value('vibrancy');
    if v <> '' then Parsecp.vibrancy := StrToFloat(String(v));
    if (LimitVibrancy) and (Parsecp.vibrancy > 1) then Parsecp.vibrancy := 1;
    v := Attributes.Value('gamma_threshold');
    if v <> '' then Parsecp.gamma_threshold := StrToFloat(String(v))
    else Parsecp.gamma_threshold := 0;

    v := Attributes.Value('zoom');
    if v <> '' then Parsecp.zoom := StrToFloat(String(v));
    v := Attributes.Value('scale');
    if v <> '' then Parsecp.pixels_per_unit := StrToFloat(String(v));
    v := Attributes.Value('rotate');
    if v <> '' then Parsecp.FAngle := -PI * StrToFloat(String(v))/180;
    v := Attributes.Value('angle');
    if v <> '' then Parsecp.FAngle := StrToFloat(String(v));

    // 3d
    v := Attributes.Value('cam_pitch');
    if v <> '' then Parsecp.cameraPitch := StrToFloat(String(v));
    v := Attributes.Value('cam_yaw');
    if v <> '' then Parsecp.cameraYaw := StrToFloat(String(v));
    v := Attributes.Value('cam_dist');
    if v <> '' then Parsecp.cameraPersp := 1/StrToFloat(String(v));
    v := Attributes.Value('cam_perspective');
    if v <> '' then Parsecp.cameraPersp := StrToFloat(String(v));
    v := Attributes.Value('cam_zpos');
    if v <> '' then Parsecp.cameraZpos := StrToFloat(String(v));
    v := Attributes.Value('cam_dof');
    if v <> '' then Parsecp.cameraDOF := abs(StrToFloat(String(v)));

    //density estimation
    v := Attributes.Value('estimator_radius');
    if v <> '' then Parsecp.estimator := StrToFloat(String(v));
    v := Attributes.Value('estimator_minimum');
    if v <> '' then Parsecp.estimator_min := StrToFloat(String(v));
    v := Attributes.Value('estimator_curve');
    if v <> '' then Parsecp.estimator_curve := StrToFloat(String(v));
    v := Attributes.Value('enable_de');
    if (v = '1') then Parsecp.enable_de := true;

    v := Attributes.Value('new_linear');
    if (v = '1') then Parsecp.noLinearFix := true
    else ParseCp.noLinearFix := false;

    v := Attributes.Value('curves');
    if (v <> '') then begin
      GetTokens(String(v), tokens);
      ParsePos := -1;
      for i := 0 to 3 do
      begin
        Inc(ParsePos);ParseCp.curvePoints[i][0].x := StrToFloat(Tokens[ParsePos]);
        Inc(ParsePos);ParseCp.curvePoints[i][0].y := StrToFloat(Tokens[ParsePos]);
        Inc(ParsePos);ParseCp.curveWeights[i][0] := StrToFloat(Tokens[ParsePos]);
        Inc(ParsePos);ParseCp.curvePoints[i][1].x := StrToFloat(Tokens[ParsePos]);
        Inc(ParsePos);ParseCp.curvePoints[i][1].y := StrToFloat(Tokens[ParsePos]);
        Inc(ParsePos);ParseCp.curveWeights[i][1] := StrToFloat(Tokens[ParsePos]);
        Inc(ParsePos);ParseCp.curvePoints[i][2].x := StrToFloat(Tokens[ParsePos]);
        Inc(ParsePos);ParseCp.curvePoints[i][2].y := StrToFloat(Tokens[ParsePos]);
        Inc(ParsePos);ParseCp.curveWeights[i][2] := StrToFloat(Tokens[ParsePos]);
        Inc(ParsePos);ParseCp.curvePoints[i][3].x := StrToFloat(Tokens[ParsePos]);
        Inc(ParsePos);ParseCp.curvePoints[i][3].y := StrToFloat(Tokens[ParsePos]);
        Inc(ParsePos);ParseCp.curveWeights[i][3] := StrToFloat(Tokens[ParsePos]);
      end;

    end;

    try
      v := Attributes.Value('center');
      GetTokens(String(v), tokens);

      Parsecp.center[0] := StrToFloat(Tokens[0]);
      Parsecp.center[1] := StrToFloat(Tokens[1]);
    except
      Parsecp.center[0] := 0;
      Parsecp.center[1] := 0;
    end;

    v := Attributes.Value('size');
    GetTokens(String(v), tokens);

    Parsecp.width := StrToInt(Tokens[0]);
    Parsecp.height := StrToInt(Tokens[1]);

    try
      v := Attributes.Value('background');
      GetTokens(String(v), tokens);

      Parsecp.background[0] := Floor(StrToFloat(Tokens[0]) * 255);
      Parsecp.background[1] := Floor(StrToFloat(Tokens[1]) * 255);
      Parsecp.background[2] := Floor(StrToFloat(Tokens[2]) * 255);
    except
      Parsecp.background[0] := 0;
      Parsecp.background[1] := 0;
      Parsecp.background[2] := 0;
    end;

    v := Attributes.Value('soloxform');
    if v <> '' then Parsecp.soloXform := StrToInt(String(v));

    v := Attributes.Value('plugins');
    GetTokens(String(v), tokens);
    if (tokens.Count > 0) then begin
      ParseCP.used_plugins.Clear;

      for i := 0 to tokens.Count - 1 do
        ParseCP.used_plugins.Add(tokens[i]);
    end;

    v := Attributes.Value('nick');
    if Trim(String(v)) = '' then v := TStringType(SheepNick);
    Parsecp.Nick := String(v);
    v := Attributes.Value('url');
    if Trim(String(v)) = '' then v := TStringType(SheepUrl);
    Parsecp.URL := String(v);
  end
  else if TagName='palette' then
  begin
    XMLPaletteFormat := String(Attributes.Value('format'));
    XMLPaletteCount := StrToIntDef(String(Attributes.Value('count')), 256);
  end;
 finally
    Tokens.free;
 end;
end;

function flatten_val(Attributes: TAttrList): double;
var
  vv: array of double;
  vn: array of string;
  i: integer;
  s: string;
  d: boolean;
begin

  SetLength(vv, 24);
  SetLength(vn, 24);

  d := false;

  vn[0] := 'linear3D'; vn[1] := 'bubble';
  vn[2] := 'cylinder'; vn[3] := 'zblur';
  vn[4] := 'blur3D'; vn[5] := 'pre_ztranslate';
  vn[6] := 'pre_rotate_x'; vn[7] := 'pre_rotate_y';
  vn[8] := 'ztranslate'; vn[9] := 'zcone';
  vn[10] := 'post_rotate_x'; vn[11] := 'post_rotate_y';
  vn[12] := 'julia3D'; vn[13] := 'julia3Dz';
  vn[14] := 'curl3D_cz'; vn[15] := 'hemisphere';
  vn[16] := 'bwraps2'; vn[17] := 'bwraps';
  vn[18] := 'falloff2'; vn[19] := 'crop';
  vn[20] := 'pre_falloff2'; vn[21] := 'pre_crop';
  vn[22] := 'post_falloff2'; vn[23] := 'post_crop';


  for i := 0 to 23 do
  begin
    s := String(Attributes.Value(TStringType(vn[i])));
    if (s <> '') then vv[i] := StrToFloat(s)
    else vv[i] := 0;
    d := d or (vv[i] <> 0);
  end;

  if (d) then Result := 0
  else Result := 1;

  SetLength(vv, 0);
  SetLength(vn, 0);
end;
function linear_val(Attributes: TAttrList): double;
var
  vv: array of double;
  vn: array of string;
  i: integer;
  s: string;
begin
  SetLength(vv, 2);
  SetLength(vn, 2);

  Result := 0;

  vn[0] := 'linear3D';
  vn[1] := 'linear';
  for i := 0 to 1 do
  begin
    s := String(Attributes.Value(TStringType(vn[i])));
    if (s <> '') then vv[i] := StrToFloat(s)
    else vv[i] := 0;
    Result := Result + vv[i];
  end;

  SetLength(vv, 0);
  SetLength(vn, 0);
end;

procedure TMainForm.XmlScannerContent(Sender: TObject; Content: String);
begin
  if XMLPaletteCount <= 0 then begin
    //ShowMessage('ERROR: No colors in palette!');
    Application.MessageBox(PChar(TextByKey('common-invalidformat')), 'Apophysis', MB_ICONERROR);
    exit;
  end;
  if XMLPaletteFormat = 'RGB' then
  begin
    ParseCompactColors(ParseCP, XMLPaletteCount, Content, false);
  end
  else if XMLPaletteFormat = 'RGBA' then
  begin
    ParseCompactColors(ParseCP, XMLPaletteCount, Content);
  end
  else begin
    Application.MessageBox(PChar(TextByKey('common-invalidformat')), 'Apophysis', MB_ICONERROR);
    exit;
  end;
  Parsecp.cmapindex := -1;

  XMLPaletteFormat := '';
  XMLPaletteCount := 0;
end;

procedure TMainForm.XMLScannerEmptyTag(Sender: TObject; TagName: string;
  Attributes: TAttrList);
var
  i: integer;
  v, l, l3d: TStringType;
  d, floatcolor, vl, vl3d: double;
  Tokens: TStringList;
begin

  Tokens := TStringList.Create;
  try
    if (TagName = 'xform') or (TagName = 'finalxform') then
     if {(TagName = 'finalxform') and} (FinalXformLoaded) then Application.MessageBox(PChar(TextByKey('common-invalidformat')), 'Apophysis', MB_ICONERROR)
     else
    begin
      for i := 0 to Attributes.Count - 1 do begin
        if not ScanVariations(String(attributes.Name(i))) and
           not ScanVariables(String(attributes.Name(i))) then
           CheckAttribute(String(Attributes.Name(i)));
      end;
      if (TagName = 'finalxform') or (activeXformSet > 0) then FinalXformLoaded := true;

     with ParseCP.xform[nXform] do begin
      Clear;
      v := Attributes.Value('weight');
      if (v <> '') and (TagName = 'xform') then density := StrToFloat(String(v));
      if (TagName = 'finalxform') then
      begin
        v := Attributes.Value('enabled');
        if v <> '' then ParseCP.finalXformEnabled := (StrToInt(String(v)) <> 0)
        else ParseCP.finalXformEnabled := true;
      end;

      if activexformset > 0 then density := 0; // tmp...

      v := Attributes.Value('color');
      if v <> '' then color := StrToFloat(String(v));
      v := Attributes.Value('var_color');
      if v <> '' then pluginColor := StrToFloat(String(v));
      v := Attributes.Value('symmetry');
      if v <> '' then symmetry := StrToFloat(String(v));
      v := Attributes.Value('coefs');
      GetTokens(String(v), tokens);
      if Tokens.Count < 6 then Application.MessageBox(PChar(TextByKey('common-invalidformat')), 'Apophysis', MB_ICONERROR);
      c[0][0] := StrToFloat(Tokens[0]);
      c[0][1] := StrToFloat(Tokens[1]);
      c[1][0] := StrToFloat(Tokens[2]);
      c[1][1] := StrToFloat(Tokens[3]);
      c[2][0] := StrToFloat(Tokens[4]);
      c[2][1] := StrToFloat(Tokens[5]);

      v := Attributes.Value('post');
      if v <> '' then begin
        GetTokens(String(v), tokens);
        if Tokens.Count < 6 then Application.MessageBox(PChar(TextByKey('common-invalidformat')), 'Apophysis', MB_ICONERROR);
        p[0][0] := StrToFloat(Tokens[0]);
        p[0][1] := StrToFloat(Tokens[1]);
        p[1][0] := StrToFloat(Tokens[2]);
        p[1][1] := StrToFloat(Tokens[3]);
        p[2][0] := StrToFloat(Tokens[4]);
        p[2][1] := StrToFloat(Tokens[5]);
      end;

      v := Attributes.Value('chaos');
      if v <> '' then begin
        GetTokens(String(v), tokens);
        for i := 0 to Tokens.Count-1 do
          modWeights[i] := Abs(StrToFloat(Tokens[i]));
      end;
      //else for i := 0 to NXFORMS-1 do modWeights[i] := 1;

      // for 2.09 flames compatibility
      v := Attributes.Value('opacity');
      if v <> '' then begin
        if StrToFloat(String(v)) = 0.0 then begin
          transOpacity := 0;
        end else begin
          transOpacity := StrToFloat(String(v));
        end;
      end;

      // 7x.9 name tag
      v := Attributes.Value('name');
      if v <> '' then begin
        TransformName := String(v);
      end;

      v := Attributes.Value('plotmode');
      if v <> '' then begin
        if v = 'off' then begin
          transOpacity := 0;
        end;
      end;

      // tricky: attempt to convert parameters to 15C+-format if necessary
      if (ParseCp.noLinearFix) then
        for i := 0 to 1 do
        begin
          SetVariation(i, 0);
          v := TStringType(ReadWithSubst(Attributes, varnames(i)));
          //v := Attributes.Value(AnsiString(varnames(i)));
          if v <> '' then
              SetVariation(i, StrToFloat(String(v)));
        end
      else begin
        SetVariation(0, linear_val(Attributes));
        SetVariation(1, flatten_val(Attributes));
      end;

      // now parse the rest of the variations...as usual
      for i := 2 to NRVAR - 1 do
      begin
        SetVariation(i, 0);
        v := TStringType(ReadWithSubst(Attributes, varnames(i)));
        //v := Attributes.Value(AnsiString(varnames(i)));
        if v <> '' then
            SetVariation(i, StrToFloat(String(v)));
      end;

      // and the variables
      for i := 0 to GetNrVariableNames - 1 do begin
        v := TStringType(ReadWithSubst(Attributes, GetVariableNameAt(i)));
        //v := Attributes.Value(AnsiString(GetVariableNameAt(i)));
        if v <> '' then begin
          {$ifndef VAR_STR}
          d := StrToFloat(String(v));
          SetVariable(GetVariableNameAt(i), d);
          {$else}
          SetVariableStr(GetVariableNameAt(i), String(v));
          {$endif}
        end;
      end;

      // legacy variation/variable notation
      v := Attributes.Value('var1');
      if v <> '' then
      begin
        for i := 0 to NRVAR - 1 do
          SetVariation(i, 0);
        SetVariation(StrToInt(String(v)), 1);
      end;
      v := Attributes.Value('var');
      if v <> '' then
      begin
        for i := 0 to NRVAR - 1 do
          SetVariation(i, 0);
        GetTokens(String(v), tokens);
        if Tokens.Count > NRVAR then Application.MessageBox(PChar(TextByKey('common-invalidformat')), 'Apophysis', MB_ICONERROR);
        for i := 0 to Tokens.Count - 1 do
          SetVariation(i, StrToFloat(Tokens[i]));
      end;
     end;
      Inc(nXform);
    end;
    if TagName = 'color' then
    begin
      // disable generating palette
      //if Parsecp.cmapindex = -2 then
        Parsecp.cmapindex := -1;

      i := StrToInt(String(Attributes.value('index')));
      v := Attributes.value('rgb');
      GetTokens(String(v), tokens);
      floatcolor := StrToFloat(Tokens[0]);
      Parsecp.cmap[i][0] := round(floatcolor);
      floatcolor := StrToFloat(Tokens[1]);
      Parsecp.cmap[i][1] := round(floatcolor);
      floatcolor := StrToFloat(Tokens[2]);
      Parsecp.cmap[i][2] := round(floatcolor);
    end;
    if TagName = 'colors' then
    begin
      ParseCompactcolors(Parsecp, StrToInt(String(Attributes.value('count'))),
        String(Attributes.value('data')));
      Parsecp.cmapindex := -1;
    end;
    if TagName = 'symmetry' then
    begin
      i := StrToInt(String(Attributes.value('kind')));
      Parsecp.symmetry := i;
    end;
    if TagName = 'xdata' then
    begin
      Parsecp.xdata := Parsecp.xdata + String(Attributes.value('content'));
    end;
  finally
    Tokens.free;
  end;
end;

procedure TMainForm.mnuFlamepdfClick(Sender: TObject);
begin
  WinShellOpen('http://www.flam3.com/flame_draves.pdf');
end;

///////////////////////////////////////////////////////////////////////////////
procedure TMainForm.ImageMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if button = mbMiddle then begin
    //FMouseMoveState := msHeight;
    exit;
  end else if button = mbRight then begin
  //FMouseMoveState := msPitchYaw;
    camDragValueY := MainCP.cameraPitch * 180.0 / PI;
    camDragValueX := MainCP.cameraYaw * 180.0 / PI;

     camDragMode := true;
     camDragPos.x := 0;
     camDragPos.y := 0;
     camDragOld.x := x;
     camDragOld.y := y;
     camMM := false;
     //SetCaptureControl(TControl(Sender));

    //Screen.Cursor := crNone;
    //GetCursorPos(mousepos); // hmmm
    //mousePos := (Sender as TControl).ClientToScreen(Point(x, y));
    camDragged := false;
    exit;
  end;
  //if button <> mbLeft then exit;
  FClickRect.TopLeft := Point(x, y);
  FClickRect.BottomRight := FClickRect.TopLeft;
  case FMouseMoveState of
    msZoomWindow:
      begin
        FSelectRect.TopLeft := Point(x, y);
        FSelectRect.BottomRight := Point(x, y);
        DrawZoomWindow;

//        if ssAlt in Shift then
//          FMouseMoveState := msZoomOutWindowMove
//        else
          FMouseMoveState := msZoomWindowMove;
      end;
    msZoomOutWindow:
      begin
        FSelectRect.TopLeft := Point(x, y);
        FSelectRect.BottomRight := Point(x, y);
        DrawZoomWindow;

//        if ssAlt in Shift then
//          FMouseMoveState := msZoomWindowMove
//        else
          FMouseMoveState := msZoomOutWindowMove;
      end;
    msDrag:
      begin
        if not assigned(FViewImage) then exit;

//        FSelectRect.TopLeft := Point(x, y);
//        FSelectRect.BottomRight := Point(x, y);
        FMouseMoveState := msDragMove;
      end;
    msRotate:
      begin
        FClickAngle := arctan2(y-Image.Height/2, Image.Width/2-x);

        FRotateAngle := 0;
//        FSelectRect.Left := x;
        DrawRotateLines(FRotateAngle);
        FMouseMoveState := msRotateMove;
      end;
  end;
end;

///////////////////////////////////////////////////////////////////////////////
procedure TMainForm.ImageMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
const
  snap_angle = 15*pi/180;
var
  dx, dy, cx, cy, sgn: integer;
  sc, vx, vy, scale: double;
  q : Extended;
begin
{
  case FMouseMoveState of
    msRotate, msRotateMove:
      Image.Cursor := crEditRotate;
    msDrag, msDragMove:
      Image.Cursor := crEditMove;
    else
      Image.Cursor := crEditArrow;
  end;
}
  case FMouseMoveState of
    msZoomWindowMove,
    msZoomOutWindowMove:
      begin
        if DrawSelection then DrawZoomWindow;
        FClickRect.BottomRight := Point(x, y);
        dx := x - FClickRect.TopLeft.X;
        dy := y - FClickRect.TopLeft.Y;

        if ssShift in Shift then begin
          if (dy = 0) or (abs(dx/dy) >= Image.Width/Image.Height) then
            dy := Round(dx / Image.Width * Image.Height)
          else
            dx := Round(dy / Image.Height * Image.Width);
          FSelectRect.Left := FClickRect.TopLeft.X - dx;
          FSelectRect.Top := FClickRect.TopLeft.Y - dy;
          FSelectRect.Right := FClickRect.TopLeft.X + dx;
          FSelectRect.Bottom := FClickRect.TopLeft.Y + dy;
        end
        else if ssCtrl in Shift then begin
          FSelectRect.TopLeft := FClickRect.TopLeft;
          sgn := IfThen(dy*dx >=0, 1, -1);
          if (dy = 0) or (abs(dx/dy) >= Image.Width/Image.Height) then begin
            FSelectRect.Right := x;
            FSelectRect.Bottom := FClickRect.TopLeft.Y + sgn * Round(dx / Image.Width * Image.Height);
          end
          else begin
            FSelectRect.Right := FClickRect.TopLeft.X + sgn * Round(dy / Image.Height * Image.Width);
            FSelectRect.Bottom := y;
          end;
        end
        else begin
          sgn := IfThen(dy*dx >=0, 1, -1);
          if (dy = 0) or (abs(dx/dy) >= Image.Width/Image.Height) then begin
            cy := (y + FClickRect.TopLeft.Y) div 2;
            FSelectRect.Left := FClickRect.TopLeft.X;
            FSelectRect.Right := x;
            FSelectRect.Top := cy - sgn * Round(dx / 2 / Image.Width * Image.Height);
            FSelectRect.Bottom := cy + sgn * Round(dx / 2 / Image.Width * Image.Height);
          end
          else begin
            cx := (x + FClickRect.TopLeft.X) div 2;
            FSelectRect.Left := cx - sgn * Round(dy / 2 / Image.Height * Image.Width);
            FSelectRect.Right := cx + sgn * Round(dy / 2 / Image.Height * Image.Width);
            FSelectRect.Top := FClickRect.TopLeft.Y;
            FSelectRect.Bottom := y;
          end;
        end;
        DrawZoomWindow;
        DrawSelection := true;
      end;
    msDragMove:
      begin
        assert(assigned(FviewImage));
        assert(FViewScale <> 0);

        scale := FViewScale * Image.Width / FViewImage.Width;
        FViewPos.X := FViewPos.X + (x - FClickRect.Right) / scale;
        FViewPos.Y := FViewPos.Y + (y - FClickRect.Bottom) / scale;
        //FClickRect.BottomRight := Point(x, y);

		    DrawImageView;
      end;
    msPitchYaw:
      begin
          if camDragMode and ( (x <> camDragOld.x) or (y <> camDragOld.y) ) then
          begin
            Inc(camDragPos.x, x - camDragOld.x);
            Inc(camDragPos.y, y - camDragOld.y);

            vx := Round6(camDragValueX + camDragPos.x / 10);
            vy := Round6(camDragValueY - camDragPos.y / 10);

            MainCP.cameraPitch := vy * PI / 180.0;
            MainCP.cameraYaw := vx * PI / 180.0;

            vx := Round(vx);
            vy := Round(vy);

            camDragged := True;
            //StatusBar.Panels.Items[1].Text := Format('Pitch: %f°, Yaw: %f°', [vx,vy]);
          end;
      end;
    msRotateMove:
      begin
        if DrawSelection then DrawRotatelines(FRotateAngle);

        FRotateAngle := arctan2(y-Image.Height/2, Image.Width/2-x) - FClickAngle;
        if ssShift in Shift then // angle snap
          FRotateAngle := Round(FRotateAngle/snap_angle)*snap_angle;
        //SelectRect.Left := x;

//        pdjpointgen.Rotate(FRotateAngle);
//        FRotateAngle := 0;

        DrawRotatelines(FRotateAngle);
        DrawSelection := true;
{
        Image.Refresh;
if AdjustForm.Visible then begin
MainCp.FAngle:=-FRotateAngle;
AdjustForm.UpdateDisplay;
end;
}
      end;
  end;
  FClickRect.BottomRight := Point(x, y);
end;

function ScaleRect(r: TRect; scale: double): TSRect;
begin
  Result.Left := r.Left * scale;
  Result.Top := r.Top * scale;
  Result.Right := r.Right * scale;
  Result.Bottom := r.Bottom * scale;
end;

///////////////////////////////////////////////////////////////////////////////
procedure TMainForm.ImageMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  scale: double;
  rs: TSRect;
begin
  case FMouseMoveState of
    msZoomWindowMove:
      begin
        DrawZoomWindow;
        FMouseMoveState := msZoomWindow;
        if (abs(FSelectRect.Left - FSelectRect.Right) < 10) or
           (abs(FSelectRect.Top - FSelectRect.Bottom) < 10) then
          Exit; // zoom to much or double clicked

        StopThread;
        UpdateUndo;
        MainCp.ZoomtoRect(ScaleRect(FSelectRect, MainCP.Width / Image.Width));

        FViewScale := FViewScale * Image.Width / abs(FSelectRect.Right - FSelectRect.Left);
        FViewPos.x := FViewPos.x - ((FSelectRect.Right + FSelectRect.Left) - Image.Width)/2;
        FViewPos.y := FViewPos.y - ((FSelectRect.Bottom + FSelectRect.Top) - Image.Height)/2;
        DrawImageView;

        RedrawTimer.Enabled := True;
        UpdateWindows;
      end;
    msZoomOutWindowMove:
      begin
        DrawZoomWindow;
        FMouseMoveState := msZoomOutWindow;
        if (abs(FSelectRect.Left - FSelectRect.Right) < 10) or
           (abs(FSelectRect.Top - FSelectRect.Bottom) < 10) then
          Exit; // zoom to much or double clicked

        StopThread;
        UpdateUndo;
        MainCp.ZoomOuttoRect(ScaleRect(FSelectRect, MainCP.Width / Image.Width));

        scale := Image.Width / abs(FSelectRect.Right - FSelectRect.Left);
        FViewScale := FViewScale / scale;
        FViewPos.x := scale * (FViewPos.x + ((FSelectRect.Right + FSelectRect.Left) - Image.Width)/2);
        FViewPos.y := scale * (FViewPos.y + ((FSelectRect.Bottom + FSelectRect.Top) - Image.Height)/2);

        DrawImageView;

        RedrawTimer.Enabled := True;
        UpdateWindows;
      end;
    msDragMove:
      begin
        FClickRect.BottomRight := Point(x, y);
        FMouseMoveState := msDrag;

        if ((x = 0) and (y = 0)) or // double clicked
           ((FClickRect.left = FClickRect.right) and (FClickRect.top = FClickRect.bottom))
          then Exit;

        StopThread;
        UpdateUndo;
        MainCp.MoveRect(ScaleRect(FClickRect, MainCP.Width / Image.Width));

        RedrawTimer.Enabled := True;
        UpdateWindows;
      end;
    msRotateMove:
      begin
        DrawRotatelines(FRotateAngle);

        FMouseMoveState := msRotate;

        if (FRotateAngle = 0) then Exit; // double clicked

        StopThread;
        UpdateUndo;
        if MainForm_RotationMode = 0 then MainCp.Rotate(FRotateAngle)
        else MainCp.Rotate(-FRotateAngle);

        if assigned(FViewImage) then begin
          FViewImage.Free;
          FViewImage := nil;
          DrawImageView;
        end;

        RedrawTimer.Enabled := True;
        UpdateWindows;
      end;
    msPitchYaw:
      begin
        camDragMode := false;
        Screen.Cursor := crDefault;

        if camDragged then
        begin
          camDragged := False;
          RedrawTimer.Enabled := True;
          UpdateWindows;
        end;


      end;
  end;
end;

///////////////////////////////////////////////////////////////////////////////
procedure TMainForm.DrawImageView;
var
  i, j: integer;
  bm: TBitmap;
  r: TRect;
  scale: double;
const
  msg = #54; // 'NO PREVIEW';
var
  ok: boolean;
  GlobalMemoryInfo: TMemoryStatus; // holds the global memory status information
  area: int64;
  gridp: integer;
begin
  bm := TBitmap.Create;
  bm.Width := Image.Width;
  bm.Height := Image.Height;
  with bm.Canvas do begin
    if ShowTransparency then begin
      Brush.Color := $F0F0F0;
      FillRect(Rect(0, 0, bm.Width, bm.Height));
      Brush.Color := $C0C0C0;
      for i := 0 to ((bm.Width - 1) shr 3) do begin
        for j := 0 to ((bm.Height - 1) shr 3) do begin
          if odd(i + j) then
            FillRect(Rect(i shl 3, j shl 3, (i+1) shl 3, (j+1) shl 3));
        end;
      end;
    end
    else begin
      Brush.Color := MainCP.background[0] or (MainCP.background[1] shl 8) or (MainCP.background[2] shl 16);
      FillRect(Rect(0, 0, bm.Width, bm.Height));
    end;
  end;

  ok := false;
  if assigned(FViewImage) then begin
    scale := FViewScale * Image.Width / FViewImage.Width;

    r.Left := Image.Width div 2 + round(scale * (FViewPos.X - FViewImage.Width/2));
    r.Right := Image.Width div 2 + round(scale * (FViewPos.X + FViewImage.Width/2));
    r.Top := Image.Height div 2 + round(scale * (FViewPos.Y - FViewImage.Height/2));
    r.Bottom := Image.Height div 2 + round(scale * (FViewPos.Y + FViewImage.Height/2));

    GlobalMemoryInfo.dwLength := SizeOf(GlobalMemoryInfo);
    GlobalMemoryStatus(GlobalMemoryInfo);
    area := abs(r.Right - r.Left) * int64(abs(r.Bottom - r.Top));

    if (area * 4 < GlobalMemoryInfo.dwAvailPhys div 2) or
      (area <= Screen.Width*Screen.Height*4) then
    try
      FViewImage.Draw(bm.Canvas, r);
      ok := true;
    except
    end;

    // Gridlines for composition (taken from JK mod by Jed Kelsey)
    if (EnableGuides) then begin
    with bm.Canvas do begin
      Pen.Width := 1;
      Pen.Color := TColor(LineCenterColor); //$000000; // Center
      MoveTo(0, bm.Height shr 1); LineTo(bm.Width, bm.Height shr 1);
      MoveTo(bm.Width shr 1, 0); LineTo(bm.Width shr 1, bm.Height);
      Pen.Color := TColor(LineThirdsColor); //$C000C0; // Thirds
      gridp := Floor(bm.Height/3);
      MoveTo(0, gridp); LineTo(bm.Width, gridp);
      MoveTo(0, bm.Height-gridp); LineTo(bm.Width, bm.Height-gridp);
      gridp := Floor(bm.Width/3);
      MoveTo(gridp, 0); LineTo(gridp, bm.Height);
      MoveTo(bm.Width-gridp, 0); LineTo(bm.Width-gridp, bm.Height);
      Pen.Color := TColor(LineGRColor); //$0000F0; // "Golden Ratio" (per axis)
      gridp := Floor(bm.Height * 0.61803399);
      MoveTo(0, gridp); LineTo(bm.Width, gridp);
      MoveTo(0, bm.Height-gridp); LineTo(bm.Width, bm.Height-gridp);
      gridp := Floor(bm.Width * 0.61803399);
      MoveTo(gridp, 0); LineTo(gridp, bm.Height);
      MoveTo(bm.Width-gridp, 0); LineTo(bm.Width-gridp, bm.Height);
    end;
  end;
  end;

  if not ok then
    with bm.Canvas do
    begin
      Font.Name := 'Wingdings'; // 'Arial';
      Font.Height := bm.Height div 4;
      Font.Color := $808080;
      Brush.Style := bsClear;
      i := (bm.Width - TextWidth(msg)) div 2;
      j := (bm.Height - TextHeight(msg)) div 2;
      Font.Color := 0;
      TextOut(i+2,j+2, msg);
      Font.Color := clWhite; //$808080;
      TextOut(i,j, msg);
    end;
  Image.Picture.Graphic := bm;
  //EditForm.PaintBackground;
  Image.Refresh;
  bm.Free;
end;

///////////////////////////////////////////////////////////////////////////////
procedure TMainForm.DrawPitchYawLines(YawAngle: double; PitchAngle: double);
var
  bkuPen: TPen;
  points: array[0..3] of TPoint;
  i: integer;
begin
  bkuPen := TPen.Create;
  bkuPen.Assign(Image.Canvas.Pen);
  Image.Canvas.Pen.Mode    := pmXor;
  Image.Canvas.Pen.Color   := clWhite;
  Image.Canvas.Pen.Style   := psDot; //psDash;
  Image.Canvas.Brush.Style := bsClear;

//  Image.Canvas.Rectangle(FSelectRect);
  points[0].x := 0;
  points[0].y := round((Image.Height / 2) * sin(PitchAngle));
  points[1].x := Image.Width - 1;
  points[1].y := points[0].y;
  points[2].x := points[1].x;
  points[2].y := round((Image.Height) - ((Image.Height / 2) * sin(PitchAngle)));
  points[3].x := points[0].x;
  points[3].y := points[2].y;

  Image.Canvas.MoveTo(Points[3].x, Points[3].y);
  for i := 0 to 3 do begin
    Image.Canvas.LineTo(Points[i].x, Points[i].y);
  end;

  Image.Canvas.Pen.Assign(bkuPen);
  bkuPen.Free;
end;

procedure TMainForm.DrawRotateLines(Angle: double);
var
  bkuPen: TPen;
  points: array[0..3] of TPoint;
  i,x,y: integer;
begin
  bkuPen := TPen.Create;
  bkuPen.Assign(Image.Canvas.Pen);
  Image.Canvas.Pen.Mode    := pmXor;
  Image.Canvas.Pen.Color   := clWhite;
  Image.Canvas.Pen.Style   := psDot; //psDash;
  Image.Canvas.Brush.Style := bsClear;

//  Image.Canvas.Rectangle(FSelectRect);
  points[0].x := (Image.Width div 2)-1;
  points[0].y := (Image.Height div 2)-1;
  points[1].x := (Image.Width div 2)-1;
  points[1].y := -Image.Height div 2;
  points[2].x := -Image.Width div 2;
  points[2].y := -Image.Height div 2;
  points[3].x := -Image.Width div 2;
  points[3].y := (Image.Height div 2)-1;

  for i := 0 to 3 do begin
    x := points[i].x;
    y := points[i].y;

    points[i].x := round(cos(Angle) * x + sin(Angle) * y) + Image.Width div 2;
    points[i].y := round(-sin(Angle) * x + cos(Angle) * y) + Image.Height div 2;
  end;

  Image.Canvas.MoveTo(Points[3].x, Points[3].y);
  for i := 0 to 3 do begin
    Image.Canvas.LineTo(Points[i].x, Points[i].y);
  end;

  Image.Canvas.Pen.Assign(bkuPen);
  bkuPen.Free;
end;

///////////////////////////////////////////////////////////////////////////////
procedure TMainForm.DrawZoomWindow;
const
  cornerSize = 32;
var
  bkuPen: TPen;
  dx, dy, cx, cy: integer;
  l, r, t, b: integer;
begin
  bkuPen := TPen.Create;
  bkuPen.Assign(Image.Canvas.Pen);
  with Image.Canvas do begin
    Pen.Mode    := pmXor;
    Pen.Color   := clWhite;
    Brush.Style := bsClear;

    Pen.Style   := psDot; //psDash;

    if ssShift in FShiftState then
    begin
      dx := FClickRect.Right - FClickRect.Left;
      dy := FClickRect.Bottom - FClickRect.Top;
      Rectangle(FClickRect.Left - dx, FClickRect.Top - dy, FClickRect.Right, FClickRect.Bottom);
    end
    else Rectangle(FClickRect);

    dx := FSelectRect.Right - FSelectRect.Left;
    if dx >= 0 then begin
      l := FSelectRect.Left - 1;
      r := FSelectRect.Right;
    end
    else begin
      dx := -dx;
      l := FSelectRect.Right - 1;
      r := FSelectRect.Left;
    end;
    dx := min(dx div 2 - 1, cornerSize);

    dy := FSelectRect.Bottom - FSelectRect.Top;
    if dy >= 0 then begin
      t := FSelectRect.Top - 1;
      b := FSelectRect.Bottom;
    end
    else begin
      dy := -dy;
      t := FSelectRect.Bottom - 1;
      b := FSelectRect.Top;
    end;
    dy := min(dy div 2, cornerSize);

    pen.Style := psSolid;

    MoveTo(l + dx, t);
    LineTo(l, t);
    LineTo(l, t + dy);
    MoveTo(r - dx, t);
    LineTo(r, t);
    LineTo(r, t + dy);
    MoveTo(r - dx, b);
    LineTo(r, b);
    LineTo(r, b - dy);
    MoveTo(l + dx, b);
    LineTo(l, b);
    LineTo(l, b - dy);
{
    cx := (l + r) div 2;
    cy := (t + b) div 2;
    MoveTo(cx - dx div 2, cy);
    LineTo(cx + dx div 2 + 1, cy);
    MoveTo(cx, cy - dy div 2);
    LineTo(cx, cy + dy div 2 + 1);
}
    Pen.Assign(bkuPen);
  end;
  bkuPen.Free;
end;

///////////////////////////////////////////////////////////////////////////////
procedure TMainForm.tbzoomwindowClick(Sender: TObject);
begin
  FMouseMoveState := msZoomWindow;
end;

///////////////////////////////////////////////////////////////////////////////
procedure TMainForm.tbzoomoutwindowClick(Sender: TObject);
begin
  FMouseMoveState := msZoomOutWindow;
end;

///////////////////////////////////////////////////////////////////////////////
procedure TMainForm.tbDragClick(Sender: TObject);
begin
  FMouseMoveState := msDrag;
end;

///////////////////////////////////////////////////////////////////////////////
procedure TMainForm.tbRotateClick(Sender: TObject);
begin
  FMouseMoveState := msRotate;
end;

///////////////////////////////////////////////////////////////////////////////
procedure TMainForm.FillVariantMenu;
var
  i: integer;
  s: string;
  NewMenuItem : TMenuItem;
begin
  SetLength(VarMenus, NrVar);

  for i := 0 to NRVAR - 1 do begin
    NewMenuItem := TMenuItem.Create(self);
    s := varnames(i);
    NewMenuItem.Caption    := uppercase(s[1]) + copy(s, 2, length(s)-1);
    NewMenuItem.OnClick    := VariantMenuClick;
    NewMenuItem.Enabled    := True;
    NewMenuItem.Name       := 'var' + intTostr(i);
    NewMenuItem.Tag        := i;
    NewMenuItem.GroupIndex := 2;
    NewMenuItem.RadioItem  := True;
    VarMenus[i] := NewMenuItem;
    if i < NumBuiltinVars then
      mnuBuiltinVars.Add(NewMenuItem)
    else
      mnuPluginVars.Add(NewMenuItem);
  end;
end;

///////////////////////////////////////////////////////////////////////////////

procedure TMainForm.VariantMenuClick(Sender: TObject);
begin
  TMenuItem(Sender).Checked := True;
  UpdateUndo;
  Variation := TVariation(TMenuItem(Sender).Tag);
  SetVariation(maincp);
  ResetLocation;
  RedrawTimer.Enabled := True;
  UpdateWindows;
end;

//--Z--////////////////////////////////////////////////////////////////////////

procedure TMainForm.tbQualityBoxKeyPress(Sender: TObject; var Key: Char);
begin
  if key = #13 then
  begin
    tbQualityBoxSet(Sender);
    key := #0;
  end
  else if key = #27 then tbQualityBox.Text := FloatToStr(defSampleDensity);
end;

procedure TMainForm.tbQualityBoxSet(Sender: TObject);
var
  q: double;
begin
  try
    q := StrToFloat(tbQualityBox.Text);
  except
    exit;
  end;
  defSampleDensity := q;

  StopThread;
  RedrawTimer.Enabled := True;
  UpdateWindows;
end;

///////////////////////////////////////////////////////////////////////////////
procedure TMainForm.ImageDblClick(Sender: TObject);
begin
  if FMouseMoveState = msRotateMove then
  begin
//        FRotateAngle := 0;
    StopThread;
    UpdateUndo;
    MainCp.FAngle := 0;
    RedrawTimer.Enabled := True;
    UpdateWindows;
  end
  else mnuResetLocationClick(Sender);
end;

///////////////////////////////////////////////////////////////////////////////
procedure TMainForm.tbShowAlphaClick(Sender: TObject);
begin
  //tbShowAlpha.Down := not tbShowAlpha.Down;
  ShowTransparency := tbShowAlpha.Down;

  DrawImageView;
end;

///////////////////////////////////////////////////////////////////////////////
procedure TMainForm.tbShowTraceClick(Sender: TObject);
begin
  TraceForm.Show;
end;

///////////////////////////////////////////////////////////////////////////////
procedure TMainForm.FormKeyUpDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var
  MousePos: TPoint;
begin
  if Shift <> FShiftState then begin
    if FMouseMoveState in [msZoomWindowMove, msZoomOutWindowMove, msRotateMove, msDragMove] then
    begin
      // hack: to generate MouseMove event
      GetCursorPos(MousePos);
      SetCursorPos(MousePos.x, MousePos.y);
    end;

    if (FMouseMoveState in [msZoomWindowMove, msZoomOutWindowMove]) then
    begin
      DrawZoomWindow;
      FShiftState := Shift;
      DrawZoomWindow;
    end
    else FShiftState := Shift;
  end;
end;

procedure TMainForm.ListViewChanging(Sender: TObject; Item: TListItem;
  Change: TItemChange; var AllowChange: Boolean);
var sc, fc: string;
begin
  if (Item = nil) or (Sender <> ListView1) then exit;

  sc := ''; fc := '';
  if (ListView1.Selected <> nil) then sc := ListView1.Selected.Caption;
  if (ListView1.ItemFocused <> nil) then fc := ListView1.ItemFocused.Caption;

  if (Trim(Item.Caption) = Trim(maincp.name)) and (Item.Selected) and
     (Item.Selected) and (Change = ctState) then
  begin
    if (DoNotAskAboutChange = true) then
    begin
      exit;
    end;
    if (UndoIndex <> 0) then
    begin
      // hack
      if (LastCaptionSel = sc) and (LastCaptionFoc = fc) then begin
        AllowChange := LastDecision;
        if Not AllowChange then begin
          ListView1.OnChange := nil;
          ListView1.OnChanging := nil;
          ListView1.Selected := Item;
          ListView1.ItemFocused := Item;
          ListView1.OnChanging := ListViewChanging;
          ListView1.OnChange := ListViewChange;
        end;
        Exit;
      end;

      LastCaptionSel := sc;
      LastCaptionFoc := fc;

      if Application.MessageBox('Do you really want to open another flame? All changes made to the current flame will be lost.', 'Apophysis', MB_ICONWARNING or MB_YESNO) <> IDYES then
      begin
        AllowChange := false;
        ListView1.OnChange := nil;
        ListView1.OnChanging := nil;
        ListView1.Selected := Item;
        ListView1.ItemFocused := Item;
        ListView1.OnChanging := ListViewChanging;
        ListView1.OnChange := ListViewChange;
      end
      else
      begin
        AllowChange := true;
      end;

      LastDecision := AllowChange;
    end;
  end;
end;

procedure TMainForm.ListViewInfoTip(Sender: TObject; Item: TListItem;
  var InfoTip: String);
var
  Bitmap: TBitmap;
  lcp: TControlPoint;
begin
  // flame preview in a tooltip...
{
  BitMap := TBitMap.create;
  Bitmap.PixelFormat := pf24bit;
  BitMap.Width := 100;
  BitMap.Height := 100;

  lcp := TControlPoint.Create;
  lcp.Copy(mainCP);
  lcp.cmap := mainCP.cmap;

  if Assigned(Renderer) then begin
    Renderer.WaitFor;
    Renderer.Free;
  end;
  if not Assigned(Renderer) then
  begin
    lcp.sample_density := 1;
    lcp.spatial_oversample := 1;
    lcp.spatial_filter_radius := 0.3;
    lcp.AdjustScale(100, 100);
    lcp.Transparency := false;
  end;
  try
    Renderer := TRenderThread.Create;
    assert(Renderer <> nil);
    Renderer.BitsPerSample := 0
    Renderer.TargetHandle := self.Handle;
    Renderer.SetCP(lcp);
    Renderer.Priority := tpLower;
    Renderer.NrThreads := 1
    Renderer.Resume;
    Renderer.WaitFor;
  except
  end;


  lcp.Free;
  Bitmap.Free;
}
end;

procedure TMainForm.btnViewIconsClick(Sender: TObject);
begin
  ListView1.ViewStyle := vsIcon;
  btnViewList.Down := false;
  btnViewIcons.Down := true;
  ClassicListMode := false;

  if (OpenFile <> '') then
    ListXML(OpenFile, 1);
end;

procedure TMainForm.btnViewListClick(Sender: TObject);
begin
  ListView1.ViewStyle := vsReport;
  btnViewList.Down := true;
  btnViewIcons.Down := false;
  ClassicListMode := true;
end;

procedure TMainForm.ListView1Click(Sender: TObject);
begin
  //MissingStuff := '';
end;

procedure TMainForm.XmlScannerEndTag(Sender: TObject; TagName: String);
var sb : string;
begin
  if (TagName='flame') then begin
    EndParsing(ParseCP, sb);
    MainForm.StatusBar.Panels[0].Text := sb;
  end;
end;

procedure TMainForm.ToolButton19Click(Sender: TObject);
begin
  AdjustForm.UpdateDisplay;
  AdjustForm.PageControl.TabIndex:=4;
  AdjustForm.Show;
end;

procedure TMainForm.ToolButton7Click(Sender: TObject);
begin
  if (LoadForm.Showing = false) then LoadForm.Show;
end;

procedure TMainForm.ToolButton8Click(Sender: TObject);
var
  i:integer;
begin
  //EditForm.InvokeResetAll;
  if (AlwaysCreateBlankFlame) then EditForm.InvokeResetAll
  else TemplateForm.Show;
end;

procedure TMainForm.FormResize(Sender: TObject);
begin
  if (MainForm.Width <= TbBreakWidth) then
    Toolbar.Height := 26 * 2 + 8
  else Toolbar.Height := 26;
end;

function Split(const fText: String; const fSep: Char; fTrim: Boolean=false; fQuotes: Boolean=false):TStringList;
var vI: Integer;
    vBuffer: String;
    vOn: Boolean;
begin
  Result:=TStringList.Create;
  vBuffer:='';
  vOn:=true;
  for vI:=1 to Length(fText) do
  begin
    if (fQuotes and(fText[vI]=fSep)and vOn)or(Not(fQuotes) and (fText[vI]=fSep)) then
    begin
      if fTrim then vBuffer:=Trim(vBuffer);
      if vBuffer='' then vBuffer:=fSep; // !!! e.g. split(',**',',')...
      if vBuffer[1]=fSep then
        vBuffer:=Copy(vBuffer,2,Length(vBuffer));
      Result.Add(vBuffer);
      vBuffer:='';
    end;
    if fQuotes then
    begin
      if fText[vI]='"' then
      begin
        vOn:=Not(vOn);
        Continue;
      end;
      if (fText[vI]<>fSep)or((fText[vI]=fSep)and(vOn=false)) then
        vBuffer:=vBuffer+fText[vI];
    end else
      if fText[vI]<>fSep then
        vBuffer:=vBuffer+fText[vI];
  end;
  if vBuffer<>'' then
  begin
    if fTrim then vBuffer:=Trim(vBuffer);
    Result.Add(vBuffer);
  end;
end;

procedure TMainForm.mnuResetUIClick(Sender: TObject);
begin
  ListBackPanel.Width := ThumbnailSize + 90;
  Splitter.Left := ListBackPanel.Width;
end;

procedure TMainForm.AutoSaveTimerTimer(Sender: TObject);
var
  filename,title : string;
  Tag: string;
  IFile: TextFile;
  FileList, FileListPre: TStringList;
  i, p: integer;
  erase : boolean;
  bakname: string;
begin
  erase := false;
  filename := AutoSavePath;
  title := CleanXMLName(maincp.name) + ' (' + FormatDateTime('MM-dd-yyyy hh:mm:ss', Now) + ')';
  Tag := RemoveExt(filename);

  if FileExists(filename) then begin
    FileListPre := TStringList.create;
      try
        FileListPre.LoadFromFile(filename);
        if (FileListPre.Count > 1000) then erase := true;
      finally
        FileListPre.Free;
      end;

      if (erase) then begin
        bakname := ChangeFileExt(filename, '.bak');
        if FileExists(bakname) then DeleteFile(bakname);
        RenameFile(filename, bakname);
      end;
  end;

  try
    if FileExists(filename) then
    begin
      bakname := ChangeFileExt(filename, '.temp');
      if FileExists(bakname) then DeleteFile(bakname);
      RenameFile(filename, bakname);

      FileList := TStringList.create;
      try
        FileList.LoadFromFile(bakname);

        if Pos('<flame name="' + title + '"', FileList.Text) <> 0 then
        begin
          i := 0;
          while Pos('<flame name="' + title + '"', Trim(FileList[i])) = 0 do
            inc(i);

          p := 0;
          while p = 0 do
          begin
            p := Pos('</flame>', FileList[i]);
            FileList.Delete(i);
          end;
        end;

//      FileList := TStringList.create;
//      try
//        FileList.LoadFromFile(filename);

        // fix first line
        if (FileList.Count > 0) then begin
          FileList[0] := '<flames name="' + Tag + '">';
        end;

        if FileList.Count > 2 then
        begin
          if pos('<flame ', FileList.text) <> 0 then
            repeat
              FileList.Delete(FileList.Count - 1);
            until (Pos('</flame>', FileList[FileList.count - 1]) <> 0)
          else
            repeat
              FileList.Delete(FileList.Count - 1);
            until (Pos('<' + Tag + '>', FileList[FileList.count - 1]) <> 0) or
                  (Pos('</flames>', FileList[FileList.count - 1]) <> 0);
        end else
        begin
          FileList.Delete(FileList.Count - 1);
        end;

        FileList.Add(Trim(FlameToXMLAS(maincp, title, false)));
        FileList.Add('</flames>');
        FileList.SaveToFile(filename);

      finally
        if FileExists(bakname) and not FileExists(filename) then
          RenameFile(bakname, filename);

        FileList.Free;
        if FileExists(bakname) then DeleteFile(bakname);
      end;
    end
    else
    begin
    // New file ... easy
      AssignFile(IFile, filename);
      ReWrite(IFile);
      Writeln(IFile, '<flames name="' + Tag + '">');
      Write(IFile, FlameToXMLAS(maincp, title, false));
      Writeln(IFile, '</flames>');
      CloseFile(IFile);
    end;
  except on E: EInOutError do
    begin
      //Application.MessageBox('Cannot save file', 'Apophysis', 16);
    end;
  end;
end;

procedure TMainForm.Restorelastautosave1Click(Sender: TObject);
var fn:string;
begin
  if (not fileexists(AutoSavePath)) then begin
    Application.MessageBox(PChar(TextByKey('main-status-noautosave')), PChar('Apophysis'), MB_ICONERROR);
    exit;
  end;

{$ifdef DisableScripting}
{$else}
  ScriptEditor.Stopped := True;
{$endif}
    fn := AutoSavePath;
    MainForm.CurrentFileName := fn;
    LastOpenFile := fn;
    Maincp.name := '';
    ParamFolder := ExtractFilePath(fn);
    ListView.ReadOnly := False;
    mnuListRename.Enabled := True;
    mnuItemDelete.Enabled := True;
    OpenFile := fn;
    //MainForm.Caption := AppVersionString + ' - ' + OpenFile; // --Z--
    if APP_BUILD = '' then MainForm.Caption := AppVersionString + ' - ' + openFile
    else MainForm.Caption := AppVersionString + ' ' + APP_BUILD + ' - ' + openFile;
    OpenFileType := ftXML;
    ListXML(fn, 1)
end;

procedure TMainForm.mnuHelpTopicsClick(Sender: TObject);
var
  URL, HelpTopic: string;
begin
  {if EditForm.Active then HelpTopic := 'Transform editor.htm'
//  else if GradientForm.Active then HelpTopic := 'Gradient window.htm'
  else if AdjustForm.Active then HelpTopic := 'Adjust window.htm'
  else if MutateForm.Active then HelpTopic := 'Mutation window.htm'
  else if RenderForm.Active then HelpTopic := 'Render window.htm';
  HtmlHelp(0, nil, HH_CLOSE_ALL, 0);
  URL := AppPath + 'Apophysis 2.0.chm';
  if HelpTopic <> '' then URL := URL + '::\' + HelpTopic;
  HtmlHelp(0, PChar(URL), HH_DISPLAY_TOC, 0);  }
  //if (FileExists(HelpPath)) then
  if (HelpPath <> '') then begin
    if (not WinShellExecute('open', HelpPath)) then begin
      MessageBox(self.Handle, PCHAR(Format(TextByKey('common-genericopenfailure'), [HelpPath])), PCHAR('Apophysis'), MB_ICONHAND);
    end;
  end else MessageBox(self.Handle, PCHAR(TextByKey('main-status-nohelpfile')), PCHAR('Apophysis'), MB_ICONHAND);
  //else MessageBox(self.Handle, PCHAR('Could not find "' + HelpPath + '"'), PCHAR('Error'), MB_ICONHAND);
end;

function TMainForm.RetrieveXML(cp : TControlPoint):string;
begin
    Result := FlameToXML(cp, false, false);
end;

procedure TMainForm.tbGuidesClick(Sender: TObject);
begin
//  tbGuides.Down := not tbGuides.Down;
  EnableGuides := tbGuides.Down;
  DrawImageView;
end;

function WinExecAndWait32(FileName: string): integer;
var
  zAppName: array[0..1024] of Char;
  zCurDir: array[0..255] of Char;
  WorkDir: string;
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
  r : dword;
begin
  StrPCopy(zAppName, FileName);
  GetDir(0, WorkDir);
  StrPCopy(zCurDir, WorkDir);
  FillChar(StartupInfo, Sizeof(StartupInfo), #0);
  StartupInfo.cb := Sizeof(StartupInfo);

  StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
  StartupInfo.wShowWindow := 0;
  if (not CreateProcess(nil, zAppName, nil, nil, false, CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo)) then
    Result := -1
  else begin
    WaitforSingleObject(ProcessInfo.hProcess, INFINITE);
    GetExitCodeProcess(ProcessInfo.hProcess, r);
    result := r;
    CloseHandle(ProcessInfo.hProcess);
    CloseHandle(ProcessInfo.hThread);
  end;
end;

procedure TMainForm.mnuTurnFlameToScriptClick(Sender: TObject);
var
  txt: string;
begin
  txt := Trim(FlameToXML(Maincp, false, false));
  {$ifdef DisableScripting}
{$else}
  ScriptEditor.ScriptFromFlame(txt);
  ScriptEditor.Show;
{$endif}
end;

constructor TThumbnailThread.Create(SourceFile : string; FlameNames : TstringList);
var
  i : integer;
  ListItem : TListItem;
begin
  ThumbnailSize := MainForm.UsedThumbnails.Width;
  Flames := FlameNames;
  FileName := SourceFile;

  MainForm.UsedThumbnails.Clear;
  MainForm.UsedThumbnails.Add(ThumbnailPlaceholder, nil);

  MainForm.ListView1.Items.BeginUpdate;
  MainForm.ListView1.Items.Clear;

  for i := 0 to FlameNames.Count - 1 do begin
    ListItem := MainForm.ListView1.Items.Add;
    ListItem.Caption := FlameNames[i];
    ListItem.ImageIndex := 0;
  end;

  MainForm.ListView1.Items.EndUpdate;
  initialized := true;
  
  inherited create(True);
end;

destructor TThumbnailThread.Destroy;
begin
  if (Initialized) then begin
    ThumbnailSize := 0;
    FileName := '';
    if (Flames <> nil) then begin
      Flames.Free;
      Flames := nil;
    end;
    Initialized := false;
    inherited destroy;
  end;
end;

procedure TThumbnailThread.Execute;
var
  Renderer : TRenderer;
  cp : TControlPoint;
  Thumbnail : TBitmap;

  flameXML : string;
  w, h, r : double;
  i : integer;

  stored_thumb : TJPegImage;
  stored_thumb_data : TBinArray;
  stored_thumb_size : integer;
  memstream : TMemoryStream;
begin
  Inherited;

  Renderer := TRenderer.Create;
  cp := TControlPoint.Create;

  //MainForm.ListView1.Items.BeginUpdate;
  for i := 0 to Flames.Count - 1 do begin
    cp.Clear;
    flameXML := LoadXMLFlameText(filename, Flames[i]);
    MainForm.ParseXML(cp, PCHAR(flameXML), true);

    if (cp.xdata <> '') then begin
      stored_thumb := TJPegImage.Create;
      B64Decode(cp.xdata, stored_thumb_data, stored_thumb_size);
      memstream := TMemoryStream.Create;
      memstream.Size := stored_thumb_size;
      stored_thumb_size := Length(stored_thumb_data);
      memstream.WriteBuffer(stored_thumb_data[0], stored_thumb_size);
      memstream.Seek(0, soBeginning);
      //-X- test thumbnail integrity...memstream.SaveToFile('C:\Test.jpg');
      stored_thumb.LoadFromStream(memstream);
      memstream.Free;

      w := stored_thumb.Width; h := stored_thumb.Height;

      Thumbnail := TBitmap.Create;
      Thumbnail.PixelFormat := pf24bit;
      Thumbnail.HandleType := bmDIB;
      Thumbnail.Width := ThumbnailSize;
      Thumbnail.Height := ThumbnailSize;
      Thumbnail.Canvas.Brush.Color := GetSysColor(5);
      Thumbnail.Canvas.FillRect(Rect(0, 0, ThumbnailSize, ThumbnailSize));
      Thumbnail.Canvas.Draw(round(ThumbnailSize / 2 - w / 2), round(ThumbnailSize / 2 - h / 2), stored_thumb);

      MainForm.UsedThumbnails.Add(Thumbnail, nil);
      MainForm.ListView1.Items[i].ImageIndex := MainForm.UsedThumbnails.Count - 1;

      Thumbnail.Free;
      Thumbnail := nil;

      MainForm.ListView1.Refresh;

      stored_thumb.Free;
    end else begin
      w := cp.Width; h := cp.Height; r := w / h;
      if (w < h) then begin
        w := r * ThumbnailSize;
        h := ThumbnailSize;
      end else if (w > h) then begin
        h := ThumbnailSize / r;
        w := ThumbnailSize;
      end else begin
        w := ThumbnailSize;
        h := ThumbnailSize;
      end;
      cp.AdjustScale(round(w), round(h));
      cp.Width := round(w);
      cp.Height := round(h);
      cp.spatial_oversample := defOversample;
      cp.spatial_filter_radius := defFilterRadius;
      cp.sample_density := 3;

      Thumbnail := nil;
      Renderer.SetCP(cp);
      Renderer.Render;

      Thumbnail := TBitmap.Create;
      Thumbnail.PixelFormat := pf24bit;
      Thumbnail.HandleType := bmDIB;
      Thumbnail.Width := ThumbnailSize;
      Thumbnail.Height := ThumbnailSize;
      Thumbnail.Canvas.Brush.Color := GetSysColor(5);
      Thumbnail.Canvas.FillRect(Rect(0, 0, ThumbnailSize, ThumbnailSize));
      Thumbnail.Canvas.Draw(round(ThumbnailSize / 2 - w / 2), round(ThumbnailSize / 2 - h / 2), renderer.GetImage);

      MainForm.UsedThumbnails.Add(Thumbnail, nil);
      MainForm.ListView1.Items[i].ImageIndex := MainForm.UsedThumbnails.Count - 1;

      Thumbnail.Free;
      Thumbnail := nil;
      MainForm.ListView1.Refresh;
    end;
  end;
  //MainForm.ListView1.Items.EndUpdate;

  cp.Free;
  Renderer.Free;
  ThumbnailSize := 0;
  FileName := '';
  if (Flames <> nil) then begin
    Flames.Free;
    Flames := nil;
  end;
end;

procedure ListXMLSimple(FileName: string; sel: integer);
var
  FStrings : TStringList;
  i, p, n  : integer;
  title    : string;
  item     : TListItem;
begin

  FStrings := TStringList.Create;
  FStrings.LoadFromFile(FileName);
  //MainForm.pnlLSPFrame.Visible := true;
  MainForm.ListView1.Items.BeginUpdate;
  MainForm.ListView1.Items.Clear;

  try
    if (Pos('<flame ', Lowercase(FStrings.Text)) <> 0) then
    begin
      for i := 0 to FStrings.Count - 1 do
      begin
        p := Pos('<flame ', LowerCase(FStrings[i]));
        if (p <> 0) then
        begin
          MainForm.ListXMLScanner.LoadFromBuffer(TCharType(TStringType(FSTrings[i])));
          MainForm.ListXMLScanner.Execute;

          if Trim(pname) = '' then
            Title := '*untitled ' + ptime
          else
            Title := Trim(pname);
          if Title <> '' then
          begin
            if ((i mod 5) = 0) then MainForm.LoadSaveProgress.Position := round(100*i/FStrings.Count);
            item := MainForm.ListView1.Items.Add; item.Caption := Title; item.ImageIndex := -1;
            (*Inc(n); if (n > BatchSize) and not brk then begin
              if (ID_NO = Application.MessageBox(PAnsiChar('WARNING! The currently loading batch contains more than ' +
                inttostr(BatchSize) + ' flames. Do you want to continue loading it?'), PAnsiChar('Apophysis'),
                MB_ICONQUESTION or MB_YESNO)) then break else brk := true;
            end; *)
          end;
        end;
      end;
    end;
  finally
    FStrings.Free;
  end;

  //MainForm.pnlLSPFrame.Visible := false;
  MainForm.LoadSaveProgress.Position := 0;
  MainForm.ListView1.Items.EndUpdate;

  case sel of
    0: MainForm.ListView1.Selected := MainForm.ListView1.Items[MainForm.ListView1.Items.Count - 1];
    1: MainForm.ListView1.Selected := MainForm.ListView1.Items[0];
    2: // do nothing
  end;

end;

procedure ListXMLThumbnails(FileName: string; sel: integer);
var
  FStrings : TStringList;
  FFlames  : TStringList;
  i, p, n  : integer;
  title    : string;
  thread   : TThumbnailThread;
  brk      : boolean;
begin

  FStrings := TStringList.Create;
  FFlames  := TStringList.Create;

  FStrings.LoadFromFile(FileName);

  for i := 0 to MainForm.ListView1.Items.Count - 1 do begin
    MainForm.ListView1.Items[i].ImageIndex := -1;
  end;

  //MainForm.pnlLSPFrame.Visible := true;
  try
    if (Pos('<flame ', Lowercase(FStrings.Text)) <> 0) then
    begin
      for i := 0 to FStrings.Count - 1 do
      begin
        p := Pos('<flame ', LowerCase(FStrings[i]));
        if (p <> 0) then
        begin
          MainForm.ListXMLScanner.LoadFromBuffer(TCharType(TStringType(FSTrings[i])));
          MainForm.ListXMLScanner.Execute;

          if Trim(pname) = '' then
            Title := '*untitled ' + ptime
          else
            Title := Trim(pname);
          if Title <> '' then
          begin
            if ((i mod 5) = 0) then MainForm.LoadSaveProgress.Position := round(100*i/FStrings.Count);
            FFlames.Add(Title);
            (*Inc(n); if (n > BatchSize) and not brk then begin
              if (ID_NO = Application.MessageBox(PAnsiChar('WARNING! The currently loading batch contains more than ' +
                inttostr(BatchSize) + ' flames. Do you want to continue loading it?'), PAnsiChar('Apophysis'),
                MB_ICONQUESTION or MB_YESNO)) then break else brk := true;
            end; *)
          end;
        end;
      end;
    end;
  finally
    FStrings.Free;
  end;
  //MainForm.pnlLSPFrame.Visible := false;
  MainForm.LoadSaveProgress.Position := 0;

  thread := TThumbnailThread.Create(FileName, FFlames);
  case sel of
    0: MainForm.ListView1.Selected := MainForm.ListView1.Items[MainForm.ListView1.Items.Count - 1];
    1: MainForm.ListView1.Selected := MainForm.ListView1.Items[0];
    2: // do nothing
  end;

  thread.Resume;
end;

procedure ListXML(FileName: string; sel: integer);
begin
  MainForm.ParseLoadingBatch := true;
  if (ClassicListMode) or (NXFORMS < 100) then ListXmlSimple(FileName, sel)
  else ListXmlThumbnails(FileName, sel);
  MainForm.ParseLoadingBatch := false;
end;

procedure TMainForm.mnuReportFlameClick(Sender: TObject);
var str:string; i : integer;
begin
  if (not LoadForm.Visible) then LoadForm.Show;
  str := MainCP.name + #13#10 +
  '===============================================' + #13#10 +
  Format(TextByKey('main-report-transformcount'), [MainCp.NumXForms]) + #13#10 +
  Format(TextByKey('main-report-finaltransform'), [IfThen(maincp.finalXformEnabled, TextByKey('common-yes'), TextByKey('common-no'))]) + #13#10 +
  TextByKey('main-report-usedplugins');
  MainCP.FillUsedPlugins;
  if MainCp.used_plugins.Count = 0 then begin
    LoadForm.Output.Text := LoadForm.Output.Text +  #13#10 + str + ' ' + TextByKey('main-report-noplugins') +  #13#10;
    exit;
  end;
  for i := 0 to MainCP.used_plugins.Count-1 do begin
    str := str + #13#10 + '  - ' + MainCP.used_plugins[i];
  end;
  LoadForm.Output.Text := LoadForm.Output.Text + #13#10 + str + #13#10;
end;

procedure TMainForm.mnuManualClick(Sender: TObject);
begin
  WinShellOpen('http://dl.dropbox.com/u/20949676/ApophysisUserManual/index.html');
end;

procedure TMainForm.CreateSubstMap;
begin
  SubstSource.Add('cross2'); SubstTarget.Add('cross');
  SubstSource.Add('Epispiral'); SubstTarget.Add('epispiral');
  SubstSource.Add('Epispiral_n'); SubstTarget.Add('epispiral_n');
  SubstSource.Add('Epispiral_thickness'); SubstTarget.Add('epispiral_thickness');
  SubstSource.Add('Epispiral_holes'); SubstTarget.Add('epispiral_holes');
  SubstSource.Add('bwraps2'); SubstTarget.Add('bwraps');
  SubstSource.Add('bwraps2_cellsize'); SubstTarget.Add('bwraps_cellsize');
  SubstSource.Add('bwraps2_space'); SubstTarget.Add('bwraps_space');
  SubstSource.Add('bwraps2_gain'); SubstTarget.Add('bwraps_gain');
  SubstSource.Add('bwraps2_inner_twist'); SubstTarget.Add('bwraps_inner_twist');
  SubstSource.Add('bwraps2_outer_twist'); SubstTarget.Add('bwraps_outer_twist');
  SubstSource.Add('pre_bwraps2'); SubstTarget.Add('pre_bwraps');
  SubstSource.Add('pre_bwraps2_cellsize'); SubstTarget.Add('pre_bwraps_cellsize');
  SubstSource.Add('pre_bwraps2_space'); SubstTarget.Add('pre_bwraps_space');
  SubstSource.Add('pre_bwraps2_gain'); SubstTarget.Add('pre_bwraps_gain');
  SubstSource.Add('pre_bwraps2_inner_twist'); SubstTarget.Add('pre_bwraps_inner_twist');
  SubstSource.Add('pre_bwraps2_outer_twist'); SubstTarget.Add('pre_bwraps_outer_twist');
  SubstSource.Add('post_bwraps2'); SubstTarget.Add('post_bwraps');
  SubstSource.Add('post_bwraps2_cellsize'); SubstTarget.Add('post_bwraps_cellsize');
  SubstSource.Add('post_bwraps2_space'); SubstTarget.Add('post_bwraps_space');
  SubstSource.Add('post_bwraps2_gain'); SubstTarget.Add('post_bwraps_gain');
  SubstSource.Add('post_bwraps2_inner_twist'); SubstTarget.Add('post_bwraps_inner_twist');
  SubstSource.Add('post_bwraps2_outer_twist'); SubstTarget.Add('post_bwraps_outer_twist');
  SubstSource.Add('bwraps7'); SubstTarget.Add('bwraps');
  SubstSource.Add('bwraps7_cellsize'); SubstTarget.Add('bwraps_cellsize');
  SubstSource.Add('bwraps7_space'); SubstTarget.Add('bwraps_space');
  SubstSource.Add('bwraps7_gain'); SubstTarget.Add('bwraps_gain');
  SubstSource.Add('bwraps7_inner_twist'); SubstTarget.Add('bwraps_inner_twist');
  SubstSource.Add('bwraps7_outer_twist'); SubstTarget.Add('bwraps_outer_twist');
  SubstSource.Add('logn'); SubstTarget.Add('log');
  SubstSource.Add('logn_base'); SubstTarget.Add('log_base');
end;
function TMainForm.ReadWithSubst(Attributes: TAttrList; attrname: string): string;
var i: integer; v: TStringType;
begin
  v := Attributes.Value(TStringType(attrname));
  if (v <> '') then begin
    Result := String(v);
    Exit;
  end;

  for i := 0 to SubstTarget.Count - 1 do begin
    if (SubstTarget[i] = attrname) then begin
      v := Attributes.Value(TStringType(SubstSource[i]));
      if (v <> '') then begin
        Result := String(v);
        Exit;
      end;
    end;
  end;

  Result := '';
end;

end.
