{**********************************************}
{   TeeChart and TeeTree Image Filters         }
{                                              }
{   Copyright (c) 2006-2020 by Steema Software }
{        All Rights Reserved                   }
{**********************************************}
unit TeeFilters;
{$I TeeDefs.inc}

{$R-}

interface

uses
  {$IFNDEF FMX}
  {$IFNDEF LINUX}
  Windows,
  {$ENDIF}
  {$ENDIF}

  Classes,
  {$IFDEF D6}
  Types,
  {$ENDIF}

  {$IFDEF D18}
  System.Generics.Collections,
  {$ENDIF}

  {$IFDEF FMX}
  System.UITypes,
  FMX.Types, FMX.Controls, FMX.Objects,

  {$IFDEF D18}
  FMX.StdCtrls,
  {$ENDIF}
  {$IFDEF D19}
  FMX.Graphics,
  {$ENDIF}

  {$ELSE}
  Controls, Graphics, StdCtrls, ExtCtrls,

  {$IFDEF D17}
  System.UITypes,
  {$ENDIF}

  {$ENDIF}

  {$IFDEF D16}
  {$IFDEF FMX}
  FMXTee.Canvas
  {$ELSE}
  VCLTee.TeCanvas
  {$ENDIF}
  {$ELSE}
  TeCanvas
  {$ENDIF}
  ;

type
  {$IFDEF FMX}
  TRect=TRectI;
  {$ENDIF}

  TResizeFilter=class(TTeeFilter)
  private
    FWidth  : Integer;
    FHeight : Integer;
  public
    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;

    procedure Assign(Source:TPersistent); override;

    procedure CreateEditor(Creator:IFormCreator; AChanged:TNotifyEvent); override;
    procedure PrepareForGallery; override;
    class function Description: String; override;
  published
    property Width:Integer read FWidth write FWidth default 0;
    property Height:Integer read FHeight write FHeight default 0;
  end;

  TCropFilter=class(TResizeFilter)
  private
    FLeft   : Integer;
    FSmooth : Boolean;
    FTop    : Integer;
  public
    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;

    procedure Assign(Source:TPersistent); override;

    procedure CreateEditor(Creator:IFormCreator; AChanged:TNotifyEvent); override;
    class function Description: String; override;
  published
    property Left:Integer read FLeft write FLeft default 0;
    property Smooth:Boolean read FSmooth write FSmooth default False;
    property Top:Integer read FTop write FTop default 0;
  end;

  TInvertFilter=class(TTeeFilter)
  public
    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;
    class function Description: String; override;
  end;

  TGrayMethod=(gmSimple, gmEye, gmEye2, gmLight);

  TGrayScaleFilter=class(TTeeFilter)
  private
    FMethod : TGrayMethod;
  public
    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;

    procedure Assign(Source:TPersistent); override;

    procedure CreateEditor(Creator:IFormCreator; AChanged:TNotifyEvent); override;
    class function Description: String; override;
  published
    property Method:TGrayMethod read FMethod write FMethod default gmSimple;
  end;

  TFlipFilter=class(TTeeFilter)
  public
    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;
    class function Description: String; override;
  end;

  TReverseFilter=class(TTeeFilter)
  public
    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;
    class function Description: String; override;
  end;

  TAmountFilter=class(TTeeFilter)
  private
    FAmount  : Integer;
    FPercent : Boolean;
    FScrollBar : TScrollBar;

    IOnlyPositive : Boolean;
    procedure ResetScroll(Sender:TObject);
    function ScrollMin:Integer;
    function ScrollMax:Integer;
  public
    Constructor Create(Collection:TCollection); override;

    procedure Assign(Source:TPersistent); override;
    procedure CreateEditor(Creator:IFormCreator; AChanged:TNotifyEvent); override;
  published
    property Percent:Boolean read FPercent write FPercent default True;
    property Amount:Integer read FAmount write FAmount default 5;
  end;

  TMosaicFilter=class(TAmountFilter)
  public
    Constructor Create(Collection:TCollection); override;
    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;
    class function Description: String; override;
  published
    property Percent default False;
  end;

  TBrightnessFilter=class(TAmountFilter)
  public
    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;
    procedure PrepareForGallery; override;
    class function Description: String; override;
  end;

  TContrastFilter=class(TAmountFilter)
  public
    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;
    procedure PrepareForGallery; override;
    class function Description: String; override;
  end;

  TColorFilter=class(TTeeFilter)
  private
    FBlue  : Integer;
    FGreen : Integer;
    FRed   : Integer;
  public
    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;
    procedure Assign(Source:TPersistent); override;
    procedure CreateEditor(Creator:IFormCreator; AChanged:TNotifyEvent); override;
    class function Description: String; override;
  published
    property Red:Integer read FRed write FRed default 0;
    property Green:Integer read FGreen write FGreen default 0;
    property Blue:Integer read FBlue write FBlue default 0;
  end;

  TColorMethod=(cmHue, cmLum, cmSat);

  TColorReplaceFilter=class(TTeeFilter)
  private
    FTo: Byte;
    FFrom: Byte;
    FMethod: TColorMethod;
    FColor: TColor;
  public
    Constructor Create(Collection:TCollection); override;

    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;
    procedure Assign(Source:TPersistent); override;
    procedure CreateEditor(Creator:IFormCreator; AChanged:TNotifyEvent); override;
    class function Description: String; override;
  published
    property Color:TColor read FColor write FColor default {$IFDEF FMX}TAlphaColors.White{$ELSE}clWhite{$ENDIF};
    property FromValue:Byte read FFrom write FFrom default 0;
    property Method:TColorMethod read FMethod write FMethod default cmHue;
    property ToValue:Byte read FTo write FTo default 0;
  end;

  THueLumSatFilter=class(TTeeFilter)
  private
    FHue : Integer;
    FLum : Integer;
    FSat : Integer;
  public
    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;
    procedure Assign(Source:TPersistent); override;
    procedure CreateEditor(Creator:IFormCreator; AChanged:TNotifyEvent); override;
    class function Description: String; override;
  published
    property Hue:Integer read FHue write FHue default 0;
    property Luminance:Integer read FLum write FLum default 0;
    property Saturation:Integer read FSat write FSat default 0;
  end;

  TSharpenFilter=class(TConvolveFilter)
  public
    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;
    class function Description: String; override;
  end;

  TEmbossFilter=class(TConvolveFilter)
  public
    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;
    class function Description: String; override;
  end;

  TSoftenFilter=class(TConvolveFilter)
  public
    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;
    class function Description: String; override;
  end;

  TGammaCorrectionFilter=class(TAmountFilter)
  public
    Constructor Create(Collection:TCollection); override;
    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;
    class function Description: String; override;
  published
    property Amount default 70;
  end;

  TRotateFilter=class(TTeeFilter)
  private
    FAngle     : Double;
    FAutoSize  : Boolean;
    FBackColor : TColor;
    procedure SetAngle(const Value: Double);
  public
    Constructor Create(Collection:TCollection); override;

    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;
    procedure Assign(Source:TPersistent); override;
    procedure CreateEditor(Creator:IFormCreator; AChanged:TNotifyEvent); override;
    procedure PrepareForGallery; override;
    class function Description: String; override;
  published
    property Angle:Double read FAngle write SetAngle;
    property AutoSize:Boolean read FAutoSize write FAutoSize default True;
    property BackColor:TColor read FBackColor write FBackColor default {$IFDEF FMX}TAlphaColors.White{$ELSE}clWhite{$ENDIF};
  end;

  TMirrorDirection=(mdDown, mdUp, mdRight, mdLeft);

  TMirrorFilter=class(TTeeFilter)
  private
    FDirection : TMirrorDirection;
  public
    Constructor Create(Collection:TCollection); override;

    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;
    procedure Assign(Source:TPersistent); override;
    procedure CreateEditor(Creator:IFormCreator; AChanged:TNotifyEvent); override;
    class function Description: String; override;
  published
    property Direction:TMirrorDirection read FDirection write FDirection
                                default mdDown;
  end;

  TTileFilter=class(TTeeFilter)
  private
    FNumCols : Integer;
    FNumRows : Integer;
  public
    Constructor Create(Collection:TCollection); override;

    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;
    procedure Assign(Source:TPersistent); override;
    procedure CreateEditor(Creator:IFormCreator; AChanged:TNotifyEvent); override;
    class function Description: String; override;
  published
    property NumCols:Integer read FNumCols write FNumCols default 3;
    property NumRows:Integer read FNumRows write FNumRows default 3;
  end;

  TBevelFilter=class(TTeeFilter)
  private
    FBright : Integer;
    FSize   : Integer;
  public
    Constructor Create(Collection:TCollection); override;

    procedure Apply(const Bitmap: TBitmap; const R:TRect); override;
    procedure Assign(Source:TPersistent); override;
    procedure CreateEditor(Creator:IFormCreator; AChanged:TNotifyEvent); override;
    class function Description: String; override;
  published
    property Bright:Integer read FBright write FBright default 64;
    property Size:Integer read FSize write FSize default 15;
  end;

  TEqualizeFilter=class(TTeeFilter)
  public
    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;
    class function Description: String; override;
  end;

  TZoomFilter=class(TTeeFilter)
  private
    FPercent : Double;
    FSmooth  : Boolean;
  public
    Constructor Create(Collection:TCollection); override;

    procedure Apply(const Bitmap: TBitmap; const R:TRect); override;
    procedure Assign(Source:TPersistent); override;
    procedure CreateEditor(Creator:IFormCreator; AChanged:TNotifyEvent); override;
    procedure PrepareForGallery; override;
    class function Description: String; override;
  published
    property Percent:Double read FPercent write FPercent;
    property Smooth:Boolean read FSmooth write FSmooth default False;
  end;

  TSobelFilter=class(TTeeFilter)
  public
    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;
    class function Description: String; override;
  end;

  TSepiaFilter=class(TTeeFilter)
  public
    procedure Apply(const Bitmap:TBitmap; const R:TRect); override;
    class function Description: String; override;
  end;

  TImageFiltered=class(TImage)
  private
    FFilters : TFilterItems;

    IAssigning       : Boolean;
    IFilteredPicture : TBitmap;

    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    ICanvas : TCanvas;

    {$IFNDEF FMX}
    InheritedChanged : TNotifyEvent;
    procedure DoPictureChanged(Sender:TObject);
    {$ENDIF}

    procedure FilterChanged(Sender:TObject);
    function FiltersStored:Boolean;
    procedure ReadFilters(Reader: TReader);
    procedure SetFilters(const Value: TFilterItems);
    procedure WriteFilters(Writer: TWriter);
  protected
    procedure DefineProperties(Filer:TFiler); override;

    {$IFDEF FMX}
    {$IFDEF D19}
    procedure DoChanged; override;
    {$ELSE}
    procedure DoBitmapChanged(Sender: TObject); override;
    {$ENDIF}
    {$ENDIF}

    procedure Paint; override;
  public
    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;

    procedure Assign(Source:TPersistent); override;

    function Filtered:TBitmap;
  published
    property Filters:TFilterItems read FFilters write SetFilters stored False;
  end;

