<commit_before>unit AnimInterpForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
  KM_ResPalettes, KM_Defaults, KM_CommonTypes, KM_Points, KM_ResSprites, KM_ResSpritesEdit, KM_Pics, KM_ResUnits;

type
  TAnimCacheItem = record
    PicOffset: Integer;
    A: TKMAnimLoop;
  end;

  TForm1 = class(TForm)
    btnProcessUnits: TButton;
    Memo1: TMemo;
    memoErrors: TMemo;
    Label1: TLabel;
    procedure btnProcessUnitsClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    fPalettes: TKMResPalettes;
    fResUnits: TKMResUnits;
    fSprites: array[TRXType] of TKMSpritePackEdit;

    fAnimCache: array of TAnimCacheItem;

    fTempDir: string;
    fDainFolder: string;

    function GetMinCanvasSize(A: TKMAnimLoop; RT: TRXType): Integer;
    function GetDainParams(aDir: string; aAlpha: Boolean): string;
    procedure MakeImagesInterpUnit(aUT: TKMUnitType; aAction: TKMUnitActionType; aDir: TKMDirection; aBaseDir: string; aExportType: TInterpExportType);
    function DoInterpUnit(aUT: TKMUnitType; aAction: TKMUnitActionType; aDir: TKMDirection; var aPicOffset: Integer): Integer;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation
uses
  ShellApi, Math, RTTI, KM_FileIO, KromUtils,
  KM_ResHouses, KM_Log, KM_PNG;

const
  CANVAS_Y_OFFSET = 14;

{$R *.dfm}


