unit MainFm;
(*
 * Copyright (c) 2015 - 2025 Dr. Yuriy Kotsarenko. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 *)

// This example illustrates the following features:
//  * Drawing images and portions of thereof with 2D canvas, using multiple techniques, interpolation
//    filters and effects.

interface

uses
  System.Types, System.Classes, Vcl.Graphics, Vcl.Forms, Vcl.Dialogs, Vcl.Controls, PXT.Types, PXT.Graphics, 
  PXT.Controls;

type
  TMainForm = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FRenderingControl: TRenderingControl;
    FDisplaySize: TPoint2i;
    FDisplayScale: Single;

    FDevice: TDevice;
    FSwapChain: TSwapChain;
    FCanvas: TCanvas;
    FTextRenderer: TTextRenderer;
    FTexture: TTexture;

    procedure RenderingControlResize(Sender: TObject);
    procedure RenderingControlPaint(Sender: TObject);
    procedure CreateRenderingControl;
  public
  end;

var
  MainForm: TMainForm;

implementation
{$R *.dfm}

uses
  System.SysUtils, System.Math, PXT.Headers, PXT.Graphics.Consts;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  CreateRenderingControl;
  FDisplaySize := Point2i(ClientWidth, ClientHeight);
  FDisplayScale := PixelsPerInch / 96.0;
  try
    FDevice := TDevice.Create;
    FSwapChain := TSwapChain.Create(FDevice, FRenderingControl.WindowHandle, FDisplaySize, 
      TPixelFormat.RGBA8, TPixelFormat.Unknown, 8, False);
    FCanvas := TCanvas.Create(FDevice);
    FTextRenderer := TTextRenderer.Create(FCanvas, Point2i(512, 512), TPixelFormat.LA8);
    FTexture := TTexture.Create(FDevice, '../../../../Media/ForkLift.png', TPixelFormat.RGBA8);
  except
    on E: EAfterwarpException do
    begin
      MessageDlg(E.Message, mtError, [mbOK], 0);
      Application.Terminate;
      Exit;
    end;
  end;
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  FTexture.Free;
  FTextRenderer.Free;
  FCanvas.Free;
  FSwapChain.Free;
  FDevice.Free;
end;

procedure TMainForm.RenderingControlResize(Sender: TObject);
begin
  if FSwapChain.Handle <> nil then
  begin
    FDisplaySize := Point2i(FRenderingControl.Width, FRenderingControl.Height);
    FSwapChain.Resize(FDisplaySize);
    FRenderingControl.Invalidate;
  end;
end;

procedure TMainForm.RenderingControlPaint(Sender: TObject);
var
  LParams: TTextureParameters;
  LImageSize, LPosition, LOffset: TPoint2f;
  LRect: TFloatRect;
  LMagnification, LAngle, LScale: Single;
  LFontSettings: TFontParameters;
  LTechString: string;