var
  TeeFilterClasses : TStringList;  // Due to BCB QC115377, it can't be a TList<TFilterClass>

procedure TeeRegisterFilters(const FilterList:Array of TFilterClass);
procedure TeeUnRegisterFilters(const FilterList:Array of TFilterClass);

procedure ColorToHLS(Color: TColor; out Hue, Luminance, Saturation: Word);
function HLSToColor(Hue, Luminance, Saturation: Word):TColor;

procedure RGBToHLS(const Color: TRGB; out Hue, Luminance, Saturation: Word); overload;
procedure RGBToHLS(const Color: TRGBA; out Hue, Luminance, Saturation: Word); overload;

procedure HLSToRGB(Hue, Luminance, Saturation: Word; out rgb: TRGB); overload;
procedure HLSToRGB(Hue, Luminance, Saturation: Word; out rgb: TRGBA); overload;

// Converts ABitmap pixels into Gray Scale (levels of gray) v5.02 (v8 moved from TeCanvas.pas)
Procedure TeeGrayScale(const ABitmap:TBitmap; Inverted:Boolean; AMethod:Integer);

implementation

uses
  Math, SysUtils, TypInfo,
  {$IFDEF FMX}
  FMXTee.Constants
  {$ELSE}
  {$IFDEF D16}
  VCLTee.TeeConst
  {$ELSE}
  TeeConst
  {$ENDIF}
  {$ENDIF}
  ;

procedure TeeRegisterFilters(const FilterList:Array of TFilterClass);
var t : Integer;
begin
  TeeActivateGroup;

  if not Assigned(TeeFilterClasses) then
     TeeFilterClasses:=TStringList.Create;

  for t:=Low(FilterList) to High(FilterList) do
  if TeeFilterClasses.IndexOf(FilterList[t].ClassName)=-1 then
  begin
    TeeFilterClasses.Add(FilterList[t].ClassName);
    RegisterClass(FilterList[t]);
  end;
end;

procedure TeeUnRegisterFilters(const FilterList:Array of TFilterClass);
var t,tmp : Integer;
begin
  TeeActivateGroup;

  if Assigned(TeeFilterClasses) then
  for t:=Low(FilterList) to High(FilterList) do
  begin
    tmp:=TeeFilterClasses.IndexOf(FilterList[t].ClassName);

    if tmp>-1 then
       TeeFilterClasses.Delete(tmp);

    UnRegisterClass(FilterList[t]);
  end;
end;

{ TResizeFilter }

function SmoothBitmap(Bitmap:TBitmap; Width,Height:Integer):TBitmap;
begin
  {$IFDEF FMX}
  result:=TBitmap.Create(Width,Height);
  {$ELSE}
  result:=TBitmap.Create;
  TeeSetBitmapSize(result,Width,Height);
  {$ENDIF}

  SmoothStretch(Bitmap,result);
end;

procedure TResizeFilter.Apply(const Bitmap:TBitmap; const R:TRect);
var tmp : TBitmap;
begin
  if (Width>0) and (Height>0) then
  begin
    tmp:=SmoothBitmap(Bitmap,Width,Height);
    try
      TeeSetBitmapSize(Bitmap,Width,Height);

      {$IFDEF FMX}
      Bitmap.Canvas.DrawBitmap(tmp,RectF(0,0,tmp.Width,tmp.Height),
          RectF(0,0,tmp.Width,tmp.Height),1);
      {$ELSE}
      Bitmap.Canvas.Draw(0,0,tmp);
      {$ENDIF}
    finally
      tmp.Free;
    end;
  end;
// Do not call inherited;
end;

procedure TResizeFilter.CreateEditor(Creator: IFormCreator;
  AChanged: TNotifyEvent);
begin
  inherited;
  Creator.AddInteger('Width',TeeMsg_Width,0,10000); // Do not localize
  Creator.AddInteger('Height',TeeMsg_Height,0,10000); // Do not localize
end;

procedure TResizeFilter.PrepareForGallery;
begin
  FWidth:=50;
  FHeight:=50;
end;

class function TResizeFilter.Description: String;
begin
  result:=TeeMsg_Resize;
end;

procedure TResizeFilter.Assign(Source: TPersistent);
begin
  if Source is TResizeFilter then
  begin
    FWidth:=TResizeFilter(Source).FWidth;
    FHeight:=TResizeFilter(Source).FHeight;
  end;

  inherited;
end;

{ TCropFilter }

procedure TCropFilter.Apply(const Bitmap: TBitmap; const R: TRect);
var tmp : TBitmap;
begin
  if (Width>0) and (Height>0) then
  begin
    {$IFDEF FMX}
    tmp:=TBitmap.Create(Width,Height);
    {$ELSE}
    tmp:=TBitmap.Create;
    tmp.PixelFormat:=Bitmap.PixelFormat;
    TeeSetBitmapSize(tmp,Width,Height);
    {$ENDIF}
    try
      {$IFNDEF FMX}
      tmp.Canvas.CopyRect(TeeRect(tmp.Width,tmp.Height),
         Bitmap.Canvas,TeeRect(Left,Top,Left+Width-1,Top+Height-1));

      if FSmooth then
         SmoothStretch(tmp,Bitmap)
      else
         Bitmap.Canvas.StretchDraw(TeeRect(Bitmap.Width-1,Bitmap.Height-1),tmp);
      {$ENDIF}
    finally
      tmp.Free;
    end;
  end;

// Do not call inherited;
end;

procedure TCropFilter.Assign(Source: TPersistent);
begin
  if Source is TCropFilter then
  begin
    FLeft:=TCropFilter(Source).FLeft;
    FSmooth:=TCropFilter(Source).FSmooth;
    FTop:=TCropFilter(Source).FTop;
  end;

  inherited;
end;

procedure TCropFilter.CreateEditor(Creator: IFormCreator;
  AChanged: TNotifyEvent);
begin
  inherited;
  Creator.AddInteger('Left',TeeMsg_Left,0,10000); // Do not localize
  Creator.AddInteger('Top',TeeMsg_Top,0,10000); // Do not localize
  Creator.AddCheckBox('Smooth',TeeMsg_Smooth); // Do not localize
end;

class function TCropFilter.Description: String;
begin
  result:=TeeMsg_Crop;
end;

{ TInvertFilter }
procedure TInvertFilter.Apply(const Bitmap:TBitmap; const R:TRect);
var x,y : Integer;
begin
  inherited;

  if not Empty then
     if RGBA then
      for y:=R.Top to R.Bottom do
        for x:=R.Left to R.Right do
        with LinesRGBA[y,x] do
        begin
          Blue:=255-Blue;
          Green:=255-Green;
          Red:=255-Red;
        end
     {$IFNDEF FMX}
     else
      for y:=R.Top to R.Bottom do
        for x:=R.Left to R.Right do
        with Lines[y,x] do
        begin
          Blue:=255-Blue;
          Green:=255-Green;
          Red:=255-Red;
        end;
     {$ENDIF}
end;

class function TInvertFilter.Description: String;
begin
  result:=TeeMsg_Invert;
end;