procedure TForm1.FormCreate(Sender: TObject);
begin
  ExeDir := ExpandFileName(ExtractFilePath(ParamStr(0)) + '..\..\');

  Caption := 'Animation Interpolator (' + GAME_REVISION + ')';

  fPalettes := TKMResPalettes.Create;
  fPalettes.LoadPalettes(ExeDir + 'data\gfx\');

  fResUnits := TKMResUnits.Create;

  fTempDir := ExeDir + 'SpriteInterp\';
  fDainFolder := 'C:\Dev\kam_sprites\DAIN_APP Alpha 1.0\';
end;

function TForm1.GetDainParams(aDir: string; aAlpha: Boolean): string;
var
  DainFolder, DainExe: string;
begin
  DainExe := fDainFolder+'DAINAPP.exe';
  Result := 'cmd.exe /C "'+DainExe+'" --cli 1 -o '+aDir+' -p 0 -l 1 -in 8 -da 0 -se 0 -si 1 -sr 0 -ha 0 --fast_mode 0';
  if aAlpha then
    Result := Result + ' -a 1';

  //Result := 'cmd.exe /C python inference_video.py --exp=3 --scale=4.0 --png --img='+aDir+'original_frames --output='+aDir+'interpolated_frames & pause';

  //Useful for checking error messages
  //Result := Result + ' & pause';
end;


function TForm1.GetMinCanvasSize(A: TKMAnimLoop; RT: TRXType): Integer;
var
  Step, SpriteID, MaxSoFar, X, Y, W, H: Integer;

begin
  MaxSoFar := 32;
  for Step := 1 to A.Count do
  begin
    SpriteID := A.Step[Step]+1; //Sprites in units.dat are 0 indexed
    if SpriteID > 0 then
    begin
      W := fSprites[RT].RXData.Size[SpriteID].X;
      H := fSprites[RT].RXData.Size[SpriteID].Y;
      X := fSprites[RT].RXData.Pivot[SpriteID].x;
      Y := fSprites[RT].RXData.Pivot[SpriteID].y + CANVAS_Y_OFFSET;
      MaxSoFar := Max(MaxSoFar, -X);
      MaxSoFar := Max(MaxSoFar, X + W);
      MaxSoFar := Max(MaxSoFar, -Y);
      MaxSoFar := Max(MaxSoFar, Y + H);
    end;
  end;
  //Keep 1px padding on all sides
  MaxSoFar := MaxSoFar + 2;
  Result := ((MaxSoFar div 32) + 1)*32;
end;


procedure TForm1.MakeImagesInterpUnit(aUT: TKMUnitType; aAction: TKMUnitActionType; aDir: TKMDirection; aBaseDir: string; aExportType: TInterpExportType);
var
  RT: TRXType;
  origSpritesDir, interpSpritesDir: string;
  A: TKMAnimLoop;
  Step, SpriteID, CanvasSize: Integer;
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
  NeedAlpha, AllBlank, Worked: Boolean;
begin
  RT := rxUnits;
  if fSprites[RT] = nil then
  begin
    fSprites[RT] := TKMSpritePackEdit.Create(RT, fPalettes);
    fSprites[RT].LoadFromRXXFile(ExeDir + 'data\Sprites\' + RXInfo[RT].FileName + '_a.rxx');
  end;

  origSpritesDir := aBaseDir + 'original_frames\';
  interpSpritesDir := aBaseDir + 'interpolated_frames\';
  ForceDirectories(origSpritesDir);
<commit_msg>Fix canvas size calculation<commit_after>unit AnimInterpForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
  KM_ResPalettes, KM_Defaults, KM_CommonTypes, KM_Points, KM_ResSprites, KM_ResSpritesEdit, KM_Pics, KM_ResUnits;

type
  TAnimCacheItem = record
    PicOffset: Integer;
    A: TKMAnimLoop;
  end;

  TForm1 = class(TForm)
    btnProcessUnits: TButton;
    Memo1: TMemo;
    memoErrors: TMemo;
    Label1: TLabel;
    procedure btnProcessUnitsClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    fPalettes: TKMResPalettes;
    fResUnits: TKMResUnits;
    fSprites: array[TRXType] of TKMSpritePackEdit;

    fAnimCache: array of TAnimCacheItem;

    fTempDir: string;
    fDainFolder: string;

    function GetMinCanvasSize(A: TKMAnimLoop; RT: TRXType): Integer;
    function GetDainParams(aDir: string; aAlpha: Boolean): string;
    procedure MakeImagesInterpUnit(aUT: TKMUnitType; aAction: TKMUnitActionType; aDir: TKMDirection; aBaseDir: string; aExportType: TInterpExportType);
    function DoInterpUnit(aUT: TKMUnitType; aAction: TKMUnitActionType; aDir: TKMDirection; var aPicOffset: Integer): Integer;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation
uses
  ShellApi, Math, RTTI, KM_FileIO, KromUtils,
  KM_ResHouses, KM_Log, KM_PNG;

const
  CANVAS_Y_OFFSET = 14;

{$R *.dfm}


procedure TForm1.FormCreate(Sender: TObject);
begin
  ExeDir := ExpandFileName(ExtractFilePath(ParamStr(0)) + '..\..\');

  Caption := 'Animation Interpolator (' + GAME_REVISION + ')';

  fPalettes := TKMResPalettes.Create;
  fPalettes.LoadPalettes(ExeDir + 'data\gfx\');

  fResUnits := TKMResUnits.Create;

  fTempDir := ExeDir + 'SpriteInterp\';
  fDainFolder := 'C:\Dev\kam_sprites\DAIN_APP Alpha 1.0\';
end;

function TForm1.GetDainParams(aDir: string; aAlpha: Boolean): string;
var
  DainFolder, DainExe: string;
begin
  DainExe := fDainFolder+'DAINAPP.exe';
  Result := 'cmd.exe /C "'+DainExe+'" --cli 1 -o '+aDir+' -p 0 -l 1 -in 8 -da 0 -se 0 -si 1 -sr 0 -ha 0 --fast_mode 0';
  if aAlpha then
    Result := Result + ' -a 1';

  //Result := 'cmd.exe /C python inference_video.py --exp=3 --scale=4.0 --png --img='+aDir+'original_frames --output='+aDir+'interpolated_frames & pause';

  //Useful for checking error messages
  //Result := Result + ' & pause';
end;


function TForm1.GetMinCanvasSize(A: TKMAnimLoop; RT: TRXType): Integer;
var
  Step, SpriteID, MaxSoFar, X, Y, W, H: Integer;
begin
  MaxSoFar := 32;
  for Step := 1 to A.Count do
  begin
    SpriteID := A.Step[Step]+1; //Sprites in units.dat are 0 indexed
    if SpriteID > 0 then
    begin
      W := fSprites[RT].RXData.Size[SpriteID].X;
      H := fSprites[RT].RXData.Size[SpriteID].Y;
      X := fSprites[RT].RXData.Pivot[SpriteID].x;
      Y := fSprites[RT].RXData.Pivot[SpriteID].y + CANVAS_Y_OFFSET;
      MaxSoFar := Max(MaxSoFar, -X);
      MaxSoFar := Max(MaxSoFar, X + W);
      MaxSoFar := Max(MaxSoFar, -Y);
      MaxSoFar := Max(MaxSoFar, Y + H);
    end;
  end;
  //Sprite is centred so we need this much padding on both sides
  MaxSoFar := 2*MaxSoFar;
  //Keep 1px padding on all sides
  MaxSoFar := MaxSoFar + 2;
  Result := ((MaxSoFar div 32) + 1)*32;
end;


procedure TForm1.MakeImagesInterpUnit(aUT: TKMUnitType; aAction: TKMUnitActionType; aDir: TKMDirection; aBaseDir: string; aExportType: TInterpExportType);
var
  RT: TRXType;
  origSpritesDir, interpSpritesDir: string;
  A: TKMAnimLoop;
  Step, SpriteID, CanvasSize: Integer;
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
  NeedAlpha, AllBlank, Worked: Boolean;
begin
  RT := rxUnits;
  if fSprites[RT] = nil then
  begin
    fSprites[RT] := TKMSpritePackEdit.Create(RT, fPalettes);
    fSprites[RT].LoadFromRXXFile(ExeDir + 'data\Sprites\' + RXInfo[RT].FileName + '_a.rxx');
  end;

  origSpritesDir := aBaseDir + 'original_frames\';
  interpSpritesDir := aBaseDir + 'interpolated_frames\';
  ForceDirectories(origSpritesDir);

{'fraction_non_alphanumeric': 0.1254630773030378, 'fraction_numerical': 0.010249444307236354, 'mean_word_length': 4.1001259445843825, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2345655', 'n_tokens_mistral': 3220, 'n_tokens_neox': 3055, 'n_words': 703}