begin
  LScale := FDisplaySize.Y / 720.0;

  // Render on the form.
  FSwapChain.BeginScene;
  try
    FDevice.Clear([TClearLayer.Color], FloatColor($FFE1E2E6));

    FCanvas.BeginScene;
    try
      FCanvas.ContextState := TCanvasContextState.FlatScene;

      // Enable cubic interpolation for drawing image at its full size.
      FCanvas.Attributes := [TCanvasAttribute.Cubic];
      FCanvas.SamplerState := TCanvasSamplerState.Create(TTextureFilter.Linear, TTextureFilter.Linear,
        TTextureFilter.None, TTextureAddress.Clamp, TTextureAddress.Clamp);

      LParams := FTexture.Parameters;
      LImageSize := Point2f(LParams.Width, LParams.Height) * LScale;
      LPosition := (TPoint2f(FDisplaySize) - LImageSize) * 0.5;

      FCanvas.Quad(FTexture, Quad(FloatRect(LPosition, LImageSize)), TQuad.Unity, TColorRect.White);

      // Disable cubic interpolation.
      FCanvas.Attributes := [];

      LRect := FloatRectBDS(201.0, 184.0, 235.0, 210.0);
      LMagnification := 8.0 * LScale;
      LAngle := Pi * -0.15;

      // Draw rectangle to show which portion of parrot is going to be magnified.
      FCanvas.FrameRect(FloatRect(LPosition + (LRect.TopLeft - Point2f(1.0, 1.0)) * LScale,
        (LRect.Size + Point2f(2.0, 2.0)) * LScale), $FF000000, 2.0 * LScale);
      FCanvas.FrameRect(FloatRect(LPosition + LRect.TopLeft * LScale, LRect.Size * LScale),
        $FFFFFFFF, 2.0 * LScale);

      // Nearest filtering...
      FCanvas.SamplerState := TCanvasSamplerState.Create(TTextureFilter.Nearest, TTextureFilter.Nearest,
        TTextureFilter.None);
      LPosition := Point2f(FDisplaySize.X * 0.85, FDisplaySize.Y * 0.2);

      FCanvas.QuadRegion(FTexture, TQuad.Rotated(LPosition, LRect.Size, LAngle, LMagnification), Quad(LRect),
        TColorRect.White);

      // Linear filtering...
      FCanvas.SamplerState := TCanvasSamplerState.Create(TTextureFilter.Linear, TTextureFilter.Linear,
        TTextureFilter.None, TTextureAddress.Clamp, TTextureAddress.Clamp);
      LPosition := Point2f(FDisplaySize.X * 0.85, FDisplaySize.Y * 0.5);

      FCanvas.QuadRegion(FTexture, TQuad.Rotated(LPosition, LRect.Size, LAngle, LMagnification), Quad(LRect),
        TColorRect.White);

      // Linear + cubic filtering...
      FCanvas.Attributes := [TCanvasAttribute.Cubic];
      LPosition := Point2f(FDisplaySize.X * 0.85, FDisplaySize.Y * 0.8);

      FCanvas.QuadRegion(FTexture, TQuad.Rotated(LPosition, LRect.Size, LAngle, LMagnification), Quad(LRect),
        TColorRect.White);

      // Color manipulation samples.
      FCanvas.Attributes := [TCanvasAttribute.ColorAdjust];
      FCanvas.SamplerState := TCanvasSamplerState.Create(TTextureFilter.Linear, TTextureFilter.Linear,
        TTextureFilter.None, TTextureAddress.Clamp, TTextureAddress.Clamp);

      LRect := FloatRectBDS(71.0, 49.0, 265.0, 275.0);
      LMagnification := 1.4 * LScale;
      LAngle := Pi * 0.1;

      LPosition := Point2f(FDisplaySize.X * 0.15, FDisplaySize.Y * 0.17);
      FCanvas.QuadRegion(FTexture, TQuad.Rotated(LPosition, LRect.Size, LAngle, LMagnification), Quad(LRect),
        $FFAF7F7F);

      LPosition := Point2f(FDisplaySize.X * 0.15, FDisplaySize.Y * 0.5);
      FCanvas.QuadRegion(FTexture, TQuad.Rotated(LPosition, LRect.Size, LAngle, LMagnification), Quad(LRect),
        $FF7F107F);

      LPosition := Point2f(FDisplaySize.X * 0.15, FDisplaySize.Y * 0.83);
      FCanvas.QuadRegion(FTexture, TQuad.Rotated(LPosition, LRect.Size, LAngle, LMagnification), Quad(LRect),
        $FF3F7F7F);

      // Text information.
      FCanvas.Attributes := [];
      FCanvas.ContextState := TCanvasContextState.FlatText;
      FCanvas.SamplerState := TCanvasSamplerState.Create(TTextureFilter.Nearest, TTextureFilter.Nearest);

      LFontSettings := TFontParameters.Create('Arial', 16.0 * FDisplayScale, TFontWeight.Bold);
      LFontSettings.Effect.BorderType := TFontBorder.SemiHeavy;
      LFontSettings.Effect.ShadowOpacity := 0.75;
      FTextRenderer.FontParameters := LFontSettings;

      LOffset := Point2f(-50.0, -90.0) * LScale;

      LPosition := Point2f(FDisplaySize.X * 0.93, FDisplaySize.Y * 0.2) + LOffset;
      FTextRenderer.DrawAligned(LPosition, 'Nearest', ColorPair($FF325DFF, $FFC5DCFF), TTextAlignment.Middle,
        TTextAlignment.Middle);

      LPosition := Point2f(FDisplaySize.X * 0.93, FDisplaySize.Y * 0.5) + LOffset;
      FTextRenderer.DrawAligned(LPosition, 'Linear', ColorPair($FF51F700, $FFA7FFC5), TTextAlignment.Middle,
        TTextAlignment.Middle);

      LPosition := Point2f(FDisplaySize.X * 0.93, FDisplaySize.Y * 0.8) + LOffset;
      FTextRenderer.DrawAligned(LPosition, 'Cubic', ColorPair($FF34B9FF, $FF93FFF6), TTextAlignment.Middle,
        TTextAlignment.Middle);

      LOffset := Point2f(70.0, -110) * LScale;

      LPosition := Point2f(FDisplaySize.X * 0.1, FDisplaySize.Y * 0.17) + LOffset;
      FTextRenderer.DrawAligned(LPosition, 'Positive hue shift', ColorPair($FF75C916, $FF9CFADC),
        TTextAlignment.Middle, TTextAlignment.Middle);

      LPosition := Point2f(FDisplaySize.X * 0.1, FDisplaySize.Y * 0.5) + LOffset;
      FTextRenderer.DrawAligned(LPosition, 'Chroma reduction', ColorPair($FF26A1FF, $FF62EAFF),
        TTextAlignment.Middle, TTextAlignment.Middle);

      LPosition := Point2f(FDisplaySize.X * 0.1, FDisplaySize.Y * 0.83) + LOffset;
      FTextRenderer.DrawAligned(LPosition, 'Negative hue shift', ColorPair($FF3318D6, $FFFDC3E7),
        TTextAlignment.Middle, TTextAlignment.Middle);

      // Draw technology string.
      LFontSettings := TFontParameters.Create('Segoe UI', 16.0 * FDisplayScale, TFontWeight.SemiBold);
      LFontSettings.Effect.BorderType := TFontBorder.SemiHeavy;
      LFontSettings.Effect.BorderBrightness := 1.0;
      LFontSettings.Effect.FillBrightness := 0.0;
      LFontSettings.Effect.ShadowBrightness := 0.75;
      LFontSettings.Effect.ShadowOpacity := 1.0;
      FTextRenderer.FontParameters := LFontSettings;

      if FDevice.Technology = TDeviceTechnology.Direct3D then
        LTechString := 'Direct3D (' + IntToStr(FDevice.TechVersion shr 8) + '.' +
          IntToStr((FDevice.TechVersion shr 4) and $0F) + '), features: '
      else
        LTechString := 'OpenGL (' + IntToStr(FDevice.TechVersion shr 8) + '.' +
          IntToStr((FDevice.TechVersion shr 4) and $0F) + '+), features: ';

      LTechString := LTechString + IntToStr(FDevice.TechFeatureVersion shr 8) + '.' +
        IntToStr((FDevice.TechFeatureVersion shr 4) and $0F);

      FTextRenderer.DrawAligned(Point2f(FDisplaySize.X * 0.5, FDisplaySize.Y - 4.0 * FDisplayScale),
        LTechString, $A0FFFFFF, TTextAlignment.Middle, TTextAlignment.Ending);
    finally
      FCanvas.EndScene;
    end;
  finally
    FSwapChain.EndScene;
  end;
end;

procedure TMainForm.CreateRenderingControl;
begin
  FRenderingControl := TRenderingControl.Create(nil);
  FRenderingControl.Parent := Self;
  FRenderingControl.Align := alClient;
  FRenderingControl.Width := 256;
  FRenderingControl.Height := 256;
  FRenderingControl.OnPaint := RenderingControlPaint;
  FRenderingControl.OnResize := RenderingControlResize;
end;

end.
