unit ASNumUtils;

{ **************************************************************************** }
{ Rejbrand AlgoSim numerical utilities library                                 }
{ Copyright © 2017 Andreas Rejbrand                                            }
{ https://english.rejbrand.se/                                                 }
{ **************************************************************************** }

{$WARN SYMBOL_PLATFORM OFF}
{$WARN DUPLICATE_CTOR_DTOR OFF}

interface

uses SysUtils, Types, Math, ASNum, ASColors, ASPixmap;

type
  TNumColorFunc<T> = reference to function(const Value: T): TASPixel;
  TMatrixNormalizationKind = (mnkNone, mnkMinMax, mnkModulus);
  TNonOptionalMatrixNormalizationKind = mnkMinMax..mnkModulus;

function MatrixPlot(const AMatrix: TRealMatrix;
  AColorFunc: TNumColorFunc<TASR>; AWidth, AHeight: Integer;
  ANormalizationKind: TMatrixNormalizationKind = mnkNone): TASPixmap; overload;

function MatrixPlot(const AMatrix: TRealMatrix;
  const AColor: TASPixel; AWidth, AHeight: Integer;
  ANormalizationKind: TNonOptionalMatrixNormalizationKind): TASPixmap; overload;

function MatrixPlot(const AMatrix: TRealMatrix;
  const AColor1, AColor2: TASPixel; AWidth, AHeight: Integer;
  ANormalizationKind: TNonOptionalMatrixNormalizationKind): TASPixmap; overload;

function MatrixPlot(const AMatrix: TRealMatrix;
  const AColor1, AColor2, AColor3: TASPixel; AWidth, AHeight: Integer;
  ANormalizationKind: TNonOptionalMatrixNormalizationKind): TASPixmap; overload;


implementation

function MatrixPlot(const AMatrix: TRealMatrix;
  AColorFunc: TNumColorFunc<TASR>; AWidth, AHeight: Integer;
  ANormalizationKind: TMatrixNormalizationKind): TASPixmap; overload;
var
  CellWidth, CellHeight: Double;
  y: Integer;
  x: Integer;
  minval, maxval, span: TASR;
begin
  AWidth := Max(AWidth, AMatrix.Size.Cols);
  AHeight := Max(AHeight, AMatrix.Size.Rows);
  Result := TASPixmap.CreateUninitialized(AWidth, AHeight);
  CellWidth := AWidth / AMatrix.Size.Cols;
  CellHeight := AHeight / AMatrix.Size.Rows;

  case ANormalizationKind of
    mnkNone:
      for y := 0 to AMatrix.Size.Rows - 1 do
        for x := 0 to AMatrix.Size.Cols - 1 do
          Result.FillRect(
            Rect(Round(x * CellWidth),
              Round(y * CellHeight),
              Round((x + 1) * CellWidth),
              Round((y + 1) * CellHeight)),
            AColorFunc(AMatrix[y, x]));
    mnkMinMax:
      begin
        minval := Min(AMatrix);
        maxval := Max(AMatrix);
        span := maxval - minval;

        if span = 0 then
          raise EMathException.Create('MatrixPlot: The smallest and the greatest value in the matrix coincide.');

        for y := 0 to AMatrix.Size.Rows - 1 do
          for x := 0 to AMatrix.Size.Cols - 1 do
            Result.FillRect(
              Rect(Round(x * CellWidth),
                Round(y * CellHeight),
                Round((x + 1) * CellWidth),
                Round((y + 1) * CellHeight)),
              AColorFunc((AMatrix[y, x] - minval) / span));
      end;
    mnkModulus:
      begin
        maxval := Max(AMatrix.Abs());
        minval := -maxval;
        span := maxval - minval;

        if maxval = 0 then
          raise EMathException.Create('MatrixPlot: The smallest and the greatest value in the matrix coincide.');

        for y := 0 to AMatrix.Size.Rows - 1 do
          for x := 0 to AMatrix.Size.Cols - 1 do
            Result.FillRect(
              Rect(Round(x * CellWidth),
                Round(y * CellHeight),
                Round((x + 1) * CellWidth),
                Round((y + 1) * CellHeight)),
              AColorFunc((AMatrix[y, x] - minval) / span));
      end;
  end;

end;

function MatrixPlot(const AMatrix: TRealMatrix;
  const AColor: TASPixel; AWidth, AHeight: integer;
  ANormalizationKind: TNonOptionalMatrixNormalizationKind): TASPixmap;
var
  f: TNumColorFunc<TASR>;
begin
  f := function(const Value: TASR): TASPixel
    begin
      Result := Value * AColor;
    end;
  Result := MatrixPlot(AMatrix, f, AWidth, AHeight, ANormalizationKind);
end;

function MatrixPlot(const AMatrix: TRealMatrix;
  const AColor1, AColor2: TASPixel; AWidth, AHeight: integer;
  ANormalizationKind: TNonOptionalMatrixNormalizationKind): TASPixmap;
var
  f: TNumColorFunc<TASR>;
begin
  f := function(const Value: TASR): TASPixel
    begin
      Result := (1 - Value) * AColor1 + Value * AColor2;
    end;
  Result := MatrixPlot(AMatrix, f, AWidth, AHeight, ANormalizationKind);
end;

function MatrixPlot(const AMatrix: TRealMatrix;
  const AColor1, AColor2, AColor3: TASPixel; AWidth, AHeight: integer;
  ANormalizationKind: TNonOptionalMatrixNormalizationKind): TASPixmap;
var
  f: TNumColorFunc<TASR>;
begin
  f := function(const Value: TASR): TASPixel
    begin
      if Value < 0.5 then
        Result := (1 - 2*Value) * AColor1 + 2*Value * AColor2
      else
        Result := (1 - 2*(Value - 0.5)) * AColor2 + 2*(Value - 0.5) * AColor3
    end;
  Result := MatrixPlot(AMatrix, f, AWidth, AHeight, ANormalizationKind);
end;

end.