{ TGrayScaleFilter }
procedure TGrayScaleFilter.Apply(const Bitmap:TBitmap; const R:TRect);

  procedure RGBASimple;
  var x,y :  Integer;
      tmp : Byte;
  begin
    for y:=R.Top to R.Bottom do
        for x:=R.Left to R.Right do
            with LinesRGBA[y,x] do
            begin
              tmp:=(Blue+Green+Red) div 3;
              Blue:=tmp;
              Green:=tmp;
              Red:=tmp;
            end;
  end;

  procedure RGBAEye;
  var x,y :  Integer;
      tmp : Byte;
  begin
    for y:=R.Top to R.Bottom do
        for x:=R.Left to R.Right do
            with LinesRGBA[y,x] do
            begin
              tmp:=Round( (0.299*Red) + (0.587*Green) + (0.114*Blue));

              Blue:=tmp;
              Green:=tmp;
              Red:=tmp;
            end;
  end;

  procedure RGBAEye2;
  var x,y :  Integer;
      tmp : Byte;
  begin
    for y:=R.Top to R.Bottom do
        for x:=R.Left to R.Right do
            with LinesRGBA[y,x] do
            begin
              tmp:=(11*Red+16*Green+5*Blue) div 32;

              Blue:=tmp;
              Green:=tmp;
              Red:=tmp;
            end;
  end;

  function Max(const R,G,B:Byte):Byte; {$IFDEF D9}inline;{$ENDIF}
  begin
    if G>R then result:=G
           else result:=R;

    if B>result then result:=B;
  end;

  function Min(const R,G,B:Byte):Byte; {$IFDEF D9}inline;{$ENDIF}
  begin
    if G<R then result:=G
           else result:=R;

    if B<result then result:=B;
  end;

  procedure RGBALight;
  var x,y :  Integer;
      tmp : Byte;
  begin
    for y:=R.Top to R.Bottom do
        for x:=R.Left to R.Right do
            with LinesRGBA[y,x] do
            begin
              tmp:=(Max(Red,Green,Blue)+Min(Red,Green,Blue)) div 2;

              Blue:=tmp;
              Green:=tmp;
              Red:=tmp;
            end;
  end;

  {$IFNDEF FMX}
  procedure RGBSimple;
  var x,y :  Integer;
      tmp : Byte;
  begin
    for y:=R.Top to R.Bottom do
        for x:=R.Left to R.Right do
            with Lines[y,x] do
            begin
              tmp:=(Blue+Green+Red) div 3;

              Blue:=tmp;
              Green:=tmp;
              Red:=tmp;
            end;
  end;

  procedure RGBEye;
  var x,y :  Integer;
      tmp : Byte;
  begin
    for y:=R.Top to R.Bottom do
        for x:=R.Left to R.Right do
            with Lines[y,x] do
            begin
              tmp:=Round( (0.299*Red) + (0.587*Green) + (0.114*Blue));

              Blue:=tmp;
              Green:=tmp;
              Red:=tmp;
            end;
  end;

  procedure RGBEye2;
  var x,y :  Integer;
      tmp : Byte;
  begin
    for y:=R.Top to R.Bottom do
        for x:=R.Left to R.Right do
            with Lines[y,x] do
            begin
              tmp:=(11*Red+16*Green+5*Blue) div 32;

              Blue:=tmp;
              Green:=tmp;
              Red:=tmp;
            end;
  end;

  procedure RGBLight;
  var x,y :  Integer;
      tmp : Byte;
  begin
    for y:=R.Top to R.Bottom do
        for x:=R.Left to R.Right do
            with Lines[y,x] do
            begin
              tmp:=(Max(Red,Green,Blue)+Min(Red,Green,Blue)) div 2;

              Blue:=tmp;
              Green:=tmp;
              Red:=tmp;
            end;
  end;
  {$ENDIF}

begin
  inherited;

  if Empty then
     Exit;

  if RGBA then
  case Method of
    gmSimple: RGBASimple;
       gmEye: RGBAEye;
      gmEye2: RGBAEye2;
     gmLight: RGBALight;
    end
  {$IFNDEF FMX}
  else
  case Method of
    gmSimple: RGBSimple;
       gmEye: RGBEye;
      gmEye2: RGBEye2;
     gmLight: RGBLight;
    end;
 {$ENDIF}
end;

procedure TGrayScaleFilter.Assign(Source: TPersistent);
begin
  if Source is TGrayScaleFilter then
     FMethod:=TGrayScaleFilter(Source).FMethod;

  inherited;
end;

procedure TGrayScaleFilter.CreateEditor(Creator: IFormCreator;
  AChanged: TNotifyEvent);
begin
  inherited;
  Creator.AddCombo('Method'); // Do not localize
end;

class function TGrayScaleFilter.Description: String;
begin
  result:=TeeMsg_GrayScale;
end;

{ TMosaicFilter }
constructor TMosaicFilter.Create(Collection:TCollection);
begin
  inherited;
  FAmount:=8;
  FPercent:=False;
  IOnlyPositive:=True;
end;

procedure TMosaicFilter.Apply(const Bitmap:TBitmap; const R:TRect);
var
  tmpAmountX : Integer;
  tmpAmountY : Integer;
  tmpDims    : Single;
  tmpAmountPixels : Integer;

  procedure DoMosaic(const tmpX,tmpY:Integer);
  var ar,
      ag,
      ab : Integer;
      xx,
      yy : Integer;
      a    : {$IFDEF FMX}TRGBA{$ELSE}TRGB{$ENDIF};
      Line : {$IFDEF FMX}PRGBAs{$ELSE}PRGBs{$ENDIF};
  begin
    ar:=0;
    ag:=0;
    ab:=0;

    for yy:=0 to tmpAmountY do
    begin
      Line:={$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[tmpY+yy];

      for xx:=0 to tmpAmountX do
      with Line[tmpX+xx] do
      begin
        Inc(ar,Red);
        Inc(ag,Green);
        Inc(ab,Blue);
      end;
    end;

    a.Red:=Round(ar*tmpDims);
    a.Green:=Round(ag*tmpDims);
    a.Blue:=Round(ab*tmpDims);

    for yy:=0 to tmpAmountY do
    begin
      Line:={$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[tmpY+yy];
      for xx:=0 to tmpAmountX do
          Line[tmpX+xx]:=a;
    end;
  end;

  procedure DoMosaicRow(const tmpY:Integer);
  var tmpX : Integer;
  begin
    tmpX:=R.Left;

    while tmpX<R.Right-tmpAmountPixels do
    begin
      DoMosaic(tmpX,tmpY);
      Inc(tmpX,tmpAmountPixels);
    end;

    // Remainder horizontal mosaic cell
    if tmpX<R.Right then
    begin
      tmpAmountX:=R.Right-tmpX;
      tmpDims:=1.0/(Succ(tmpAmountX)*Succ(tmpAmountY));

      DoMosaic(tmpX,tmpY);

      tmpAmountX:=tmpAmountY;
      tmpDims:=1.0/Sqr(tmpAmountPixels);
    end;
  end;

var tmpY : Integer;
begin
  inherited;

  if Empty then
     Exit;

  if Amount>0 then
  begin
    // % is applied to the smaller rectangle edge size:
    if FPercent then
       tmpAmountPixels:=Max(1,Round(Amount*Min((R.Right-R.Left),(R.Bottom-R.Top))*0.01))
    else
       tmpAmountPixels:=Amount;

    tmpDims:=1.0/Sqr(tmpAmountPixels);
    tmpAmountX:=tmpAmountPixels-1;
    tmpAmountY:=tmpAmountX;

    tmpY:=R.Top;
    while tmpY<R.Bottom-tmpAmountPixels do
    begin
      DoMosaicRow(tmpY);
      Inc(tmpY,tmpAmountPixels);
    end;

    // Remainder vertical mosaic row cells
    if tmpY<R.Bottom then
    begin
      tmpAmountY:=R.Bottom-tmpY-1;
      tmpDims:=1.0/(Succ(tmpAmountX)*Succ(tmpAmountY));
      DoMosaicRow(tmpY);
    end;
  end;
end;

class function TMosaicFilter.Description: String;
begin
  result:=TeeMsg_Mosaic;
end;

{ TFlipFilter }
procedure TFlipFilter.Apply(const Bitmap:TBitmap; const R:TRect);
var tmp : {$IFDEF FMX}TRGBA{$ELSE}TRGB{$ENDIF};
    tmpH,
    tmpY,
    x,y : Integer;
begin
  inherited;

  if Empty then
     Exit;

  tmpH:=R.Bottom-R.Top;

  for y:=R.Top to R.Top+(tmpH div 2)-1 do
      for x:=R.Left to R.Right do
      begin
        tmp:={$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,x];
        tmpY:=tmpH-y;
        {$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,x]:={$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[tmpY,x];
        {$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[tmpY,x]:=tmp;
      end;
end;

class function TFlipFilter.Description: String;
begin
  result:=TeeMsg_Flip;
end;

{ TReverseFilter }
procedure TReverseFilter.Apply(const Bitmap:TBitmap; const R:TRect);
var tmp : {$IFDEF FMX}TRGBA{$ELSE}TRGB{$ENDIF};
    tmpW,
    tmpX,
    x,y : Integer;
begin
  inherited;

  if Empty then
     Exit;

  tmpW:=R.Right-R.Left;

  for x:=R.Left to R.Left+(tmpW div 2)-1 do
      for y:=R.Top to R.Bottom do
      begin
        tmp:={$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,x];
        tmpX:=tmpW-x;
        {$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,x]:={$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,tmpX];
        {$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,tmpX]:=tmp;
      end;
end;

class function TReverseFilter.Description: String;
begin
  result:=TeeMsg_Reverse;
end;

{ TAmountFilter }
Constructor TAmountFilter.Create(Collection:TCollection);
begin
  inherited;
  FPercent:=True;
  FAmount:=5; // %
end;

function TAmountFilter.ScrollMin:Integer;
begin
  if FPercent then
     if IOnlyPositive then result:=0 else result:=-100
  else
     if IOnlyPositive then result:=0 else result:=-255;
end;

function TAmountFilter.ScrollMax:Integer;
begin
  if FPercent then result:=100
              else result:=255;
end;

procedure TAmountFilter.ResetScroll(Sender:TObject);
begin
  FScrollBar.Min:=ScrollMin;
  FScrollBar.Max:=ScrollMax;
end;

procedure TAmountFilter.CreateEditor(Creator:IFormCreator; AChanged:TNotifyEvent);
begin
  inherited;
  FScrollBar:=Creator.AddScroll('Amount',ScrollMin,ScrollMax); // Do not localize
  Creator.AddCheckBox('Percent',TeeMsg_Percent,ResetScroll); // Do not localize
end;

procedure TAmountFilter.Assign(Source: TPersistent);
begin
  if Source is TAmountFilter then
  begin
    FAmount:=TAmountFilter(Source).FAmount;
    FPercent:=TAmountFilter(Source).FPercent;
  end;

  inherited;
end;

{ TBrightnessFilter }
procedure TBrightnessFilter.Apply(const Bitmap:TBitmap; const R: TRect);
var x,y,l :  Integer;
    IPercent : Single;
begin
  if Amount=0 then
     Exit;

  inherited;

  if Empty then
     Exit;

  if Percent then
  begin
    IPercent:=FAmount*0.01;

    for y:=R.Top to R.Bottom do
        for x:=R.Left to R.Right do
        with {$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,x] do
        begin
          l:=Red+Round(255*IPercent);
          if l<0 then Red:=0 else if l>255 then Red:=255 else Red:=l;

          l:=Green+Round(255*IPercent);
          if l<0 then Green:=0 else if l>255 then Green:=255 else Green:=l;

          l:=Blue+Round(255*IPercent);
          if l<0 then Blue:=0 else if l>255 then Blue:=255 else Blue:=l;
        end;
  end
  else
  for y:=R.Top to R.Bottom do
      for x:=R.Left to R.Right do
      with {$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,x] do
      begin
        l:=Red+Amount;
        if l<0 then Red:=0 else if l>255 then Red:=255 else Red:=l;

        l:=Green+Amount;
        if l<0 then Green:=0 else if l>255 then Green:=255 else Green:=l;

        l:=Blue+Amount;
        if l<0 then Blue:=0 else if l>255 then Blue:=255 else Blue:=l;
      end;
end;

procedure TBrightnessFilter.PrepareForGallery;
begin
  FAmount:=30;
end;

class function TBrightnessFilter.Description: String;
begin
  result:=TeeMsg_Brightness;
end;

procedure ColorToHLS(Color: TColor; out Hue, Luminance, Saturation: Word);
var tmp : TRGB;
begin
  //Color:={ColorToRGB}(Color);
  tmp.Red:=GetRValue(Color);
  tmp.Green:=GetGValue(Color);
  tmp.Blue:=GetBValue(Color);
  RGBToHLS(tmp,Hue,Luminance,Saturation);
end;

type
  Float=Single;

const
  // HLSMAX BEST IF DIVISIBLE BY 6.  RGBMAX, HLSMAX must each fit in a byte.
  HLSMAX = 240;  // H,L, and S vary over 0-HLSMAX
  RGBMAX = 255;  // R,G, and B vary over 0-RGBMAX

  RGBMAX2 = 2.0*RGBMAX;

  HLSMAXDiv2=HLSMAX/2;
  HLSMAXDiv3=HLSMAX/3;
  HLSMAXDiv6=HLSMAX/6;
  HLSMAXDiv12=HLSMAX/12;
  HLSMAX2=HLSMAX*2;
  HLSMAX2Div3=HLSMAX2/3;

  { Hue is undefined if Saturation is 0 (grey-scale)
    This value determines where the Hue scrollbar is
    initially set for achromatic colors }
  HLSUndefined = 160; // HLSMAX2Div3;

procedure RGBToHLS(const Color: TRGBA; out Hue, Luminance, Saturation: Word);
var tmp : TRGB;
begin
  tmp.Blue:=Color.Blue;
  tmp.Green:=Color.Green;
  tmp.Red:=Color.Red;

  RGBToHLS(tmp,Hue,Luminance,Saturation);
end;

procedure RGBToHLS(const Color: TRGB; out Hue, Luminance, Saturation: Word);
Const
  Inv2RGBMAX = 1/ ( 2 * RGBMAX);
var
  H, L, S: Float;
  R, G, B: Word;
  dif : Integer;
  sum, cMax, cMin: Word;
  InvDif,
  Rdelta, Gdelta, Bdelta: Single; { intermediate value: % of spread from max }
begin
  R:=Color.Red;
  G:=Color.Green;
  B:=Color.Blue;

  { calculate lightness }
  if R>G then
     if R>B then cMax:=R else cMax:=B
  else
     if G>B then cMax:=G else cMax:=B;

  if R<G then
     if R<B then cMin:=R else cMin:=B
  else
     if G<B then cMin:=G else cMin:=B;

  sum:=(cMax + cMin);

  L := ( (sum * HLSMAX) + RGBMAX ) * Inv2RGBMAX;

  if cMax = cMin then  { r=g=b --> achromatic case }
  begin                { saturation }
    Hue := HLSUndefined;  // pwHue := 160;      { MS ColoroHLS always defaults to 160 in this case }
    Luminance := Round(L);
    Saturation := 0;
  end
  else                 { chromatic case }
  begin
    dif:=cMax-cMin;

    { saturation }
    if L <= HLSMAXDiv2 then
       S := ( (dif*HLSMAX) + (sum*0.5) ) / sum
    else
       S := ( (dif*HLSMAX) + ( RGBMAX-(sum*0.5) )) / (2*RGBMAX-sum);

    { hue }
    InvDif:=1/dif;

    Rdelta := ( ((cMax-R)*HLSMAXDiv6) + (dif*0.5) ) * InvDif;
    Gdelta := ( ((cMax-G)*HLSMAXDiv6) + (dif*0.5) ) * InvDif;
    Bdelta := ( ((cMax-B)*HLSMAXDiv6) + (dif*0.5) ) * InvDif;

    if R = cMax then
       H := Bdelta - Gdelta
    else
    if G = cMax then
       H := HLSMAXDiv3 + Rdelta - Bdelta
    else // B == cMax
       H := HLSUndefined + Gdelta - Rdelta;

    if H < 0 then H := H + HLSMAX
    else
    if H > HLSMAX then H := H - HLSMAX;

    Hue := Round(H);
    Luminance := Round(L);
    Saturation := Round(S);
  end;
end;

function HLSToColor(Hue, Luminance, Saturation: Word):TColor;
var tmp : TRGB;
begin
  HLSToRGB(Hue,Luminance,Saturation,tmp);
  result:=RGB(tmp.Red,tmp.Green,tmp.Blue);
end;

procedure HLSToRGB(Hue, Luminance, Saturation: Word; out rgb: TRGBA);
var tmp : TRGB;
begin
  HLSToRGB(Hue,Luminance,Saturation,tmp);
  rgb.Blue:=tmp.Blue;
  rgb.Green:=tmp.Green;
  rgb.Red:=tmp.Red;
  rgb.Alpha:=255;
end;

procedure HLSToRGB(Hue, Luminance, Saturation: Word; out rgb: TRGB);

  function RoundColor(const Value: Integer): Integer;
  begin
    if Value > 255 then Result := 255
                   else Result := Value;
  end;

  function HueToRGB(const Lum, Sat, Hue:Float): Integer;
  const
    InvHLSMAXDiv6=1/HLSMAXDiv6;
  begin
    { return r,g, or b value from this tridrant }
    if hue < HLSMAXDiv6 then
        Result := Round( Lum + (((Sat-Lum)*hue+HLSMAXDiv12)*InvHLSMAXDiv6))
    else
    if hue < HLSMAXDiv2 then
        Result := Round( Sat)
    else
    if hue < HLSMAX2Div3 then
        Result := Round( Lum + (((Sat-Lum)*(HLSMAX2Div3-hue)+HLSMAXDiv12)*InvHLSMAXDiv6) )
    else
        Result := Round( Lum );
  end;

const
  InvHLSMAX=1/HLSMAX;

var
  Magic1, Magic2: Float;       { calculated magic numbers (really!) }

  function RoundColor2(Hue: Float): Integer;
  begin
    { range check: note values passed add/subtract thirds of range }
    if hue < 0 then hue:=hue+HLSMAX
    else
    if hue > HLSMAX then hue:=hue-HLSMAX;

    result:=RoundColor(Round((HueToRGB(Magic1,Magic2,Hue)*RGBMAX + HLSMAXDiv2)*InvHLSMAX));
  end;

begin
  if Saturation = 0 then
  with rgb do
  begin            { achromatic case }
    Red := RoundColor(Round((Luminance * RGBMAX)*InvHLSMAX) );
    Green:=Red;
    Blue:=Green;
    if Hue <> HLSUndefined then ;{ ERROR }
  end
  else
  begin            { chromatic case }
    { set up magic numbers }
    if Luminance <= HLSMAXDiv2 then
       Magic2 := (Luminance * (HLSMAX + Saturation) + HLSMAXDiv2) * InvHLSMAX
    else
       Magic2 := Luminance + Saturation - ((Luminance * Saturation) + HLSMAXDiv2) * InvHLSMAX;

    Magic1 := 2 * Luminance - Magic2;

    { get RGB, change units from HLSMAX to RGBMAX }
    with rgb do
    begin            { achromatic case }
      Red:=RoundColor2(Hue+HLSMAXDiv3);
      Green:=RoundColor2(Hue);
      Blue:=RoundColor2(Hue-HLSMAXDiv3);
    end;
  end;
end;

{ TColorFilter }

procedure TColorFilter.Apply(const Bitmap:TBitmap; const R: TRect);
var x,y    : Integer;
    tmpInt : Integer;
    Line   :  {$IFDEF FMX}PRGBAs{$ELSE}PRGBs{$ENDIF};
begin
  inherited;

  if Empty then
     Exit;

  if (Red<>0) or (Green<>0) or (Blue<>0) then
  for y:=R.Top to R.Bottom do
  begin
    Line:={$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y];

    for x:=R.Left to R.Right do
    with Line[x] do
    begin
      if Self.FRed<>0 then
      begin
        tmpInt:=Red+Self.FRed;
        if tmpInt<0 then Red:=0 else
        if tmpInt>255 then Red:=255 else
                           Red:=tmpInt;
      end;

      if Self.FGreen<>0 then
      begin
        tmpInt:=Green+Self.FGreen;
        if tmpInt<0 then Green:=0 else
        if tmpInt>255 then Green:=255 else
                           Green:=tmpInt;
      end;

      if Self.FBlue<>0 then
      begin
        tmpInt:=Blue+Self.FBlue;
        if tmpInt<0 then Blue:=0 else
        if tmpInt>255 then Blue:=255 else
                           Blue:=tmpInt;
      end;
    end;
  end;
end;

procedure TColorFilter.Assign(Source:TPersistent);
begin
  if Source is TColorFilter then
  begin
    FRed:=TColorFilter(Source).FRed;
    FGreen:=TColorFilter(Source).FGreen;
    FBlue:=TColorFilter(Source).FBlue;
  end;

  inherited;
end;

procedure TColorFilter.CreateEditor(Creator: IFormCreator;
  AChanged: TNotifyEvent);
begin
  inherited;
  Creator.AddScroll('Red',-255,255); // Do not localize
  Creator.AddScroll('Green',-255,255); // Do not localize
  Creator.AddScroll('Blue',-255,255); // Do not localize
end;

class function TColorFilter.Description: String;
begin
  result:=TeeMsg_Color;
end;

{ TColorReplaceFilter }

Constructor TColorReplaceFilter.Create(Collection: TCollection);
begin
  inherited;
  FColor:={$IFDEF FMX}TAlphaColors.White{$ELSE}clWhite{$ENDIF};
end;

procedure TColorReplaceFilter.Apply(const Bitmap: TBitmap; const R: TRect);
var x, y : Integer;
    tmp,
    Hue, Lum, Sat : Word;
    tmpColor : {$IFDEF FMX}TRGBA{$ELSE}TRGB{$ENDIF};
begin
  inherited;

  if Empty then
     Exit;

  if RGBA then

  tmpColor:={$IFDEF FMX}ColorRGBA{$ELSE}RGBValue{$ENDIF}(Color);

  for y:=R.Top to R.Bottom do
    for x:=R.Left to R.Right do
    begin
      RGBToHLS({$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,x],Hue,Lum,Sat);

      case FMethod of
        cmHue: tmp:=Hue;
        cmLum: tmp:=Lum;
      else
        {cmSat:} tmp:=Sat;
      end;

      if (tmp>=FromValue) and (tmp<=ToValue) then
         {$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,x]:=tmpColor;
    end;
end;

procedure TColorReplaceFilter.CreateEditor(Creator: IFormCreator;
  AChanged: TNotifyEvent);
begin
  inherited;

  Creator.AddCombo('Method'); // Do not localize
  Creator.AddScroll('FromValue',0,255); // Do not localize
  Creator.AddScroll('ToValue',0,255); // Do not localize

  Creator.AddColor('Color','Color');
end;

class function TColorReplaceFilter.Description: String;
begin
  result:=TeeMsg_ColorReplacer;
end;

procedure TColorReplaceFilter.Assign(Source: TPersistent);
begin
  if Source is TColorReplaceFilter then
  begin
    FTo:=TColorReplaceFilter(Source).FTo;
    FFrom:=TColorReplaceFilter(Source).FFrom;
    FColor:=TColorReplaceFilter(Source).FColor;
    FMethod:=TColorReplaceFilter(Source).FMethod;
  end;

  inherited;
end;

{ THueLumSatFilter }

procedure THueLumSatFilter.Apply(const Bitmap:TBitmap; const R: TRect);
var x,y    : Integer;
    tmpInt : Integer;
    tmpHue : Word;
    tmpLum : Word;
    tmpSat : Word;
    Line   : {$IFDEF FMX}PRGBAs{$ELSE}PRGBs{$ENDIF};
begin
  inherited;

  if Empty then
     Exit;

  if (FHue<>0) or (FLum<>0) or (FSat<>0) then
  for y:=R.Top to R.Bottom do
  begin
    Line:={$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y];

    for x:=R.Left to R.Right do
    begin
      RGBToHLS(Line[x],tmpHue,tmpLum,tmpSat);

      if Self.FHue<>0 then
      begin
        tmpInt:=tmpHue+Self.FHue;
        if tmpInt<0 then tmpHue:=0 else
        if tmpInt>255 then tmpHue:=255 else
                           tmpHue:=tmpInt;
      end;

      if Self.FLum<>0 then
      begin
        tmpInt:=tmpLum+Self.FLum;
        if tmpInt<0 then tmpLum:=0 else
        if tmpInt>255 then tmpLum:=255 else
                           tmpLum:=tmpInt;
      end;

      if Self.FSat<>0 then
      begin
        tmpInt:=tmpSat+Self.FSat;
        if tmpInt<0 then tmpSat:=0 else
        if tmpInt>255 then tmpSat:=255 else
                           tmpSat:=tmpInt;
      end;

      HLSToRGB(tmpHue,tmpLum,tmpSat,Line[x]);
    end;
  end;
end;

procedure THueLumSatFilter.Assign(Source:TPersistent);
begin
  if Source is THueLumSatFilter then
  begin
    FHue:=THueLumSatFilter(Source).FHue;
    FLum:=THueLumSatFilter(Source).FLum;
    FSat:=THueLumSatFilter(Source).FSat;
  end;

  inherited;
end;

procedure THueLumSatFilter.CreateEditor(Creator: IFormCreator;
  AChanged: TNotifyEvent);
begin
  inherited;
  Creator.AddScroll('Hue',-255,255); // Do not localize
  Creator.AddScroll('Luminance',-255,255); // Do not localize
  Creator.AddScroll('Saturation',-255,255); // Do not localize
end;

class function THueLumSatFilter.Description: String;
begin
  result:=TeeMsg_HueLumSat;
end;

{ TSharpenFilter }

procedure TSharpenFilter.Apply(const Bitmap:TBitmap; const R: TRect);
const Center=2.0;
      Pix=-((Center-1)/8.0);
begin
  Weights[-1,-1]:=Pix;  Weights[-1,0]:=Pix;    Weights[-1,1]:=Pix;
  Weights[ 0,-1]:=Pix;  Weights[ 0,0]:=Center; Weights[ 0,1]:=Pix;
  Weights[ 1,-1]:=Pix;  Weights[ 1,0]:=Pix;    Weights[ 1,1]:=Pix;

  inherited;
end;

class function TSharpenFilter.Description: String;
begin
  result:=TeeMsg_Sharpen;
end;

{ TGammaCorrectionFilter }
Constructor TGammaCorrectionFilter.Create(Collection:TCollection);
begin
  inherited;
  FAmount:=70;
  IOnlyPositive:=True;
end;

procedure TGammaCorrectionFilter.Apply(const Bitmap:TBitmap; const R: TRect);
var t,
    x,y    : Integer;
    IGamma : Array[0..255] of Byte;
    tmp    : Single;
begin
  inherited;

  if Empty then
     Exit;

  if FPercent then
     tmp:=Abs(Amount)*0.01
  else
     tmp:=Abs(Amount)/255.0;

  tmp:=Max(0.001, tmp);

  IGamma[0]:=0;
  for t:=1 to 255 do
      IGamma[t]:=Round(Exp(Ln(t/255.0)/tmp)*255.0);

  for y:=R.Top to R.Bottom do
    for x:=R.Left to R.Right do
    with {$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,x] do
    begin
      Red:=IGamma[Red];
      Green:=IGamma[Green];
      Blue:=IGamma[Blue];
    end;
end;

class function TGammaCorrectionFilter.Description: String;
begin
  result:=TeeMsg_GammaCorrection;
end;

{ TEmbossFilter }

procedure TEmbossFilter.Apply(const Bitmap:TBitmap; const R: TRect);
begin
  Weights[-1,-1]:= 0;  Weights[-1,0]:=-1;    Weights[-1,1]:=0;
  Weights[ 0,-1]:=-1;  Weights[ 0,0]:=1;     Weights[ 0,1]:=1;
  Weights[ 1,-1]:= 0;  Weights[ 1,0]:=-1;    Weights[ 1,1]:=0;

  inherited;
end;

class function TEmbossFilter.Description: String;
begin
  result:=TeeMsg_Emboss;
end;

{ TContrastFilter }

procedure TContrastFilter.Apply(const Bitmap:TBitmap; const R: TRect);
var x,y,l :  Integer;
    IPercent : Single;
begin
  inherited;

  if Empty then
     Exit;

  if Percent then
     IPercent:=FAmount*0.01
  else
     IPercent:=1;

  for y:=R.Top to R.Bottom do
      for x:=R.Left to R.Right do
      with {$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,x] do
      begin
        if Percent then l:=Red+(Round(Red*IPercent)*(Red-128) div 256)
                   else l:=Red+(Amount*(Red-128) div 256);

        if l<0 then Red:=0 else if l>255 then Red:=255 else Red:=l;

        if Percent then l:=Green+(Round(Green*IPercent)*(Green-128) div 256)
                   else l:=Green+(Amount*(Green-128) div 256);

        if l<0 then Green:=0 else if l>255 then Green:=255 else Green:=l;

        if Percent then l:=Blue+(Round(Blue*IPercent)*(Blue-128) div 256)
                   else l:=Blue+(Amount*(Blue-128) div 256);

        if l<0 then Blue:=0 else if l>255 then Blue:=255 else Blue:=l;
      end;
end;

class function TContrastFilter.Description: String;
begin
  result:=TeeMsg_Contrast;
end;

procedure TContrastFilter.PrepareForGallery;
begin
  FAmount:=60;
end;

{ TSoftenFilter }

procedure TSoftenFilter.Apply(const Bitmap:TBitmap; const R: TRect);
begin
  Weights[-1,-1]:=0;  Weights[-1,0]:=0;    Weights[-1,1]:=0;
  Weights[ 0,-1]:=0;  Weights[ 0,0]:=1;    Weights[ 0,1]:=1;
  Weights[ 1,-1]:=0;  Weights[ 1,0]:=1;    Weights[ 1,1]:=1;

  inherited;
end;

class function TSoftenFilter.Description: String;
begin
  result:=TeeMsg_AntiAlias;
end;

{ TImageFiltered }

Constructor TImageFiltered.Create(AOwner: TComponent);
begin
  inherited;

  {$IFNDEF FMX}
  ControlStyle:=ControlStyle+[csOpaque];

  InheritedChanged:=Picture.OnChange;
  Picture.OnChange:=DoPictureChanged;
  {$ENDIF}

  FFilters:=TFilterItems.Create(Self,TTeeFilter);
  FFilters.OnChange:=FilterChanged;
end;

Destructor TImageFiltered.Destroy;
begin
  FreeAndNil(IFilteredPicture);
  FFilters.Free;
  ICanvas.Free;
  inherited;
end;

procedure TImageFiltered.Assign(Source:TPersistent);
begin
  if Source is TImageFiltered then
     Filters:=TImageFiltered(Source).FFilters;

  inherited;
end;

procedure TImageFiltered.FilterChanged(Sender:TObject);
begin
  {$IFDEF FMX}
  Repaint;
  {$ELSE}
  Invalidate;
  {$ENDIF}
end;

{$IFDEF FMX}
{$IFDEF D19}
procedure TImageFiltered.DoChanged;
{$ELSE}
procedure TImageFiltered.DoBitmapChanged(Sender: TObject);
{$ENDIF}
begin
  if not IAssigning then
     FreeAndNil(IFilteredPicture);

  inherited;
end;
{$ELSE}
procedure TImageFiltered.DoPictureChanged;
begin
  if not IAssigning then
  begin
    FreeAndNil(IFilteredPicture);

    if Assigned(InheritedChanged) then
       InheritedChanged(Self);
  end;
end;
{$ENDIF}

function TImageFiltered.Filtered:TBitmap;
begin
  if FFilters.Changed then
  begin
    FreeAndNil(IFilteredPicture);
    FFilters.Changed:=False;
  end;

  if not Assigned(IFilteredPicture) then
  {$IFDEF FMX}
  if Bitmap<>nil then
  {$ELSE}
  if Assigned(Picture) and Assigned(Picture.Graphic) then
  {$ENDIF}
  begin
    IFilteredPicture:=TBitmap.Create{$IFNDEF D19}{$IFDEF FMX}(0,0){$ENDIF}{$ENDIF};

    IAssigning:=True;
    try
      {$IFDEF FMX}
      IFilteredPicture.Assign(Bitmap);
      {$ELSE}
      IFilteredPicture.Assign(Picture.Graphic);
      {$ENDIF}
    finally
      IAssigning:=False;
    end;

    FFilters.ApplyTo(IFilteredPicture);
  end;

  result:=IFilteredPicture;
end;

procedure TImageFiltered.SetFilters(const Value: TFilterItems);
begin
  FFilters.Assign(Value);
  FreeAndNil(IFilteredPicture);
end;

procedure TImageFiltered.Paint;
{$IFNDEF FMX}
var tmp : TGraphic;
    DestH,
    DestW : Integer;
{$ENDIF}
begin
  {$IFDEF FMX}
  {$ELSE}
  if not Assigned(ICanvas) then
  begin
    ICanvas:=TControlCanvas.Create;
    TControlCanvas(ICanvas).Control:=Self;
  end;

  tmp:=Filtered;

  with ICanvas do
  begin
    if Assigned(tmp) then
    begin
      DestW:=DestRect.Right-DestRect.Left;
      DestH:=DestRect.Bottom-DestRect.Top;

      if (tmp.Width=DestW) and (tmp.Height=DestH) then
         Draw(0,0,tmp)
      else
         StretchDraw(DestRect,tmp);
    end;

    if csDesigning in ComponentState then
    begin
      Pen.Style:=psDash;
      Brush.Style:=bsClear;

      {$IFDEF MSWINDOWS}
      SetBkMode(Handle,Windows.TRANSPARENT);
      {$ENDIF}

      with ClientRect do
           Rectangle(Left,Top,Right,Bottom);
    end;
  end;
  {$ENDIF}
end;

procedure TImageFiltered.ReadFilters(Reader: TReader);
begin
  TTeePicture.ReadFilters(Reader,Filters);
end;

procedure TImageFiltered.WriteFilters(Writer: TWriter);
begin
  TTeePicture.WriteFilters(Writer,Filters);
end;

function TImageFiltered.FiltersStored:Boolean;
begin
  result:=Assigned(FFilters) and (FFilters.Count>0);
end;

procedure TImageFiltered.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineProperty('FilterItems',ReadFilters,WriteFilters,FiltersStored);  // Do not localize
end;

{ TRotateFilter }

Constructor TRotateFilter.Create(Collection:TCollection);
begin
  inherited;
  FBackColor:={$IFDEF FMX}TAlphaColors.White{$ELSE}clWhite{$ENDIF};
  FAutoSize:=True;
end;

procedure TRotateFilter.Apply(const Bitmap: TBitmap; const R: TRect);
var h,
    w : Integer;

  procedure Rotate90(const Filter:TTeeFilter);
  var x,
      y : Integer;
  begin
    for y:=0 to h-1 do
        for x:=0 to w-1 do
            Filter.{$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[x,h-y-1]:={$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,x];
  end;

  procedure Rotate270(const Filter:TTeeFilter);
  var x,
      y : Integer;
  begin
    for y:=0 to h-1 do
        for x:=0 to w-1 do
            Filter.{$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[w-x-1,y]:={$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,x];
  end;

  procedure RotateAngle(const DestWidth,DestHeight:Integer; const tmpSin,tmpCos:Single; const Dest:TTeeFilter);
  var tmpX,
      tmpY,
      xc,
      yc,
      xxc,
      yyc,
      xx,
      yy,
      x,
      y : Integer;

      tmpYSin,
      tmpYCos  : Single;

      f2Lines : {$IFDEF FMX}PRGBAs{$ELSE}PRGBs{$ENDIF};
  begin
    xxc:=DestWidth div 2;
    yyc:=DestHeight div 2;

    xc:=w div 2;
    yc:=h div 2;

    tmpY:=-yyc-1;

    for y:=0 to DestHeight-1 do
    begin
      Inc(tmpY);
      tmpYSin:=(tmpY*tmpSin)-xc;
      tmpYCos:=(tmpY*tmpCos)+yc;

      f2Lines:=Dest.{$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y];

      tmpX:=-xxc-1;

      for x:=0 to DestWidth-1 do
      begin
        Inc(tmpX);

        xx:=Round(tmpX*tmpCos-tmpYSin);

        if (xx>=0) and (xx<w) then
        begin
          yy:=Round(tmpX*tmpSin+tmpYCos);

          if (yy>=0) and (yy<h) then
             f2Lines[x]:={$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[yy,xx];
        end;
      end;
    end;
  end;

var tmp : TBitmap;
    f2 : TTeeFilter;
    Sin,
    Cos : {$IFDEF D15}Single{$ELSE}Extended{$ENDIF};
begin
  inherited;

  if Empty then
     Exit;

  while Angle>360 do
        FAngle:=Angle-360;

  if Angle=180 then
  begin
    TFlipFilter.ApplyTo(Bitmap);
    TReverseFilter.ApplyTo(Bitmap);
  end
  else
  if Angle<>0 then
  begin
    tmp:=TBitmap.Create{$IFNDEF D19}{$IFDEF FMX}(0,0){$ENDIF}{$ENDIF};
    try
      h:=Bitmap.Height;
      w:=Bitmap.Width;

      if (Angle=90) or (Angle=270) then
         TeeSetBitmapSize(tmp,h,w)
      else
      begin
        SinCos((360-Angle)*TeePiStep,Sin,Cos);

        if AutoSize then
        begin
          if Sin*Cos>0 then
             TeeSetBitmapSize(tmp,Abs(Round(w*Cos+h*Sin)),
                                  Abs(Round(w*Sin+h*Cos)))
          else
             TeeSetBitmapSize(tmp,Abs(Round(w*Cos-h*Sin)),
                                  Abs(Round(w*Sin-h*Cos)));
        end
        else
          TeeSetBitmapSize(tmp,w,h);
      end;

      if (w>1) and (h>1) then
      begin
        {$IFNDEF FMX}
        if BackColor=clNone then
           tmp.Transparent:=True
        else
        {$ENDIF}
        if BackColor<>{$IFDEF FMX}TAlphaColors.White{$ELSE}clWhite{$ENDIF} then
        with tmp.Canvas do
        begin
          {$IFDEF FMX}
          ClearRect(TeeRect(tmp.Width,tmp.Height));
          {$ELSE}
          Brush.Style:=bsSolid;
          Brush.Color:=FBackColor;
          FillRect(TeeRect(tmp.Width,tmp.Height));
          {$ENDIF}
        end;

        f2:=TTeeFilter.Create(nil);
        try
          f2.Apply(tmp);

          if Angle=90 then
             Rotate90(f2)
          else
          if Angle=270 then
             Rotate270(f2)
          else
             RotateAngle(tmp.Width,tmp.Height,Sin,Cos,f2);

          {$IFNDEF FMX}
          Bitmap.FreeImage;
          {$ENDIF}

          Bitmap.Assign(tmp);
        finally
          f2.Free;
        end;
      end;
    finally
      tmp.Free;
    end;
  end;
end;

procedure TRotateFilter.Assign(Source:TPersistent);
begin
  if Source is TRotateFilter then
  begin
    FAngle:=TRotateFilter(Source).FAngle;
    FAutoSize:=TRotateFilter(Source).FAutoSize;
    FBackColor:=TRotateFilter(Source).FBackColor;
  end;

  inherited;
end;

procedure TRotateFilter.PrepareForGallery;
begin
  FAngle:=45;
end;

class function TRotateFilter.Description: String;
begin
  result:=TeeMsg_Rotate;
end;

procedure TRotateFilter.SetAngle(const Value: Double);
begin
  if FAngle<>Value then
  begin
    FAngle:=Value;
    // Repaint;
  end;
end;

procedure TRotateFilter.CreateEditor(Creator:IFormCreator; AChanged: TNotifyEvent);
begin
  inherited;
  Creator.AddScroll('Angle',0,360); // Do not localize
  Creator.AddColor('BackColor',TeeMsg_Back); // Do not localize
  Creator.AddCheckBox('AutoSize',TeeMsg_Autosize); // Do not localize
end;

{ TMirrorFilter }

Constructor TMirrorFilter.Create(Collection: TCollection);
begin
  inherited;
  AllowRegion:=False;
end;

procedure TMirrorFilter.Apply(const Bitmap: TBitmap; const R: TRect);
var tmp : TBitmap;

  procedure DrawBitmap(X,Y:Integer);
  begin
    {$IFDEF FMX}
    tmp.Canvas.DrawBitmap(Bitmap,RectF(0,0,Bitmap.Width,Bitmap.Height),
        RectF(X,Y,X+Bitmap.Width,Y+Bitmap.Height),1);
    {$ELSE}
    tmp.Canvas.Draw(0,0,Bitmap)
    {$ENDIF}
  end;

begin
  inherited;

  if Empty then
     Exit;

  tmp:=TBitmap.Create{$IFNDEF D19}{$IFDEF FMX}(0,0){$ENDIF}{$ENDIF};
  try
    if (Direction=mdDown) or (Direction=mdUp) then
    begin
      TeeSetBitmapSize(tmp,Bitmap.Width,Bitmap.Height*2);

      if Direction=mdDown then
         DrawBitmap(0,0)
      else
         DrawBitmap(0,Bitmap.Height);

      TFlipFilter.ApplyTo(Bitmap);

      if Direction=mdDown then
         DrawBitmap(0,Bitmap.Height)
      else
         DrawBitmap(0,0);

      Bitmap.Height:=Bitmap.Height*2;
    end
    else
    begin
      TeeSetBitmapSize(tmp,Bitmap.Width*2,Bitmap.Height);

      if Direction=mdRight then
         DrawBitmap(0,0)
      else
         DrawBitmap(Bitmap.Width,0);

      TReverseFilter.ApplyTo(Bitmap);

      if Direction=mdRight then
         DrawBitmap(Bitmap.Width,0)
      else
         DrawBitmap(0,0);

      Bitmap.Width:=Bitmap.Width*2;
    end;

    {$IFDEF FMX}
    Bitmap.Canvas.DrawBitmap(tmp,RectF(0,0,tmp.Width,tmp.Height),
          RectF(0,0,tmp.Width,tmp.Height),1);
    {$ELSE}
    Bitmap.Canvas.Draw(0,0,tmp);
    {$ENDIF}
  finally
    tmp.Free;
  end;
end;

procedure TMirrorFilter.CreateEditor(Creator: IFormCreator;
  AChanged: TNotifyEvent);
begin
  inherited;
  Creator.AddCombo('Direction'); // Do not localize
end;

class function TMirrorFilter.Description: String;
begin
  result:=TeeMsg_Mirror;
end;

procedure TMirrorFilter.Assign(Source: TPersistent);
begin
  if Source is TMirrorFilter then
     FDirection:=TMirrorFilter(Source).FDirection;

  inherited;
end;

{ TTileFilter }

Constructor TTileFilter.Create(Collection: TCollection);
begin
  inherited;
  FNumCols:=3;
  FNumRows:=3;
end;

procedure TTileFilter.Apply(const Bitmap: TBitmap; const R: TRect);
var tmpCol,
    tmpRow,
    tmpW,
    tmpH : Integer;
    tmp  : TBitmap;
begin
  inherited;

  if Empty then
     Exit;

  if FNumCols<1 then FNumCols:=1;
  if FNumRows<1 then FNumRows:=1;

  tmpW:=(R.Right-R.Left) div FNumCols;
  tmpH:=(R.Bottom-R.Top) div FNumRows;

  if (tmpW>0) and (tmpH>0) then
  begin
    tmp:=SmoothBitmap(Bitmap,tmpW,tmpH);
    try
      // Pending: resize last column width, and last row height,
      // to adjust for rounding "div" missing pixels:
      
      for tmpCol:=0 to FNumCols-1 do
          for tmpRow:=0 to FNumRows-1 do
              {$IFDEF FMX}
              Bitmap.Canvas.DrawBitmap(tmp,RectF(0,0,tmp.Width,tmp.Height),
                    RectF(tmpCol*tmpW,tmpRow*tmpH,
                          (tmpCol*tmpW)+tmp.Width,
                          (tmpRow*tmpH)+tmp.Height),1);
              {$ELSE}
              Bitmap.Canvas.Draw(tmpCol*tmpW,tmpRow*tmpH,tmp);
              {$ENDIF}
    finally
      tmp.Free;
    end;
  end;
end;

procedure TTileFilter.CreateEditor(Creator: IFormCreator;
  AChanged: TNotifyEvent);
begin
  inherited;
  Creator.AddInteger('NumCols',TeeMsg_Columns,1,1000); // Do not localize
  Creator.AddInteger('NumRows',TeeMsg_Rows,1,1000); // Do not localize
end;

class function TTileFilter.Description: String;
begin
  result:=TeeMsg_Tile;
end;

procedure TTileFilter.Assign(Source: TPersistent);
begin
  if Source is TTileFilter then
  begin
    FNumCols:=TTileFilter(Source).FNumCols;
    FNumRows:=TTileFilter(Source).FNumRows;
  end;

  inherited;
end;

{ TBevelFilter }

Constructor TBevelFilter.Create(Collection: TCollection);
begin
  inherited;
  FBright:=64;
  FSize:=15;
end;

procedure TBevelFilter.Apply(const Bitmap: TBitmap; const R: TRect);
var t,
    x,y,
    h2,w2,
    x1,x2,
    y1,y2 : Integer;
begin
  inherited;

  if Empty then
     Exit;

  x1:=R.Left;
  x2:=R.Right;
  y1:=R.Top;
  y2:=R.Bottom;

  w2:=(R.Right-R.Left) div 2;
  h2:=(R.Bottom-R.Top) div 2;

  for t:=0 to FSize-1 do
  begin
    if t<h2 then
    for x:=R.Left+t to R.Right-t do
    begin
      with {$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y1,x] do
      begin
        if Red+Bright>255 then Red:=255
                          else Inc(Red,Bright);
        if Green+Bright>255 then Green:=255
                            else Inc(Green,Bright);
        if Blue+Bright>255 then Blue:=255
                           else Inc(Blue,Bright);
      end;

      with {$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y2,x] do
      begin
        if Red-Bright<0 then Red:=0
                        else Dec(Red,Bright);
        if Green-Bright<0 then Green:=0
                          else Dec(Green,Bright);
        if Blue-Bright<0 then Blue:=0
                         else Dec(Blue,Bright);
      end;

    end;

    Inc(y1);
    Dec(y2);

    if t<w2 then
    for y:=R.Top+t+1 to R.Bottom-t do
    begin
      with {$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,x1] do
      begin
        if Red+Bright>255 then Red:=255
                          else Inc(Red,Bright);
        if Green+Bright>255 then Green:=255
                            else Inc(Green,Bright);
        if Blue+Bright>255 then Blue:=255
                           else Inc(Blue,Bright);
      end;

      with {$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,x2] do
      begin
        if Red-Bright<0 then Red:=0
                        else Dec(Red,Bright);
        if Green-Bright<0 then Green:=0
                          else Dec(Green,Bright);
        if Blue-Bright<0 then Blue:=0
                         else Dec(Blue,Bright);
      end;
    end;

    Inc(x1);
    Dec(x2);
  end;
end;

procedure TBevelFilter.CreateEditor(Creator: IFormCreator;
  AChanged: TNotifyEvent);
begin
  inherited;
  Creator.AddScroll('Bright',1,255); // Do not localize
  Creator.AddScroll('Size',1,1000); // Do not localize
end;

class function TBevelFilter.Description: String;
begin
  result:=TeeMsg_Bevel;
end;

procedure TBevelFilter.Assign(Source: TPersistent);
begin
  if Source is TBevelFilter then
  begin
    FBright:=TBevelFilter(Source).FBright;
    FSize:=TBevelFilter(Source).FSize;
  end;

  inherited;
end;

{ TEqualizeFilter }

procedure TEqualizeFilter.Apply(const Bitmap:TBitmap; const R:TRect);
var Min,
    Max : TRGB;
    x,y : Integer;
    RedFactor,
    GreenFactor,
    BlueFactor : Single;
begin
  inherited;

  if Empty then
     Exit;

  // Calculate min and max RGB
  Min.Red:=255;
  Min.Green:=255;
  Min.Blue:=255;

  Max.Red:=0;
  Max.Green:=0;
  Max.Blue:=0;

  for y:=R.Top+1 to R.Bottom-1 do
      for x:=R.Left+1 to R.Right-1 do
        with {$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,x] do
        begin
          if Red>Max.Red then Max.Red:=Red;
          if Red<Min.Red then Min.Red:=Red;

          if Green>Max.Green then Max.Green:=Green;
          if Green<Min.Green then Min.Green:=Green;

          if Blue>Max.Blue then Max.Blue:=Blue;
          if Blue<Min.Blue then Min.Blue:=Blue;
        end;

  // Check if already equalized
  if (Min.Red=0) and (Min.Green=0) and (Min.Blue=0) and
     (Max.Red=255) and (Max.Green=255) and (Max.Blue=255) then
       Exit;

  // Equalize

  RedFactor:=Max.Red-Min.Red;
  if RedFactor=0 then RedFactor:=0 else RedFactor:=255/RedFactor;

  GreenFactor:=Max.Green-Min.Green;
  if GreenFactor=0 then GreenFactor:=0 else GreenFactor:=255/GreenFactor;

  BlueFactor:=Max.Blue-Min.Blue;
  if BlueFactor=0 then BlueFactor:=0 else BlueFactor:=255/BlueFactor;

  for y:=R.Top+1 to R.Bottom-1 do
      for x:=R.Left+1 to R.Right-1 do
        with {$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,x] do
        begin
          Red:=Round((Red-Min.Red)*RedFactor);
          Green:=Round((Green-Min.Green)*GreenFactor);
          Blue:=Round((Blue-Min.Blue)*BlueFactor);
        end;
end;

class function TEqualizeFilter.Description: String;
begin
  result:=TeeMsg_Equalize;
end;

{ TZoomFilter }

Constructor TZoomFilter.Create(Collection: TCollection);
begin
  inherited;
  FPercent:=10;
end;

procedure TZoomFilter.Apply(const Bitmap: TBitmap; const R: TRect);
var w,h,
    wp,hp : Integer;

  procedure DoCrop(ALeft,ATop:Integer; const ABitmap:TBitmap);
  begin
    with TCropFilter.Create(nil) do
    try
      Left:=ALeft+wp;
      Top:=ATop+hp;
      Width:=Max(1,w-2*wp);
      Height:=Max(1,h-2*hp);
      Smooth:=Self.Smooth;
      Apply(ABitmap,R);
    finally
      Free;
    end;
  end;

var tmp : TBitmap;
begin
  w:=R.Right-R.Left+1;
  h:=R.Bottom-R.Top+1;
  wp:=Round(FPercent*w*0.005);
  hp:=Round(FPercent*h*0.005);

  if (Bitmap.Width=w) and (Bitmap.Height=h) then
     DoCrop(R.Left,R.Top,Bitmap)
  else
  begin
    {$IFDEF FMX}
    tmp:=TBitmap.Create(w,h);
    {$ELSE}
    tmp:=TBitmap.Create;
    TeeSetBitmapSize(tmp,w,h);
    {$ENDIF}

    try
      {$IFNDEF FMX}
      tmp.Canvas.CopyRect(TeeRect(w,h),Bitmap.Canvas,R);

      DoCrop(0,0,tmp);

      Bitmap.Canvas.Draw(R.Left,R.Top,tmp);
      {$ENDIF}
    finally
      tmp.Free;
    end;
  end;
end;

procedure TZoomFilter.CreateEditor(Creator: IFormCreator;
  AChanged: TNotifyEvent);
begin
  inherited;
  Creator.AddScroll('Percent',0,100); // Do not localize
  Creator.AddCheckBox('Smooth',TeeMsg_Smooth); // Do not localize
end;

procedure TZoomFilter.PrepareForGallery;
begin
  FPercent:=60;
end;

class function TZoomFilter.Description: String;
begin
  result:=TeeMsg_Zoom;
end;

procedure TZoomFilter.Assign(Source: TPersistent);
begin
  if Source is TZoomFilter then
  begin
    FPercent:=TZoomFilter(Source).FPercent;
    FSmooth:=TZoomFilter(Source).FSmooth;
  end;

  inherited;
end;

{ TSobelFilter }

procedure TSobelFilter.Apply(const Bitmap: TBitmap; const R: TRect);
const
  WX : Array[-1..1,-1..1] of Integer=((1,2, 1),(0,0, 0),(-1,-2,-1));
  WY : Array[-1..1,-1..1] of Integer=((1,0,-1),(2,0,-2),( 1, 0,-1));

var SumX,
    SumY,
    xx,yy,
    tmp,
    x,y : Integer;
    tmpGray : Array of Array of Byte;
begin
  inherited;

  if Empty then
     Exit;

  // Temp grayscale pixels:
  SetLength(tmpGray,R.Bottom-R.Top+1,R.Right-R.Left+1);

  for y:=R.Top+1 to R.Bottom-1 do
      for x:=R.Left+1 to R.Right-1 do
          with {$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,x] do
               tmpGray[y-R.Top,x-R.Left]:=(Blue+Green+Red) div 3;

  // Loop all pixels, calculate Sobel pixel:
  for y:=R.Top+1 to R.Bottom-1 do
      for x:=R.Left+1 to R.Right-1 do
      begin
        SumX:=0;
        SumY:=0;

        for yy:=-1 to 1 do
            for xx:=-1 to 1 do
            begin
              tmp:=tmpGray[y+yy,x+xx];
              Inc(SumX,tmp*WX[xx,yy]);
              Inc(SumY,tmp*WY[xx,yy]);
            end;

        tmp:=Abs(SumX)+Abs(SumY);
        if tmp>255 then tmp:=255;

        with {$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,x] do
        begin
          Blue:=tmp;
          Green:=tmp;
          Red:=tmp;
        end;
      end;

  // Release temp grayscale
  tmpGray:=nil;
end;

class function TSobelFilter.Description: String;
begin
  result:=TeeMsg_Sobel;
end;

{$IFNDEF FMX}
procedure RotateGradient(const Gradient:TCustomTeeGradient; const ABitmap:TBitmap);
begin
  // Speed Protection: do not rotate huge bitmaps (it'll take many seconds)
  if ABitmap.Width*ABitmap.Height < 16777216 then
  with TRotateFilter.Create(nil) do
  try
    Angle:=Gradient.Angle;
    Apply(ABitmap);
  finally
    Free;
  end;
end;
{$ENDIF}

// This procedure will convert all pixels in ABitmap to levels of gray
Procedure TeeGrayScale(const ABitmap:TBitmap; Inverted:Boolean; AMethod:Integer);
var tmp : TGrayScaleFilter;
begin
  tmp:=TGrayScaleFilter.Create(nil);
  try
    if AMethod<>0 then tmp.Method:=gmEye;
    tmp.Apply(ABitmap);
  finally
    tmp.Free;
  end;

  if Inverted then
     TInvertFilter.ApplyTo(ABitmap);
end;

{ TSepiaFilter }

// http://www.techrepublic.com/blog/howdoi/how-do-i-convert-images-to-grayscale-and-sepia-tone-using-c/120

procedure TSepiaFilter.Apply(const Bitmap: TBitmap; const R: TRect);
var tmp,
    x,y : Integer;
    R0,G0,B0 : Byte;
begin
  inherited;

  if Empty then
     Exit;

  for y:=R.Top to R.Bottom do
      for x:=R.Left to R.Right do
      with {$IFDEF FMX}LinesRGBA{$ELSE}Lines{$ENDIF}[y,x] do
      begin
        R0:=Red;
        G0:=Green;
        B0:=Blue;

        tmp:= Round( (R0*0.393) + (G0*0.769) + (B0*0.189) );
        if tmp<0 then Red:=0 else if tmp>255 then Red:=255 else Red:=tmp;

        tmp:= Round( (R0*0.349) + (G0*0.686) + (B0*0.168) );
        if tmp<0 then Green:=0 else if tmp>255 then Green:=255 else Green:=tmp;

        tmp:=  Round( (R0*0.272) + (G0*0.534) + (B0*0.131) );
        if tmp<0 then Blue:=0 else if tmp>255 then Blue:=255 else Blue:=tmp;
      end;
end;

class function TSepiaFilter.Description: String;
begin
  result:=TeeMsg_Sepia;
end;

initialization
  TeeActivateGroup;

  {$IFDEF FMX}
    GroupDescendentsWith(TTeeFilter,TFMXObject);
  {$ELSE}
    {$IFDEF D6}
    GroupDescendentsWith(TTeeFilter,TControl);
    {$ENDIF}
  {$ENDIF}

  TeeRegisterFilters([ TInvertFilter,
                       TGrayScaleFilter,
                       TMosaicFilter,
                       TFlipFilter,
                       TReverseFilter,
                       TBrightnessFilter,
                       TContrastFilter,
                       TColorFilter,
                       TColorReplaceFilter,
                       THueLumSatFilter,
                       TBlurFilter,
                       TSharpenFilter,
                       TGammaCorrectionFilter,
                       TEmbossFilter,
                       TSoftenFilter,
                       TCropFilter,
                       TResizeFilter,
                       TRotateFilter,
                       TMirrorFilter,
                       TTileFilter,
                       TBevelFilter,
                       TEqualizeFilter,
                       TZoomFilter,
                       TSobelFilter,
                       TSepiaFilter ]);

  {$IFNDEF FMX}
  TeeGradientRotate:=RotateGradient;
  {$ENDIF}

finalization
  {$IFNDEF FMX}
  TeeGradientRotate:=nil;
  {$ENDIF}

  FreeAndNil(TeeFilterClasses);
end.

