lasarus_compotents/bgracontrols/bgraimagemanipulation.pas

4073 lines
121 KiB
ObjectPascal

// SPDX-License-Identifier: LGPL-3.0-linking-exception
unit BGRAImageManipulation;
{ ============================================================================
BGRAImageManipulation Unit
originally written in 2011 by - Emerson Cavalcanti <emersoncavalcanti at googlesites>
============================================================================
Description:
TBGRAImageManipulation is a component designed to make simple changes in an
image while maintaining the aspect ratio of the final image and allow it to
cut to reduce the unnecessary edges. The selected area is painted with a
different transparency level for easy viewing of what will be cut.
============================================================================
History:
2011-05-03 - Emerson Cavalcanti
- Initial version
2011-06-01 - Emerson Cavalcanti
- Fixed aspect ratio when the image has a dimension smaller than
the size of the component.
- Fixed memory leak on temporary bitmaps.
- Fixed unecessary release of bitmap.
- Inserted Anchor and Align property on component.
- Implemented 'Keep aspect Ratio' property. Now you can select an
area without maintaining the aspect ratio.
2011-06-03 - Emerson Cavalcanti
- Improved selection when don't use aspect ratio.
- Improved response when resize component.
- Fixed memory leak on resample bitmap.
2011-06-04 - Circular
- Fixed divide by zero when calculate aspect ratio on
getImageRect.
2011-06-07 - Emerson Cavalcanti
- Improved function of aspect ratio including a variable to
provide the value directly in the component, instead of using
the dimensions of the component as the source of this value.
- Improved exhibition of anchors on selection.
- Improved mouse cursor.
- Included function to get the aspect ratio from image size.
- Included rotate Left and Right functions.
2013-10-13 - Massimo Magnano
- Add multi crop areas
- Add get Bitmap not resampled (original scale)
2014-08-04 - lainz-007-
- Included DataType.inc inside the unit
2021-03-30 - Massimo Magnano
- Each CropArea has its own AspectRatio, Add Events, Border Color
2021-04-30 - Massimo Magnano
- CropArea list Load/Save, bug fixes
2023-06 - Massimo Magnano
- the CropArea.Area property is relative to the unscaled image (unused in render/mouse events)
- added CropArea.ScaledArea property relative to the scaled image (used in render/mouse events)
- removed the use of DeltaX, DeltaY in render/mouse/etc
- CropAreas Area and ScaledArea property is updated during the mouse events
- rewriting of the methods for taking cropped images
-08 - the CropArea.Area property can be specified in Pixels,Cm,Inch
- Alt on MouseUp Undo the Crop Area Changes,Optimized mouse events
-09 - OverAnchor gives precedence to the selected area than Z Order
- EmptyImage property; CropAreas when Image is Empty; Old Code deleted and optimized
- XML Use Laz2_XMLCfg in fpc
- divide by zero in getImageRect on Component Loading
- EmptyImage size to ClientRect when Width/Height=0; Mouse Events when Image is Empty
- CropArea Rotate and Flip
- CropArea Duplicate and SetSize
- NewCropAreaDefault property (to Cm); ResolutionUnitConvert function; SetEmptyImageSizeToCropAreas
============================================================================
}
{******************************* CONTRIBUTOR(S) ******************************
- Edivando S. Santos Brasil | mailedivando@gmail.com
(Compatibility with delphi VCL 11/2018)
***************************** END CONTRIBUTOR(S) *****************************}
{$I bgracontrols.inc}
interface
{$IFDEF FPC}
{$DEFINE USE_Laz2_XMLCfg}
{$ENDIF}
uses
Classes, Contnrs, SysUtils,
{$IFDEF FPC}LCLIntf, LResources, FPImage, {$ENDIF}
Forms, Controls, Graphics, Dialogs,
{$IFNDEF FPC}Windows, Messages, BGRAGraphics, GraphType,{$ENDIF}
{$IFDEF USE_Laz2_XMLCfg}Laz2_XMLCfg,{$ELSE}XMLConf,{$ENDIF}
BCBaseCtrls, BGRABitmap, BGRABitmapTypes, BGRAGradientScanner;
{$IFNDEF FPC}
const
crSizeNW = TCursor(-23);
crSizeN = TCursor(-24);
crSizeNE = TCursor(-25);
crSizeW = TCursor(-26);
crSizeE = TCursor(-27);
crSizeSW = TCursor(-28);
crSizeS = TCursor(-29);
crSizeSE = TCursor(-30);
crUpArrow = TCursor(-10);
crHourGlass = TCursor(-11);
crDrag = TCursor(-12);
crNoDrop = TCursor(-13);
crHSplit = TCursor(-14);
crVSplit = TCursor(-15);
crMultiDrag = TCursor(-16);
{$ENDIF}
type
TCoord = packed record
x1 : LongInt;
y1 : LongInt;
x2 : LongInt;
y2 : LongInt;
end;
TRatio = packed record
Horizontal : LongInt;
Vertical : LongInt;
end;
TCardinalDirection = (NORTH, SOUTH, WEST, EAST);
TDirection = set of TCardinalDirection;
TSizeLimits = packed record
minWidth : LongInt;
maxWidth : LongInt;
minHeight : LongInt;
maxHeight : LongInt;
end;
TBGRAImageManipulation = class;
TCropAreaList = class;
{ TCropArea }
BoolParent = (bFalse=0, bTrue=1, bParent=2);
TCropAreaIcons = set of (cIcoIndex, cIcoLockSize, cIcoLockMove);
TCropArea = class(TObject)
protected
fOwner :TBGRAImageManipulation;
OwnerList:TCropAreaList;
rScaledArea:TRect;
rArea :TRectF;
rAreaUnit:TResolutionUnit;
rRatio :TRatio;
rAspectX,
rAspectY,
rMinHeight,
rMinWidth : Integer;
rAspectRatio,
rName: String;
rKeepAspectRatio: BoolParent;
Loading :Boolean;
rIcons: TCropAreaIcons;
procedure CopyAspectFromParent;
procedure setAspectRatio(AValue: string);
procedure setKeepAspectRatio(AValue: BoolParent);
procedure setScaledArea(AValue: TRect);
function getLeft: Single;
procedure setLeft(AValue: Single);
function getTop: Single;
procedure setTop(AValue: Single);
function getWidth: Single;
procedure setWidth(AValue: Single);
function getHeight: Single;
procedure setHeight(AValue: Single);
function getMaxHeight: Single;
function getMaxWidth: Single;
function getRealAspectRatio(var ARatio: TRatio):Boolean; //return Real KeepAspect
function getRealKeepAspectRatio:Boolean;
function getIndex: Longint;
function getIsNullSize: Boolean;
procedure setArea(AValue: TRectF);
procedure setAreaUnit(AValue: TResolutionUnit);
procedure setName(AValue: String);
procedure setIcons(AValue: TCropAreaIcons);
procedure Render_Refresh;
procedure GetImageResolution(var resX, resY:Single; var resUnit:TResolutionUnit);
procedure CalculateScaledAreaFromArea;
procedure CalculateAreaFromScaledArea;
function GetPixelArea(const AValue: TRectF):TRect;
function CheckScaledOutOfBounds(var AArea:TRect):Boolean;
function CheckAreaOutOfBounds(var AArea:TRectF):Boolean;
property ScaledArea :TRect read rScaledArea write setScaledArea;
public
Rotate :Single;
UserData :Integer;
BorderColor :TBGRAPixel;
function getResampledBitmap(ACopyProperties: Boolean=False): TBGRABitmap;
function getBitmap(ACopyProperties: Boolean=False): TBGRABitmap;
constructor Create(AOwner: TBGRAImageManipulation; AArea: TRectF;
AAreaUnit: TResolutionUnit = ruNone; //Pixels
AUserData: Integer = -1); overload;
constructor Create(AOwner: TBGRAImageManipulation;
DuplicateFrom: TCropArea; InsertInList:Boolean); overload;
destructor Destroy; override;
//ZOrder
procedure BringToFront;
procedure BringToBack;
procedure BringForward;
procedure BringBackward;
//Rotate/Flip
procedure RotateLeft;
procedure RotateRight;
procedure FlipHLeft;
procedure FlipHRight;
procedure FlipVUp;
procedure FlipVDown;
procedure SetSize(AWidth, AHeight:Single);
property Area:TRectF read rArea write setArea;
property AreaUnit:TResolutionUnit read rAreaUnit write setAreaUnit;
property Top:Single read getTop write setTop;
property Left:Single read getLeft write setLeft;
property Width:Single read getWidth write setWidth;
property Height:Single read getHeight write setHeight;
property MaxWidth:Single read getMaxWidth;
property MaxHeight:Single read getMaxHeight;
property AspectRatio: string read rAspectRatio write setAspectRatio;
property KeepAspectRatio: BoolParent read rKeepAspectRatio write setKeepAspectRatio default bParent;
property Index:Longint read getIndex;
property Name:String read rName write setName;
property isNullSize: Boolean read getIsNullSize;
property Icons:TCropAreaIcons read rIcons write setIcons;
end;
{ TCropAreaList }
TCropAreaList = class(TObjectList)
protected
fOwner :TBGRAImageManipulation;
rName :String;
rLoading :Boolean;
function getCropArea(aIndex: Integer): TCropArea;
procedure setCropArea(aIndex: Integer; const Value: TCropArea);
procedure setLoading(AValue: Boolean);
procedure Notify(Ptr: Pointer; Action: TListNotification); override;
property Loading :Boolean read rLoading write setLoading;
public
constructor Create(AOwner: TBGRAImageManipulation);
function add(aCropArea: TCropArea): integer;
procedure Load(const XMLConf: TXMLConfig; XMLPath: String='');
procedure Save(const XMLConf: TXMLConfig; XMLPath: String='');
procedure LoadFromStream(Stream: TStream; XMLPath: String='');
procedure LoadFromFile(const FileName: String; XMLPath: String='');
procedure SaveToStream(Stream: TStream; XMLPath: String='');
procedure SaveToFile(const FileName: String; XMLPath: String='');
//Rotate/Flip
procedure RotateLeft;
procedure RotateRight;
procedure FlipHLeft;
procedure FlipHRight;
procedure FlipVUp;
procedure FlipVDown;
property items[aIndex: integer] : TCropArea read getCropArea write setCropArea; default;
property Name:String read rName write rName;
end;
TgetAllBitmapsCallback = procedure (Bitmap :TBGRABitmap; CropArea: TCropArea; AUserData:Integer) of object;
{ TBGRAEmptyImage }
TBGRAEmptyImage = class(TPersistent)
private
fOwner: TBGRAImageManipulation;
rAllow: Boolean;
rResolutionHeight: Single;
rResolutionUnit: TResolutionUnit;
rResolutionWidth: Single;
rShowBorder: Boolean;
function getHeight: Integer;
function getWidth: Integer;
procedure SetResolutionUnit(AValue: TResolutionUnit);
public
property Width:Integer read getWidth;
property Height:Integer read getHeight;
constructor Create(AOwner: TBGRAImageManipulation);
published
property Allow: Boolean read rAllow write rAllow default False;
property ResolutionUnit: TResolutionUnit read rResolutionUnit write SetResolutionUnit default ruPixelsPerCentimeter;
property ResolutionWidth: Single read rResolutionWidth write rResolutionWidth;
property ResolutionHeight: Single read rResolutionHeight write rResolutionHeight;
property ShowBorder: Boolean read rShowBorder write rShowBorder default False;
end;
{ TBGRANewCropAreaDefault }
TBGRANewCropAreaDefault = class(TPersistent)
private
fOwner: TBGRAImageManipulation;
rAspectRatio: string;
rKeepAspectRatio: BoolParent;
rResolutionUnit: TResolutionUnit;
public
constructor Create(AOwner: TBGRAImageManipulation);
published
property ResolutionUnit: TResolutionUnit read rResolutionUnit write rResolutionUnit default ruPixelsPerCentimeter;
property AspectRatio: string read rAspectRatio write rAspectRatio;
property KeepAspectRatio: BoolParent read rKeepAspectRatio write rKeepAspectRatio default bFalse;
end;
{ TBGRAImageManipulation }
TCropAreaEvent = procedure (Sender: TBGRAImageManipulation; CropArea: TCropArea) of object;
TCropAreaLoadEvent = function (Sender: TBGRAImageManipulation; CropArea: TCropArea;
const XMLConf: TXMLConfig; const Path:String):Integer of object;
TCropAreaSaveEvent = procedure (Sender: TBGRAImageManipulation; CropArea: TCropArea;
const XMLConf: TXMLConfig; const Path:String) of object;
TBGRAIMContextPopupEvent = procedure(Sender: TBGRAImageManipulation; CropArea: TCropArea;
AnchorSelected :TDirection; MousePos: TPoint; var Handled: Boolean) of object;
TBGRAImageManipulation = class(TBGRAGraphicCtrl)
private
{ Private declarations }
fAnchorSize: byte;
fAnchorSelected: TDirection;
fBorderSize: byte;
fAspectRatio: string;
fAspectX: integer;
fAspectY: integer;
fKeepAspectRatio: boolean;
fMinHeight: integer;
fMinWidth: integer;
fMouseCaught: boolean;
fStartPoint,
fEndPoint: TPoint;
fStartArea: TRect;
fRatio: TRatio;
fSizeLimits: TSizeLimits;
fImageBitmap, fResampledBitmap, fBackground, fVirtualScreen: TBGRABitmap;
rNewCropAreaDefault: TBGRANewCropAreaDefault;
rOnContextPopup: TBGRAIMContextPopupEvent;
function getAnchorSize: byte;
function getPixelsPerInch: Integer;
procedure setAnchorSize(const Value: byte);
function getEmpty: boolean;
procedure setBitmap(const Value: TBGRABitmap);
procedure setBorderSize(const Value: byte);
procedure setAspectRatio(const Value: string);
procedure setEmptyImage(AValue: TBGRAEmptyImage);
procedure setKeepAspectRatio(const Value: boolean);
procedure setMinHeight(const Value: integer);
procedure setMinWidth(const Value: integer);
procedure setSelectedCropArea(AValue: TCropArea);
protected
{ Protected declarations }
rCropAreas :TCropAreaList;
rNewCropArea,
rSelectedCropArea :TCropArea;
rOnCropAreaAdded: TCropAreaEvent;
rOnCropAreaDeleted: TCropAreaEvent;
rOnCropAreaChanged: TCropAreaEvent;
rOnSelectedCropAreaChanged: TCropAreaEvent;
rOnCropAreaLoad: TCropAreaLoadEvent;
rOnCropAreaSave: TCropAreaSaveEvent;
rEmptyImage: TBGRAEmptyImage;
rLoading: Boolean;
function ApplyDimRestriction(Coords: TCoord; Direction: TDirection; Bounds: TRect; AKeepAspectRatio:Boolean): TCoord;
function ApplyRatioToAxes(Coords: TCoord; Direction: TDirection; Bounds: TRect; ACropArea :TCropArea = Nil): TCoord;
procedure ApplyRatioToArea(ACropArea :TCropArea);
procedure CalcMaxSelection(ACropArea :TCropArea);
procedure findSizeLimits;
function getDirection(const Point1, Point2: TPoint): TDirection;
function getImageRect(Picture: TBGRABitmap): TRect;
function getWorkRect: TRect;
function isOverAnchor(APoint :TPoint; var AnchorSelected :TDirection; var ACursor :TCursor) :TCropArea;
procedure CreateEmptyImage;
procedure CreateResampledBitmap;
procedure Loaded; override;
procedure Paint; override;
procedure RepaintBackground;
procedure Resize; override;
procedure Render;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: integer); override;
procedure MouseMove(Shift: TShiftState; X, Y: integer); override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: integer); override;
procedure DoContextPopup(MousePos: TPoint; var Handled: Boolean); override;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Invalidate; override;
function getAspectRatioFromImage(const Value: TBGRABitmap): string;
function getResampledBitmap(ACropArea :TCropArea = Nil; ACopyProperties: Boolean=False) : TBGRABitmap;
function getBitmap(ACropArea :TCropArea = Nil; ACopyProperties: Boolean=False) : TBGRABitmap;
procedure rotateLeft(ACopyProperties: Boolean=False);
procedure rotateRight(ACopyProperties: Boolean=False);
procedure tests;
//Crop Areas Manipulation functions
function addCropArea(AArea : TRectF; AAreaUnit: TResolutionUnit = ruNone;
AUserData: Integer = -1) :TCropArea;
function addScaledCropArea(AArea : TRect; AUserData: Integer = -1) :TCropArea;
procedure delCropArea(ACropArea :TCropArea);
procedure clearCropAreas;
procedure getAllResampledBitmaps(ACallBack :TgetAllBitmapsCallback; AUserData:Integer=0; ACopyProperties: Boolean=False);
procedure getAllBitmaps(ACallBack :TgetAllBitmapsCallback; AUserData:Integer=0; ACopyProperties: Boolean=False);
procedure SetEmptyImageSizeToCropAreas(ReduceLarger: Boolean=False);
procedure SetEmptyImageSizeToNull;
procedure SetEmptyImageSize(AResolutionUnit: TResolutionUnit; AResolutionWidth, AResolutionHeight: Single);
property SelectedCropArea :TCropArea read rSelectedCropArea write setSelectedCropArea;
property CropAreas :TCropAreaList read rCropAreas;
property PixelsPerInch: Integer read getPixelsPerInch;
published
{ Published declarations }
property Align;
property Anchors;
property AnchorSize: byte Read getAnchorSize Write setAnchorSize default 5;
property Bitmap: TBGRABitmap Read fImageBitmap Write setBitmap;
property BorderSize: byte Read fBorderSize Write setBorderSize default 2;
property AspectRatio: string Read fAspectRatio Write setAspectRatio;
property KeepAspectRatio: boolean Read fKeepAspectRatio Write setKeepAspectRatio default True;
property MinHeight: integer Read fMinHeight Write setMinHeight;
property MinWidth: integer Read fMinWidth Write setMinWidth;
property Empty: boolean Read getEmpty;
property EmptyImage: TBGRAEmptyImage read rEmptyImage write setEmptyImage stored True;
property NewCropAreaDefault: TBGRANewCropAreaDefault read rNewCropAreaDefault write rNewCropAreaDefault stored True;
//Events
property OnCropAreaAdded:TCropAreaEvent read rOnCropAreaAdded write rOnCropAreaAdded;
property OnCropAreaDeleted:TCropAreaEvent read rOnCropAreaDeleted write rOnCropAreaDeleted;
property OnCropAreaChanged:TCropAreaEvent read rOnCropAreaChanged write rOnCropAreaChanged;
property OnCropAreaLoad:TCropAreaLoadEvent read rOnCropAreaLoad write rOnCropAreaLoad;
property OnCropAreaSave:TCropAreaSaveEvent read rOnCropAreaSave write rOnCropAreaSave;
//CropArea Parameter is the Old Selected Area, use SelectedCropArea property for current
property OnSelectedCropAreaChanged:TCropAreaEvent read rOnSelectedCropAreaChanged write rOnSelectedCropAreaChanged;
property OnContextPopup: TBGRAIMContextPopupEvent read rOnContextPopup write rOnContextPopup;
(* property OnStartDrag: TStartDragEvent;
property OnDragDrop: TDragDropEvent;
property OnDragOver: TDragOverEvent;
property OnEndDrag: TEndDragEvent;*)
end;
function RoundUp(AValue:Single):Integer;
function ResolutionUnitConvert(const AValue:Single; fromRes, toRes:TResolutionUnit; predefInchRes:Integer=96):Single;
procedure PixelXResolutionUnitConvert(var resX, resY:Single; fromRes, toRes:TResolutionUnit);
{$IFDEF FPC}procedure Register;{$ENDIF}
implementation
uses Math, ExtCtrls;
resourcestring
SAnchorSizeIsTooLarge =
'Anchor size is too large. %d is not within the valid range of %d..%d';
SAnchorSizeIsTooSmall =
'Anchor size is too small. %d is not within the valid range of %d..%d';
SAnchorSizeIsNotOdd = 'Anchor size is invalid. %d is not an odd number.';
SBorderSizeIsTooLarge =
'Border size is too large. %d is not within the valid range of %d..%d';
SBorderSizeIsTooSmall =
'Border size is too small. %d is not within the valid range of %d..%d';
SAspectRatioIsNotValid = 'Aspect ratio value is invalid. %s contain invalid number.';
{ Calculate the Greatest Common Divisor (GCD) using the algorithm of Euclides }
function getGCD(Nr1, Nr2: longint): longint;
begin
if Nr2 = 0 then
Result := Nr1
else
Result := getGCD(Nr2, Nr1 mod Nr2);
end;
{ Calculate the Lowest Common Multiple (LCM) using the algorithm of Euclides }
function getLCM(Nr1, Nr2: longint): longint;
begin
Result := (Nr1 * Nr2) div getGCD(Nr1, Nr2);
end;
procedure CheckAspectRatio(const Value :String; var AspectRatioText :String; var XValue, YValue :Integer);
const
ValidChars = ['0'..'9', ':'];
var
Count :Integer;
begin
if ((pos(':', Value) > 0) and (pos(':', Value) < Length(Value))) then
begin
// Check if value is valid
XValue := 0;
YValue := 0;
AspectRatioText := '';
for Count := 1 to Length(Value) do
begin
if (Value[Count] in ValidChars) then
begin
if ((Value[Count] = ':') and (Length(AspectRatioText) > 0) and
(XValue = 0)) then
begin
XValue := StrToInt(AspectRatioText);
end;
AspectRatioText := AspectRatioText + Value[Count];
end
else
begin
// Value contain invalid characters
raise EInvalidArgument.CreateFmt(SAspectRatioIsNotValid, [Value]);
end;
end;
YValue := StrToInt(Copy(AspectRatioText, Pos(':', AspectRatioText) + 1,
Length(AspectRatioText)));
end
else
begin
// Value contain invalid characters
raise EInvalidArgument.CreateFmt(SAspectRatioIsNotValid, [Value]);
end;
end;
function RoundUp(AValue:Single):Integer;
var
oRoundMode :TFPURoundingMode;
begin
oRoundMode :=Math.GetRoundMode;
//Round to Upper Value
Math.SetRoundMode(rmUp);
Result :=Round(AValue);
Math.SetRoundMode(oRoundMode);
end;
function ResolutionUnitConvert(const AValue:Single; fromRes, toRes:TResolutionUnit; predefInchRes:Integer):Single;
begin
if (fromRes<>toRes)
then Case fromRes of
ruNone: begin
if toRes=ruPixelsPerInch
then Result :=AValue/predefInchRes //in
else Result :=(AValue/predefInchRes)*2.54; //cm
end;
ruPixelsPerInch :begin
if toRes=ruPixelsPerCentimeter
then Result :=AValue*2.54 //cm
else Result :=AValue*predefInchRes; //pixel
end;
ruPixelsPerCentimeter :begin
if toRes=ruPixelsPerInch
then Result :=AValue/2.54 //in
else Result :=(AValue/2.54)*predefInchRes;//cm
end;
end
else Result:=AValue;
end;
procedure PixelXResolutionUnitConvert(var resX, resY: Single; fromRes, toRes: TResolutionUnit);
begin
//Do Conversion from/to PixelXInch/PixelXCm
if (toRes <> fromRes) then
begin
if (toRes=ruPixelsPerInch)
then begin
resX :=resX*2.54;
resY :=resY*2.54;
end
else begin
resX :=resX/2.54;
resY :=resY/2.54;
end
end;
end;
{ TCropArea }
procedure TCropArea.Render_Refresh;
begin
if not(fOwner.rCropAreas.loading) then
begin
fOwner.Render;
fOwner.Refresh;
end;
end;
procedure TCropArea.GetImageResolution(var resX, resY: Single; var resUnit: TResolutionUnit);
begin
resX :=fOwner.fImageBitmap.ResolutionX;
resY :=fOwner.fImageBitmap.ResolutionY;
resUnit :=fOwner.fImageBitmap.ResolutionUnit;
if (resX<2) or (resY<2) then //Some images have 1x1 PixelPerInch ?
begin
//No Resolution use predefined Form Values
resUnit :=rAreaUnit;
if (rAreaUnit=ruPixelsPerInch)
then resX :=fOwner.PixelsPerInch
else resX :=fOwner.PixelsPerInch/2.54;
resY :=resX;
end;
end;
function TCropArea.getIsNullSize: Boolean;
begin
Result := not((abs(rArea.Right - rArea.Left) > 0) and (abs(rArea.Bottom - rArea.Top) > 0));
end;
procedure TCropArea.setName(AValue: String);
begin
if rName=AValue then Exit;
rName:=AValue;
if assigned(fOwner.rOnCropAreaChanged)
then fOwner.rOnCropAreaChanged(fOwner, Self);
end;
procedure TCropArea.setIcons(AValue: TCropAreaIcons);
begin
if rIcons=AValue then Exit;
rIcons:=AValue;
Render_Refresh;
end;
function TCropArea.getTop: Single;
begin
Result :=rArea.Top;
end;
procedure TCropArea.setTop(AValue: Single);
var
tempArea:TRectF;
begin
if AValue=rArea.Top then Exit;
tempArea :=rArea;
tempArea.Top:=AValue;
tempArea.Height:=rArea.Height;
//CheckAreaOutOfBounds(tempArea);
Area :=tempArea;
end;
function TCropArea.getLeft: Single;
begin
Result :=rArea.Left;
end;
procedure TCropArea.setLeft(AValue: Single);
var
tempArea:TRectF;
tempSArea:TRect;
begin
if AValue=rArea.Left then Exit;
tempArea :=rArea;
tempArea.Left:=AValue;
tempArea.Width:=rArea.Width;
//CheckAreaOutOfBounds(tempArea);
Area :=tempArea;
(* if CheckScaledOutOfBounds(rScaledArea)
then begin
CalculateAreaFromScaledArea;
if assigned(fOwner.rOnCropAreaChanged)
then fOwner.rOnCropAreaChanged(fOwner, Self);
end; *)
end;
function TCropArea.getHeight: Single;
begin
Result :=rArea.Height;
end;
procedure TCropArea.setHeight(AValue: Single);
var
tempArea:TRectF;
begin
if AValue=rArea.Height then Exit;
tempArea :=rArea;
tempArea.Height:=AValue;
//CheckAreaOutOfBounds(tempArea);
Area :=tempArea;
end;
function TCropArea.getWidth: Single;
begin
Result :=rArea.Width;
end;
procedure TCropArea.setWidth(AValue: Single);
var
tempArea:TRectF;
begin
if AValue=rArea.Width then Exit;
tempArea :=rArea;
tempArea.Width:=AValue;
//CheckAreaOutOfBounds(tempArea);
Area :=tempArea;
end;
function TCropArea.getMaxHeight: Single;
begin
if (rAreaUnit=ruNone)
then Result :=fOwner.fImageBitmap.Height
else begin
if (fOwner.fImageBitmap.ResolutionY<2)
then Result :=fOwner.fImageBitmap.Height //No Resolution, Some images have 1x1 PixelPerInch ?
else begin
Result :=fOwner.fImageBitmap.ResolutionHeight;
//Do Conversion from/to inch/cm
if (rAreaUnit <> fOwner.fImageBitmap.ResolutionUnit) then
begin
if (rAreaUnit=ruPixelsPerInch)
then Result :=Result/2.54 //Bitmap is in Cm, i'm in Inch
else Result :=Result*2.54; //Bitmap is in Inch, i'm in Cm
end;
end;
end;
end;
function TCropArea.getMaxWidth: Single;
begin
if (rAreaUnit=ruNone)
then Result :=fOwner.fImageBitmap.Width
else begin
if (fOwner.fImageBitmap.ResolutionX<2)
then Result :=fOwner.fImageBitmap.Width //No Resolution, Some images have 1x1 PixelPerInch ?
else begin
Result :=fOwner.fImageBitmap.ResolutionWidth;
//Do Conversion from/to inch/cm
if (rAreaUnit <> fOwner.fImageBitmap.ResolutionUnit) then
begin
if (rAreaUnit=ruPixelsPerInch)
then Result :=Result/2.54 //Bitmap is in Cm, i'm in Inch
else Result :=Result*2.54; //Bitmap is in Inch, i'm in Cm
end;
end;
end;
end;
function TCropArea.getIndex: Longint;
begin
Result :=fOwner.CropAreas.IndexOf(Self);
end;
procedure TCropArea.CalculateScaledAreaFromArea;
var
xRatio, yRatio: Single;
resX, resY: Single;
resUnit:TResolutionUnit;
begin
if not(isNullSize) then
begin
// Calculate Scaled Area given Scale and Resolution
if (fOwner.fImageBitmap.Width=0) or (fOwner.fImageBitmap.Height=0)
then begin
xRatio :=1;
yRatio :=1;
end
else begin
xRatio := fOwner.fResampledBitmap.Width / fOwner.fImageBitmap.Width;
yRatio := fOwner.fResampledBitmap.Height / fOwner.fImageBitmap.Height;
end;
resX :=1; //if rAreaUnit=ruNone use only Ratio
resY :=1;
if (rAreaUnit<>ruNone) then
begin
GetImageResolution(resX, resY, resUnit);
PixelXResolutionUnitConvert(resX, resY, resUnit, rAreaUnit);
end;
//MaxM: Use Trunc for Top/Left and Round for Right/Bottom so we
// preserve as much data as possible when do the crop
rScaledArea.Left := Trunc(rArea.Left * resX * xRatio);
rScaledArea.Top := Trunc(rArea.Top * resY * yRatio);
rScaledArea.Right := Round(rArea.Right* resX * xRatio);
rScaledArea.Bottom := Round(rArea.Bottom * resY * yRatio);
end;
end;
procedure TCropArea.CalculateAreaFromScaledArea;
var
xRatio, yRatio: Single;
resX, resY: Single;
resUnit:TResolutionUnit;
begin
// Calculate Scaled Area given Scale and Resolution
if (fOwner.fImageBitmap.Width=0) or (fOwner.fImageBitmap.Height=0)
then begin
xRatio :=1;
yRatio :=1;
end
else begin
xRatio := fOwner.fResampledBitmap.Width / fOwner.fImageBitmap.Width;
yRatio := fOwner.fResampledBitmap.Height / fOwner.fImageBitmap.Height;
end;
resX :=1; //if rAreaUnit=ruNone use only Ratio
resY :=1;
if (rAreaUnit<>ruNone) then
begin
GetImageResolution(resX, resY, resUnit);
PixelXResolutionUnitConvert(resX, resY, resUnit, rAreaUnit);
end;
rArea.Left := (rScaledArea.Left / resX) / xRatio;
rArea.Right := (rScaledArea.Right / resX) / xRatio;
rArea.Top := (rScaledArea.Top / resY) / yRatio;
rArea.Bottom := (rScaledArea.Bottom / resY) / yRatio;
end;
function TCropArea.GetPixelArea(const AValue: TRectF): TRect;
var
resX, resY: Single;
resUnit: TResolutionUnit;
begin
if (rAreaUnit=ruNone)
then begin
Result.Left := Trunc(AValue.Left);
Result.Right := Trunc(AValue.Right);
Result.Top := Trunc(AValue.Top);
Result.Bottom := Trunc(AValue.Bottom);
end
else begin
if (rAreaUnit=ruNone)
then begin
resX :=1;
resY :=1;
end
else GetImageResolution(resX, resY, resUnit);
PixelXResolutionUnitConvert(resX, resY, resUnit, rAreaUnit);
Result.Left := Trunc(AValue.Left * resX);
Result.Top := Trunc(AValue.Top * resY);
Result.Right := Round(AValue.Right* resX);
Result.Bottom := Round(AValue.Bottom * resY);
end;
end;
function TCropArea.CheckScaledOutOfBounds(var AArea: TRect): Boolean;
var
tmpValue: Integer;
begin
Result :=False;
if (AArea.Left<0)
then begin
tmpValue :=-AArea.Left;
AArea.Left :=0;
AArea.Right:=AArea.Right+tmpValue;
Result :=True;
end;
if (AArea.Top<0)
then begin
tmpValue :=-AArea.Top;
AArea.Top :=0;
AArea.Bottom:=AArea.Bottom+tmpValue;
Result :=True;
end;
if (AArea.Right>fOwner.fResampledBitmap.Width)
then begin
tmpValue :=AArea.Right-fOwner.fResampledBitmap.Width;
AArea.Right :=fOwner.fResampledBitmap.Width;
AArea.Left:=AArea.Left-tmpValue; //if <0 ? a vicious circle
Result :=True;
end;
if (AArea.Bottom>fOwner.fResampledBitmap.Height)
then begin
tmpValue :=AArea.Bottom-fOwner.fResampledBitmap.Height;
AArea.Bottom :=fOwner.fResampledBitmap.Height;
AArea.Top:=AArea.Top-tmpValue; //if <0 ? a vicious circle
Result :=True;
end;
end;
function TCropArea.CheckAreaOutOfBounds(var AArea: TRectF):Boolean;
var
tmpValue, resWH: Single;
begin
Result :=False;
if (AArea.Left<0)
then begin
tmpValue :=-AArea.Left;
AArea.Left :=0;
AArea.Right:=AArea.Right+tmpValue;
Result :=True;
end;
if (AArea.Top<0)
then begin
tmpValue :=-AArea.Top;
AArea.Top :=0;
AArea.Bottom:=AArea.Bottom+tmpValue;
Result :=True;
end;
resWH :=fOwner.fImageBitmap.ResolutionWidth;
if (AArea.Right>resWH)
then begin
tmpValue :=AArea.Right-resWH;
AArea.Right :=resWH;
AArea.Left:=AArea.Left-tmpValue; //if <0 ? a vicious circle
Result :=True;
end;
resWH :=fOwner.fImageBitmap.ResolutionHeight;
if (AArea.Bottom>resWH)
then begin
tmpValue :=AArea.Bottom-resWH;
AArea.Bottom :=resWH;
AArea.Top:=AArea.Top-tmpValue; //if <0 ? a vicious circle
Result :=True;
end;
end;
procedure TCropArea.CopyAspectFromParent;
begin
rAspectX :=fOwner.fAspectX;
rAspectY :=fOwner.fAspectY;
rMinHeight :=fOwner.fMinHeight;
rMinWidth :=fOwner.fMinWidth;
rAspectRatio:=fOwner.fAspectRatio;
rRatio :=fOwner.fRatio;
end;
procedure TCropArea.setAspectRatio(AValue: string);
var
XValue, YValue: integer;
AspectRatioText: string;
fGCD :integer;
begin
if (rKeepAspectRatio = bParent)
then CopyAspectFromParent
else begin
if (AValue <> rAspectRatio) then
begin
// Check if value contain a valid string
CheckAspectRatio(AValue, AspectRatioText, XValue, YValue);
// Set new Aspect Ratio
rAspectRatio := AspectRatioText;
rAspectX := XValue;
rAspectY := YValue;
// Calculate the ratio
fGCD := getGCD(rAspectX, rAspectY);
// Determine the ratio of scale per axle
with rRatio do
begin
Horizontal := rAspectX div fGCD;
Vertical := rAspectY div fGCD;
end;
// Set minimun size
if ((rRatio.Horizontal < fOwner.fAnchorSize + 10) or
(rRatio.Vertical < fOwner.fAnchorSize + 10)) then
begin
rMinWidth := rRatio.Horizontal * 10;
rMinHeight := rRatio.Vertical * 10;
end
else
begin
rMinWidth := rRatio.Horizontal;
rMinHeight := rRatio.Vertical;
end;
fOwner.ApplyRatioToArea(Self);
Render_Refresh;
end;
end;
end;
procedure TCropArea.setKeepAspectRatio(AValue: BoolParent);
begin
if rKeepAspectRatio=AValue then Exit;
rKeepAspectRatio :=AValue;
if (rKeepAspectRatio = bParent) then
begin
rAspectRatio :=fOwner.AspectRatio;
CopyAspectFromParent;
if (fOwner.KeepAspectRatio)
then fOwner.ApplyRatioToArea(Self);
end
else if (rKeepAspectRatio = bTrue)
then fOwner.ApplyRatioToArea(Self);
Render_Refresh;
end;
procedure TCropArea.setArea(AValue: TRectF);
var
curKeepAspectRatio :Boolean;
curRatio :TRatio;
calcHeight, calcWidth, swapV :Single;
begin
if (rArea.TopLeft = AValue.TopLeft) and
(rArea.BottomRight = AValue.BottomRight) then Exit;
if (AValue.Left > AValue.Right) then
begin
swapV :=AValue.Left;
AValue.Left :=AValue.Right;
AValue.Right:=swapV;
end;
if (AValue.Top > AValue.Bottom) then
begin
swapV :=AValue.Top;
AValue.Top :=AValue.Bottom;
AValue.Bottom:=swapV;
end;
if fOwner.fMouseCaught
then rArea:=AValue
else begin
curKeepAspectRatio :=getRealAspectRatio(curRatio);
if curKeepAspectRatio
then begin
calcWidth :=AValue.Width;
calcHeight :=AValue.Height;
//if the Width is Changed recalculate the Height
if (calcWidth <> rArea.Width)
then calcHeight :=Trunc(abs(calcWidth) * (curRatio.Vertical / curRatio.Horizontal))
else begin
//if the New Width is the same but the Height is Changed recalculate the New Width
if (calcHeight <> rArea.Height)
then calcWidth :=Trunc(abs(calcHeight) * (curRatio.Horizontal / curRatio.Vertical));
end;
rArea.Left:=AValue.Left;
rArea.Top:=AValue.Top;
rArea.Width:=calcWidth;
rArea.Height:=calcHeight;
end
else rArea:=AValue; //Free Aspect
CalculateScaledAreaFromArea;
Render_Refresh;
end;
if assigned(fOwner.rOnCropAreaChanged)
then fOwner.rOnCropAreaChanged(fOwner, Self);
end;
procedure TCropArea.setAreaUnit(AValue: TResolutionUnit);
var
imgResX, imgResY :Single;
begin
if rAreaUnit=AValue then Exit;
if not(Loading) and not(isNullSize) then
begin
//Get Image Resolution in Pixel/Inchs
Case fOwner.Bitmap.ResolutionUnit of
ruPixelsPerInch : begin
imgResX :=fOwner.Bitmap.ResolutionX;
imgResY :=fOwner.Bitmap.ResolutionY;
end;
ruPixelsPerCentimeter : begin
imgResX :=fOwner.Bitmap.ResolutionX*2.54;
imgResY :=fOwner.Bitmap.ResolutionY*2.54;
end;
ruNone : begin
//No Image Resolution, Use predefined Monitor Values
imgResX :=fOwner.PixelsPerInch;
imgResY :=fOwner.PixelsPerInch;
end;
end;
//Paranoid test to avoid zero divisions
if (imgResX=0) then imgResX :=fOwner.PixelsPerInch;
if (imgResY=0) then imgResY :=fOwner.PixelsPerInch;
Case rAreaUnit of
ruPixelsPerInch : begin
if (AValue=ruNone)
then begin //From Inchs to Pixels, we need Image Resolution
//MaxM: Use Trunc for Top/Left and Round for Right/Bottom so we
// preserve as much data as possible when do the crop
rArea.Left:=Trunc(rArea.Left*imgResX);
rArea.Top:=Trunc(rArea.Top*imgResY);
rArea.Right:=Round(rArea.Right*imgResX);
rArea.Bottom:=Round(rArea.Bottom*imgResY);
end
else begin //From Inchs to Cm
rArea.Left:=rArea.Left*2.54;
rArea.Top:=rArea.Top*2.54;
rArea.Right:=rArea.Right*2.54;
rArea.Bottom:=rArea.Bottom*2.54;
end;
end;
ruPixelsPerCentimeter : begin
if (AValue=ruNone)
then begin //From Cm to Pixels, first convert to Inchs than use Image Resolution
rArea.Left:=Trunc((rArea.Left/2.54)*imgResX);
rArea.Top:=Trunc((rArea.Top/2.54)*imgResY);
rArea.Right:=Round((rArea.Right/2.54)*imgResX);
rArea.Bottom:=Round((rArea.Bottom/2.54)*imgResY);
end
else begin //From Cm to Inchs
rArea.Left:=rArea.Left/2.54;
rArea.Top:=rArea.Top/2.54;
rArea.Right:=rArea.Right/2.54;
rArea.Bottom:=rArea.Bottom/2.54;
end;
end;
ruNone : begin
if (AValue=ruPixelsPerInch)
then begin //From Pixels to Inchs
rArea.Left:=rArea.Left/imgResX;
rArea.Top:=rArea.Top/imgResY;
rArea.Right:=rArea.Right/imgResX;
rArea.Bottom:=rArea.Bottom/imgResY;
end
else begin
rArea.Left:=(rArea.Left/2.54)/imgResX;
rArea.Top:=(rArea.Top/2.54)/imgResY;
rArea.Right:=(rArea.Right/2.54)/imgResX;
rArea.Bottom:=(rArea.Bottom/2.54)/imgResY;
end;
end;
end;
end;
rAreaUnit:=AValue;
if assigned(fOwner.rOnCropAreaChanged)
then fOwner.rOnCropAreaChanged(fOwner, Self);
end;
procedure TCropArea.setScaledArea(AValue: TRect);
var
curKeepAspectRatio :Boolean;
curRatio :TRatio;
calcHeight, calcWidth, swapV :Longint;
begin
if rScaledArea=AValue then Exit;
if (AValue.Left > AValue.Right) then
begin
swapV :=AValue.Left;
AValue.Left :=AValue.Right;
AValue.Right:=swapV;
end;
if (AValue.Top > AValue.Bottom) then
begin
swapV :=AValue.Top;
AValue.Top :=AValue.Bottom;
AValue.Bottom:=swapV;
end;
if fOwner.fMouseCaught
then rScaledArea:=AValue
else begin
curKeepAspectRatio :=getRealAspectRatio(curRatio);
if curKeepAspectRatio
then begin
calcWidth :=AValue.Width;
calcHeight :=AValue.Height;
//if the Width is Changed recalculate the Height
if (calcWidth <> rScaledArea.Width)
then calcHeight :=Trunc(abs(calcWidth) * (curRatio.Vertical / curRatio.Horizontal))
else begin
//if the New Width is the same but the Height is Changed recalculate the New Width
if (calcHeight <> rScaledArea.Height)
then calcWidth :=Trunc(abs(calcHeight) * (curRatio.Horizontal / curRatio.Vertical));
end;
rScaledArea.Left:=AValue.Left;
rScaledArea.Top:=AValue.Top;
rScaledArea.Width:=calcWidth;
rScaledArea.Height:=calcHeight;
end
else rScaledArea:=AValue; //Free Aspect
CalculateAreaFromScaledArea;
Render_Refresh;
end;
if assigned(fOwner.rOnCropAreaChanged)
then fOwner.rOnCropAreaChanged(fOwner, Self);
end;
function TCropArea.getRealAspectRatio(var ARatio: TRatio): Boolean;
begin
Case rKeepAspectRatio of
bParent : begin
Result :=fOwner.fKeepAspectRatio;
ARatio :=fOwner.fRatio;
end;
bTrue : begin
Result :=True;
ARatio :=Self.rRatio;
end;
bFalse : Result :=False;
end;
end;
function TCropArea.getRealKeepAspectRatio: Boolean;
begin
Case rKeepAspectRatio of
bParent : Result :=fOwner.fKeepAspectRatio;
bTrue : Result :=True;
bFalse : Result :=False;
end;
end;
//Get Resampled Bitmap (Scaled to current scale)
function TCropArea.getResampledBitmap(ACopyProperties: Boolean=False): TBGRABitmap;
var
ResampledBitmap: TBGRACustomBitmap;
CropBitmap: TBGRABitmap;
begin
Result :=nil;
if not (fOwner.fImageBitmap.Empty) then
try
try
// Create a new bitmap for cropped region in original scale
CropBitmap := getBitmap(ACopyProperties);
// Create bitmap to put image on final scale
Result := TBGRABitmap.Create(rScaledArea.Width, rScaledArea.Height);
// Resize the cropped image to final scale
ResampledBitmap := CropBitmap.Resample(rScaledArea.Width, rScaledArea.Height, rmFineResample, ACopyProperties);
Result.BlendImage(0, 0, ResampledBitmap, boLinearBlend);
finally
ResampledBitmap.Free;
CropBitmap.Free;
end;
except
if (Result<>nil)
then FreeAndNil(Result);
end;
end;
//Get Original size Bitmap (not scaled to current scale)
function TCropArea.getBitmap(ACopyProperties: Boolean=False): TBGRABitmap;
begin
Result :=nil;
if not (fOwner.fImageBitmap.Empty) then
try
// Get the cropped image on selected region in original scale
Result :=fOwner.fImageBitmap.GetPart(GetPixelArea(rArea), ACopyProperties);
except
if (Result<>nil)
then FreeAndNil(Result);
end;
end;
constructor TCropArea.Create(AOwner: TBGRAImageManipulation; AArea: TRectF;
AAreaUnit: TResolutionUnit; AUserData: Integer);
begin
inherited Create;
if (AOwner = Nil)
then raise Exception.Create('TCropArea Owner is Nil');
OwnerList :=nil;
fOwner :=AOwner;
rAreaUnit :=AAreaUnit;
Area := AArea;
UserData :=AUserData;
rAspectX :=3;
rAspectY :=4;
rKeepAspectRatio :=bParent;
Loading:=False;
CopyAspectFromParent;
end;
constructor TCropArea.Create(AOwner: TBGRAImageManipulation;
DuplicateFrom: TCropArea; InsertInList:Boolean);
begin
if (DuplicateFrom = Nil)
then raise Exception.Create('TCropArea DuplicateFrom is Nil');
Create(AOwner, DuplicateFrom.Area, DuplicateFrom.AreaUnit, DuplicateFrom.UserData);
OwnerList :=nil;
rAspectX :=DuplicateFrom.rAspectX;
rAspectY :=DuplicateFrom.rAspectY;
rKeepAspectRatio :=DuplicateFrom.rKeepAspectRatio;
Loading:=False;
if rKeepAspectRatio=bParent
then CopyAspectFromParent;
if InsertInList and (DuplicateFrom.OwnerList<>nil)
then DuplicateFrom.OwnerList.add(Self);
end;
destructor TCropArea.Destroy;
begin
inherited Destroy;
end;
procedure TCropArea.BringToFront;
begin
if (OwnerList<>nil) then
try
OwnerList.Move(OwnerList.IndexOf(Self), OwnerList.Count-1);
except
end;
end;
procedure TCropArea.BringToBack;
begin
if (OwnerList<>nil) then
try
OwnerList.Move(OwnerList.IndexOf(Self), 0);
except
end;
end;
procedure TCropArea.BringForward;
var
curIndex :Integer;
begin
if (OwnerList<>nil) then
try
curIndex :=OwnerList.IndexOf(Self);
if (curIndex<OwnerList.Count-1)
then OwnerList.Move(curIndex, curIndex+1);
except
end;
end;
procedure TCropArea.BringBackward;
var
curIndex :Integer;
begin
if (OwnerList<>nil) then
try
curIndex :=OwnerList.IndexOf(Self);
if (curIndex>0)
then OwnerList.Move(curIndex, curIndex-1);
except
end;
end;
procedure TCropArea.RotateLeft;
var
newArea :TRect;
begin
newArea.Right :=rScaledArea.Left;
newArea.Bottom:=rScaledArea.Bottom;
newArea.Left:=newArea.Right-rScaledArea.Height;
newArea.Top:=newArea.Bottom-rScaledArea.Width;
CheckScaledOutOfBounds(newArea);
ScaledArea :=newArea;
end;
procedure TCropArea.RotateRight;
var
newArea :TRect;
begin
newArea.Left :=rScaledArea.Right;
newArea.Bottom:=rScaledArea.Bottom;
newArea.Right:=newArea.Left+rScaledArea.Height;
newArea.Top:=newArea.Bottom-rScaledArea.Width;
CheckScaledOutOfBounds(newArea);
ScaledArea :=newArea;
end;
procedure TCropArea.FlipHLeft;
var
newArea :TRect;
begin
newArea.Top:=rScaledArea.Top;
newArea.Bottom:=rScaledArea.Bottom;
newArea.Right :=rScaledArea.Left;
newArea.Left:=newArea.Right-rScaledArea.Width;
CheckScaledOutOfBounds(newArea);
ScaledArea :=newArea;
end;
procedure TCropArea.FlipHRight;
var
newArea :TRect;
begin
newArea.Top:=rScaledArea.Top;
newArea.Bottom:=rScaledArea.Bottom;
newArea.Left :=rScaledArea.Right;
newArea.Right:=newArea.Left+rScaledArea.Width;
CheckScaledOutOfBounds(newArea);
ScaledArea :=newArea;
end;
procedure TCropArea.FlipVUp;
var
newArea :TRect;
begin
newArea.Left:=rScaledArea.Left;
newArea.Right:=rScaledArea.Right;
newArea.Bottom :=rScaledArea.Top;
newArea.Top:=newArea.Bottom-rScaledArea.Height;
CheckScaledOutOfBounds(newArea);
ScaledArea :=newArea;
end;
procedure TCropArea.FlipVDown;
var
newArea :TRect;
begin
newArea.Left:=rScaledArea.Left;
newArea.Right:=rScaledArea.Right;
newArea.Top :=rScaledArea.Bottom;
newArea.Bottom:=newArea.Top+rScaledArea.Height;
CheckScaledOutOfBounds(newArea);
ScaledArea :=newArea;
end;
procedure TCropArea.SetSize(AWidth, AHeight: Single);
var
tempArea:TRectF;
begin
if (AWidth=rArea.Width) and (AHeight=rArea.Height)
then exit;
tempArea :=rArea;
tempArea.Width:=AWidth;
tempArea.Height:=AHeight;
//CheckAreaOutOfBounds(tempArea);
Area :=tempArea;
end;
{ TCropAreaList }
procedure TCropAreaList.setLoading(AValue: Boolean);
var
i :Integer;
begin
for i :=0 to Count-1 do items[i].Loading :=AValue;
rLoading:=AValue;
end;
function TCropAreaList.getCropArea(aIndex: Integer): TCropArea;
begin
Result := inherited Items[aIndex] as TCropArea;
end;
procedure TCropAreaList.setCropArea(aIndex: Integer; const Value: TCropArea);
begin
inherited Items[aIndex] := Value;
end;
procedure TCropAreaList.Notify(Ptr: Pointer; Action: TListNotification);
begin
Case Action of
lnAdded: begin
TCropArea(Ptr).OwnerList :=Self;
if assigned(fOwner.rOnCropAreaAdded)
then fOwner.rOnCropAreaAdded(fOwner, Ptr);
end;
lnDeleted: begin
TCropArea(Ptr).OwnerList :=Nil;
if assigned(fOwner.rOnCropAreaDeleted)
then fOwner.rOnCropAreaDeleted(fOwner, Ptr);
end;
end;
inherited Notify(Ptr, Action);
end;
constructor TCropAreaList.Create(AOwner: TBGRAImageManipulation);
begin
inherited Create;
if (AOwner = Nil)
then raise Exception.Create('Owner TBGRAImageManipulation is Nil');
fOwner :=AOwner;
rName :=Self.ClassName;
loading :=False;
end;
function TCropAreaList.add(aCropArea: TCropArea): integer;
begin
Result := inherited Add(aCropArea);
end;
procedure TCropAreaList.Load(const XMLConf: TXMLConfig; XMLPath: String);
var
i, newCount, newSelected: integer;
curItemPath, curPath: String;
newCropArea: TCropArea;
newArea: TRectF;
newAreaUnit:TResolutionUnit;
begin
try
if (XMLPath='')
then curPath :=fOwner.Name+'.'+Self.Name+'/'
else curPath :=XMLPath+'/';
newCount := XMLConf.GetValue(curPath+'Count', -1);
if (newCount=-1)
then raise Exception.Create('XML Path not Found - '+curPath+'Count');
Clear;
Loading :=True;
newSelected := XMLConf.GetValue(curPath+'Selected', 0);
for i :=0 to newCount-1 do
begin
curItemPath :=curPath+'Item' + IntToStr(i)+'/';
newArea :=RectF(0,0,0,0);
//Area
newArea.Left :=StrToFloat(XMLConf.GetValue(curItemPath+'Area/Left', '0'));
newArea.Top :=StrToFloat(XMLConf.GetValue(curItemPath+'Area/Top', '0'));
newArea.Width :=StrToFloat(XMLConf.GetValue(curItemPath+'Area/Width', IntToStr(fOwner.MinWidth)));
newArea.Height :=StrToFloat(XMLConf.GetValue(curItemPath+'Area/Height', IntToStr(fOwner.MinHeight)));
newAreaUnit :=TResolutionUnit(XMLConf.GetValue(curItemPath+'AreaUnit', 0));
newCropArea :=TCropArea.Create(Self.fOwner, newArea, newAreaUnit);
newCropArea.Loading:=True;
newCropArea.Name :=XMLConf.GetValue(curItemPath+'Name', 'Name '+IntToStr(i));
newCropArea.KeepAspectRatio :=BoolParent(XMLConf.GetValue(curItemPath+'KeepAspectRatio', Integer(bParent)));
newCropArea.AspectRatio :=XMLConf.GetValue(curItemPath+'AspectRatio', '3:4');
newCropArea.Rotate :=StrToFloat(XMLConf.GetValue(curItemPath+'Rotate', '0'));
newCropArea.UserData :=XMLConf.GetValue(curItemPath+'UserData', -1);
if assigned(fOwner.rOnCropAreaLoad)
then newCropArea.UserData :=fOwner.rOnCropAreaLoad(fOwner, newCropArea, XMLConf, curItemPath);
newCropArea.Loading:=False;
add(newCropArea);
end;
if (newSelected<newCount)
then fOwner.SelectedCropArea :=items[newSelected]
else fOwner.SelectedCropArea :=items[0];
finally
loading :=False;
fOwner.Render;
fOwner.Refresh;
end;
end;
procedure TCropAreaList.Save(const XMLConf: TXMLConfig; XMLPath: String);
var
i: integer;
curItemPath, curPath: String;
curCropArea: TCropArea;
begin
if (XMLPath='')
then curPath :=fOwner.Name+'.'+Self.Name+'/'
else curPath :=XMLPath+'/';
XMLConf.DeletePath(curPath);
XMLConf.SetValue(curPath+'Count', Count);
XMLConf.SetValue(curPath+'Selected', fOwner.SelectedCropArea.Index);
for i :=0 to Count-1 do
begin
curItemPath :=curPath+'Item' + IntToStr(i)+'/';
curCropArea:=Items[i];
XMLConf.SetValue(curItemPath+'Name', curCropArea.Name);
XMLConf.SetValue(curItemPath+'KeepAspectRatio', Integer(curCropArea.KeepAspectRatio));
XMLConf.SetValue(curItemPath+'AspectRatio', curCropArea.AspectRatio);
XMLConf.SetValue(curItemPath+'Rotate', FloatToStr(curCropArea.Rotate));
XMLConf.SetValue(curItemPath+'AreaUnit', Integer(curCropArea.AreaUnit));
XMLConf.SetValue(curItemPath+'UserData', curCropArea.UserData);
//Area
XMLConf.SetValue(curItemPath+'Area/Left', FloatToStr(curCropArea.Area.Left));
XMLConf.SetValue(curItemPath+'Area/Top', FloatToStr(curCropArea.Area.Top));
XMLConf.SetValue(curItemPath+'Area/Width', FloatToStr(curCropArea.Area.Width));
XMLConf.SetValue(curItemPath+'Area/Height', FloatToStr(curCropArea.Area.Height));
if assigned(fOwner.rOnCropAreaSave)
then fOwner.rOnCropAreaSave(fOwner, curCropArea, XMLConf, curItemPath);
end;
end;
procedure TCropAreaList.LoadFromStream(Stream: TStream; XMLPath: String);
var
FXMLConf: TXMLConfig;
begin
try
FXMLConf := TXMLConfig.Create(nil);
{$IFDEF USE_Laz2_XMLCfg}
FXMLConf.ReadFromStream(Stream);
{$ELSE}
FXMLConf.ReadOnly:=True;
FXMLConf.LoadFromStream(Stream);
{$ENDIF}
Load(FXMLConf, XMLPath);
finally
FXMLConf.Free;
end;
end;
procedure TCropAreaList.LoadFromFile(const FileName: String; XMLPath: String);
var
FXMLConf: TXMLConfig;
begin
try
{$IFDEF USE_Laz2_XMLCfg}
FXMLConf := TXMLConfig.Create(FileName);
{$ELSE}
FXMLConf := TXMLConfig.Create(nil);
FXMLConf.ReadOnly:=True;
FXMLConf.LoadFromFile(FileName);
{$ENDIF}
Load(FXMLConf, XMLPath);
finally
FXMLConf.Free;
end;
end;
procedure TCropAreaList.SaveToStream(Stream: TStream; XMLPath: String);
var
FXMLConf: TXMLConfig;
begin
try
FXMLConf := TXMLConfig.Create(nil);
Save(FXMLConf, XMLPath);
{$IFDEF USE_Laz2_XMLCfg}
FXMLConf.WriteToStream(Stream);
{$ELSE}
FXMLConf.SaveToStream(Stream);
{$ENDIF}
finally
FXMLConf.Free;
end;
end;
procedure TCropAreaList.SaveToFile(const FileName: String; XMLPath: String);
var
FXMLConf: TXMLConfig;
begin
try
{$IFDEF USE_Laz2_XMLCfg}
FXMLConf := TXMLConfig.Create(FileName);
Save(FXMLConf, XMLPath);
FXMLConf.Flush;
{$ELSE}
FXMLConf := TXMLConfig.Create(nil);
Save(FXMLConf, XMLPath);
FXMLConf.SaveToFile(FileName);
{$ENDIF}
finally
FXMLConf.Free;
end;
end;
procedure TCropAreaList.RotateLeft;
var
i :Integer;
begin
for i:=0 to Count-1 do Items[i].RotateLeft;
end;
procedure TCropAreaList.RotateRight;
var
i :Integer;
begin
for i:=0 to Count-1 do Items[i].RotateRight;
end;
procedure TCropAreaList.FlipHLeft;
var
i :Integer;
begin
for i:=0 to Count-1 do Items[i].FlipHLeft;
end;
procedure TCropAreaList.FlipHRight;
var
i :Integer;
begin
for i:=0 to Count-1 do Items[i].FlipHRight;
end;
procedure TCropAreaList.FlipVUp;
var
i :Integer;
begin
for i:=0 to Count-1 do Items[i].FlipVUp;
end;
procedure TCropAreaList.FlipVDown;
var
i :Integer;
begin
for i:=0 to Count-1 do Items[i].FlipVDown;
end;
{ TBGRAEmptyImage }
function TBGRAEmptyImage.getHeight: Integer;
var
wRect: TRect;
begin
if (rResolutionHeight<=0) or (rResolutionWidth<=0)
then begin
//wRect := fOwner.getWorkRect;
wRect := fOwner.GetClientRect;
InflateRect(wRect, -fOwner.BorderSize, -fOwner.BorderSize);
Result := wRect.Bottom-wRect.Top;
end
else Case rResolutionUnit of
ruNone : Result :=Trunc(rResolutionHeight);
ruPixelsPerInch : Result :=Round(fOwner.PixelsPerInch*rResolutionHeight);
ruPixelsPerCentimeter : Result :=Round((fOwner.PixelsPerInch/2.54)*rResolutionHeight);
end;
end;
function TBGRAEmptyImage.getWidth: Integer;
var
wRect: TRect;
begin
if (rResolutionWidth<=0) or (rResolutionHeight<=0)
then begin
//wRect := fOwner.getWorkRect;
wRect := fOwner.GetClientRect;
InflateRect(wRect, -fOwner.BorderSize, -fOwner.BorderSize);
Result := wRect.Right-wRect.Left;
end
else Case rResolutionUnit of
ruNone : Result :=Trunc(rResolutionWidth);
ruPixelsPerInch : Result :=Round(fOwner.PixelsPerInch*rResolutionWidth);
ruPixelsPerCentimeter : Result :=Round((fOwner.PixelsPerInch/2.54)*rResolutionWidth);
end;
end;
procedure TBGRAEmptyImage.SetResolutionUnit(AValue: TResolutionUnit);
begin
if (AValue<>rResolutionUnit) then
begin
rResolutionWidth :=ResolutionUnitConvert(rResolutionWidth, rResolutionUnit, AValue, fOwner.PixelsPerInch);
rResolutionHeight :=ResolutionUnitConvert(rResolutionHeight, rResolutionUnit, AValue, fOwner.PixelsPerInch);
rResolutionUnit :=AValue;
end;
end;
constructor TBGRAEmptyImage.Create(AOwner: TBGRAImageManipulation);
begin
inherited Create;
fOwner :=AOwner;
rAllow :=False;
rShowBorder :=False;
rResolutionUnit:=ruPixelsPerCentimeter;
end;
{ TBGRANewCropAreaDefault }
constructor TBGRANewCropAreaDefault.Create(AOwner: TBGRAImageManipulation);
begin
inherited Create;
fOwner :=AOwner;
rKeepAspectRatio:=bFalse;
rAspectRatio:='3:4';
rResolutionUnit:=ruPixelsPerCentimeter;
end;
{ TBGRAImageManipulation }
{ ============================================================================ }
{ =====[ Auxiliary Functions ]================================================ }
{ ============================================================================ }
{ Applies the given size constraint on the coordinates along both axes }
function TBGRAImageManipulation.ApplyDimRestriction(Coords: TCoord;
Direction: TDirection; Bounds: TRect; AKeepAspectRatio:Boolean): TCoord;
var
newCoords: TCoord;
calcWidth, calcHeight: integer;
recalculateHeight: boolean;
begin
// Gets coordinates
newCoords := Coords;
recalculateHeight := False;
// Calculated width
calcWidth := abs(newCoords.x2 - newCoords.x1);
calcHeight := abs(newCoords.y2 - newCoords.y1);
// Checks if the width is smaller than the minimum value
if (Abs(calcWidth) < MinWidth) and (MinWidth < fImageBitmap.Width) then
begin
// Resizes the width based on the minimum value
calcWidth := MinWidth;
if (EAST in Direction) then
begin
// If the motion is in a positive direction, make sure we're not going out
// of bounds
if ((newCoords.x1 + calcWidth) > Bounds.Right) then
begin
// Moves the horizontal coordinates
newCoords.x1 := Bounds.Right - calcWidth;
newCoords.x2 := Bounds.Right;
end
else
begin
// Moves the last horizontal coordinate
newCoords.x2 := newCoords.x1 + calcWidth;
end;
end
else
begin
// If the motion is in a negative direction, make sure we're not going out
// of bounds
if ((newCoords.x1 - calcWidth) < Bounds.Left) then
begin
// Moves the horizontal coordinates
newCoords.x1 := Bounds.Left + calcWidth;
newCoords.x2 := Bounds.Left;
end
else
begin
// Moves the last horizontal coordinate
newCoords.x2 := newCoords.x1 - calcWidth;
end;
end;
if (AKeepAspectRatio) then
begin
// Resizes the height based on the minimum value
recalculateHeight := True;
end;
end;
// Checks if the height is smaller than the minimum value
if (((Abs(calcHeight) < MinHeight) and (MinHeight < fImageBitmap.Height)) or
recalculateHeight) then
begin
// Resizes the height based on the minimum value
calcHeight := MinHeight;
if (SOUTH in Direction) then
begin
// If the motion is in a positive direction, make sure we're not going out
// of bounds
if ((newCoords.y1 + calcHeight) > Bounds.Bottom) then
begin
// Moves the vertical coordinates
newCoords.y1 := Bounds.Bottom - calcHeight;
newCoords.y2 := Bounds.Bottom;
end
else
begin
// Moves the last horizontal coordinate
newCoords.y2 := newCoords.y1 + calcHeight;
end;
end
else
begin
// If the motion is in a negative direction, make sure we're not going out
// of bounds
if ((newCoords.y1 - calcHeight) < Bounds.Top) then
begin
// Moves the vertical coordinates
newCoords.y1 := Bounds.Top + calcHeight;
newCoords.y2 := Bounds.Top;
end
else
begin
// Moves the last horizontal coordinate
newCoords.y2 := newCoords.y1 - calcHeight;
end;
end;
end;
Result := newCoords;
end;
{ Applies the provided ratio to the coordinates based on direction and bounds }
{ on both axes. }
function TBGRAImageManipulation.ApplyRatioToAxes(Coords: TCoord;
Direction: TDirection; Bounds: TRect; ACropArea :TCropArea = Nil): TCoord;
var
newCoords: TCoord;
calcWidth, calcHeight: integer;
RecalculatesOtherAxis,
curKeepAspectRatio :Boolean;
curRatio :TRatio;
begin
// Gets coordinates
newCoords := Coords;
if (ACropArea<>nil)
then curKeepAspectRatio :=ACropArea.getRealAspectRatio(curRatio)
else begin
curKeepAspectRatio :=Self.fKeepAspectRatio;
curRatio :=Self.fRatio;
end;
// Check if movement is only vertical
if ((fAnchorSelected = [NORTH]) or (fAnchorSelected = [SOUTH])) then
begin
// Vertical movement: keep current width
if (curKeepAspectRatio) then
begin
// Calculate height
calcHeight := newCoords.y2 - newCoords.y1;
// Make sure we're not going out of bounds
if (SOUTH in Direction) then
begin
if ((newCoords.y1 + calcHeight) > Bounds.Bottom) then
begin
calcHeight := Bounds.Bottom - newCoords.y1; // Limite height dimension
newCoords.y2 := Bounds.Bottom;
end;
end
else
begin
if ((newCoords.y1 + calcHeight) < Bounds.Top) then
begin
calcHeight := -(newCoords.y1 - Bounds.Top); // Limite height dimension
newCoords.y2 := Bounds.Top;
end;
end;
// Calculate the new width based on the proportion of height
calcWidth := Trunc(abs(calcHeight) * (curRatio.Horizontal / curRatio.Vertical));
// Make sure we're not going out of bounds
if (fAnchorSelected = [NORTH]) then
begin
if ((newCoords.x1 - calcWidth) < Bounds.Left) then
begin
calcWidth := newCoords.x1 - Bounds.Left; // Limite width dimension
newCoords.x2 := Bounds.Left;
RecalculatesOtherAxis := True;
end;
end
else
begin
if ((newCoords.x1 + calcWidth) > Bounds.Right) then
begin
calcWidth := Bounds.Right - newCoords.x1; // Limite width dimension
newCoords.x2 := Bounds.Right;
RecalculatesOtherAxis := True;
end;
end;
// Apply calculated dimensions of width on height
if {%H-}(RecalculatesOtherAxis) then
begin
if (calcHeight > 0) then
calcHeight := Trunc(calcWidth * (curRatio.Vertical / curRatio.Horizontal))
else
calcHeight := -Trunc(calcWidth * (curRatio.Vertical / curRatio.Horizontal));
newCoords.y2 := newCoords.y1 + calcHeight;
end;
end
else
begin
// Calculate height
calcHeight := newCoords.y2 - newCoords.y1;
// Make sure we're not going out of bounds
if (SOUTH in Direction) then
begin
if ((newCoords.y1 + calcHeight) > Bounds.Bottom) then
begin
calcHeight := Bounds.Bottom - newCoords.y1; // Limite height dimension
newCoords.y2 := Bounds.Bottom;
end;
end
else
begin
if ((newCoords.y1 + calcHeight) < Bounds.Top) then
begin
calcHeight := -(newCoords.y1 - Bounds.Top); // Limite height dimension
newCoords.y2 := Bounds.Top;
end;
end;
// Calculate width
if (ACropArea <> Nil)
then calcWidth := abs(ACropArea.ScaledArea.Right - ACropArea.ScaledArea.Left)
else calcWidth := 16; //Check
end;
if (fAnchorSelected = [NORTH]) then
newCoords.x2 := newCoords.x1 - calcWidth
else
newCoords.x2 := newCoords.x1 + calcWidth;
end
else
// Check if movement is only horizontal
if ((fAnchorSelected = [EAST]) or (fAnchorSelected = [WEST])) then
begin
// Horizontal movement: keep current height
if (curKeepAspectRatio) then
begin
// Calculate width
calcWidth := newCoords.x2 - newCoords.x1;
// Make sure we're not going out of bounds
if (EAST in Direction) then
begin
if ((newCoords.x1 + calcWidth) > Bounds.Right) then
begin
calcWidth := Bounds.Right - newCoords.x1; // Limite width dimension
newCoords.x2 := Bounds.Right;
end;
end;
if (WEST in Direction) then
begin
if ((newCoords.x1 + calcWidth) < Bounds.Left) then
begin
calcWidth := -(newCoords.x1 - Bounds.Left); // Limite width dimension
newCoords.x2 := Bounds.Left;
end;
end;
// Calculate the new height based on the proportion of width
calcHeight := Trunc(abs(calcWidth) * (curRatio.Vertical / curRatio.Horizontal));
// Make sure we're not going out of bounds
if (fAnchorSelected = [WEST]) then
begin
if ((newCoords.y1 - calcHeight) < Bounds.Top) then
begin
calcHeight := newCoords.y1 - Bounds.Top; // Limite height dimension
newCoords.y2 := Bounds.Top;
RecalculatesOtherAxis := True;
end;
end
else
begin
if ((newCoords.y1 + calcHeight) > Bounds.Bottom) then
begin
calcHeight := Bounds.Bottom - newCoords.y1; // Limite height dimension
newCoords.y2 := Bounds.Bottom;
RecalculatesOtherAxis := True;
end;
end;
// Apply calculated dimensions of height on width
if (RecalculatesOtherAxis) then
begin
if (calcWidth > 0) then
calcWidth := Trunc(calcHeight * (curRatio.Horizontal / curRatio.Vertical))
else
calcWidth := -Trunc(calcHeight * (curRatio.Horizontal / curRatio.Vertical));
newCoords.x2 := newCoords.x1 + calcWidth;
end;
end
else
begin
// Calculate width
calcWidth := newCoords.x2 - newCoords.x1;
// Make sure we're not going out of bounds
if (EAST in Direction) then
begin
if ((newCoords.x1 + calcWidth) > Bounds.Right) then
begin
calcWidth := Bounds.Right - newCoords.x1; // Limite width dimension
newCoords.x2 := Bounds.Right;
end;
end;
if (WEST in Direction) then
begin
if ((newCoords.x1 + calcWidth) < Bounds.Left) then
begin
calcWidth := -(newCoords.x1 - Bounds.Left); // Limite width dimension
newCoords.x2 := Bounds.Left;
end;
end;
// Calculate height
if (ACropArea <> Nil)
then calcHeight := abs(ACropArea.ScaledArea.Bottom - ACropArea.ScaledArea.Top)
else calcHeight := 16; //Check
end;
if (fAnchorSelected = [WEST]) then
newCoords.y2 := newCoords.y1 - calcHeight
else
newCoords.y2 := newCoords.y1 + calcHeight;
end
else
begin
// Diagonal movement
if (curKeepAspectRatio) then
begin
// Calculate width
calcWidth := newCoords.x2 - newCoords.x1;
// Make sure we're not going out of bounds
if (EAST in Direction) then
begin
if ((newCoords.x1 + calcWidth) > Bounds.Right) then
begin
calcWidth := Bounds.Right - newCoords.x1; // Limite width dimension
newCoords.x2 := Bounds.Right;
end;
end;
if (WEST in Direction) then
begin
if ((newCoords.x1 + calcWidth) < Bounds.Left) then
begin
calcWidth := -(newCoords.x1 - Bounds.Left); // Limite width dimension
newCoords.x2 := Bounds.Left;
end;
end;
// Calculate the new height based on the proportion of width
if ((newCoords.y2 - newCoords.y1) > 0) then
calcHeight := Trunc(abs(calcWidth) * (curRatio.Vertical / curRatio.Horizontal))
else
calcHeight := -Trunc(abs(calcWidth) * (curRatio.Vertical / curRatio.Horizontal));
// Make sure we're not going out of bounds
if (calcHeight > 0) then
begin
if (SOUTH in Direction) then
begin
if ((newCoords.y1 + calcHeight) > Bounds.Bottom) then
begin
calcHeight := Bounds.Bottom - newCoords.y1; // Limite height dimension
newCoords.y2 := Bounds.Bottom;
RecalculatesOtherAxis := True;
end;
end
else
begin
if ((newCoords.y1 - calcHeight) < Bounds.Top) then
begin
calcHeight := newCoords.y1 - Bounds.Top; // Limite height dimension
newCoords.y2 := Bounds.Top;
RecalculatesOtherAxis := True;
end;
end;
end
else
begin
if (SOUTH in Direction) then
begin
if ((newCoords.y1 - calcHeight) > Bounds.Bottom) then
begin
calcHeight := newCoords.y1 - Bounds.Bottom; // Limite height dimension
newCoords.y2 := Bounds.Bottom;
RecalculatesOtherAxis := True;
end;
end
else
begin
if ((newCoords.y1 + calcHeight) < Bounds.Top) then
begin
calcHeight := Bounds.Top - newCoords.y1; // Limite height dimension
newCoords.y2 := Bounds.Top;
RecalculatesOtherAxis := True;
end;
end;
end;
// Apply calculated dimensions of height on width
if (RecalculatesOtherAxis) then
begin
if (calcWidth > 0) then
calcWidth := Trunc(abs(calcHeight) * (curRatio.Horizontal / curRatio.Vertical))
else
calcWidth := -Trunc(abs(calcHeight) * (curRatio.Horizontal / curRatio.Vertical));
newCoords.x2 := newCoords.x1 + calcWidth;
end;
end
else
begin
// Calculate width
calcWidth := newCoords.x2 - newCoords.x1;
// Make sure we're not going out of bounds
if (EAST in Direction) then
begin
if ((newCoords.x1 + calcWidth) > Bounds.Right) then
begin
calcWidth := Bounds.Right - newCoords.x1; // Limite width dimension
newCoords.x2 := Bounds.Right;
end;
end;
if (WEST in Direction) then
begin
if ((newCoords.x1 + calcWidth) < Bounds.Left) then
begin
calcWidth := -(newCoords.x1 - Bounds.Left); // Limite width dimension
newCoords.x2 := Bounds.Left;
end;
end;
// Calculate height
calcHeight := newCoords.y2 - newCoords.y1;
// Make sure we're not going out of bounds
if (SOUTH in Direction) then
begin
if ((newCoords.y1 + calcHeight) > Bounds.Bottom) then
begin
calcHeight := Bounds.Bottom - newCoords.y1; // Limite height dimension
newCoords.y2 := Bounds.Bottom;
end;
end;
if (NORTH in Direction) then
begin
if ((newCoords.y1 + calcHeight) < Bounds.Top) then
begin
calcHeight := -(newCoords.y1 - Bounds.Top); // Limite height dimension
newCoords.y2 := Bounds.Top;
end;
end;
end;
newCoords.x2 := newCoords.x1 + calcWidth;
newCoords.y2 := newCoords.y1 + calcHeight;
end;
Result := newCoords;
end;
procedure TBGRAImageManipulation.ApplyRatioToArea(ACropArea :TCropArea);
var
calcWidth, calcHeight :Integer;
CropAreaRect, Bounds :TRect;
curRatio :TRatio;
curKeepAspectRatio :Boolean;
begin
if (ACropArea <> Nil) then
begin
CropAreaRect :=ACropArea.ScaledArea;
Bounds := getImageRect(fResampledBitmap);
// Calculate width
calcWidth :=CropAreaRect.Right-CropAreaRect.Left;
// Make sure we're not going out of bounds with Widht
if ((CropAreaRect.Left+calcWidth)>Bounds.Right) then
begin
calcWidth :=Bounds.Right-CropAreaRect.Left; // Limite width dimension
CropAreaRect.Right :=Bounds.Right;
end;
curKeepAspectRatio :=ACropArea.getRealAspectRatio(curRatio);
if curKeepAspectRatio // Calculate the new height based on the proportion of width
then calcHeight := Trunc(abs(calcWidth)*(curRatio.Vertical/curRatio.Horizontal));
//else calcHeight := CropAreaRect.Height; //Raise an Exception ???
// Make sure we're not going out of bounds with Height
if ((CropAreaRect.Top+calcHeight) > Bounds.Bottom) then
begin
calcHeight :=Bounds.Bottom-CropAreaRect.Top;
calcWidth :=Trunc(abs(calcHeight)*(curRatio.Horizontal/curRatio.Vertical));
end;
CropAreaRect.Right :=CropAreaRect.Left+calcWidth;
CropAreaRect.Bottom :=CropAreaRect.Top+calcHeight;
ACropArea.ScaledArea :=CropAreaRect;
end;
end;
{ Calculate the maximun selection allowed }
procedure TBGRAImageManipulation.CalcMaxSelection(ACropArea :TCropArea);
var
ImageRect: TRect;
newCoords: TCoord;
Direction: TDirection;
Bounds: TRect;
begin
if (ACropArea <> Nil) then
begin
ImageRect := getImageRect(fImageBitmap);
// Initiates coord
with newCoords do
begin
x1 := 0;
y1 := 0;
x2 := ImageRect.Right - ImageRect.Left;
y2 := ImageRect.Bottom - ImageRect.Top;
end;
// Determine direction
Direction := getDirection(Point(newCoords.x1, newCoords.y1),
Point(newCoords.x2, newCoords.y2));
// Determines limite values
with newCoords do
begin
x1 := 0;
y1 := 0;
x2 := ImageRect.Right - ImageRect.Left;
y2 := ImageRect.Bottom - ImageRect.Top;
end;
Bounds := getImageRect(fResampledBitmap);
// Apply the ratio
newCoords := ApplyRatioToAxes(newCoords, Direction, Bounds);
// Determines minimum value on both axes
newCoords := ApplyDimRestriction(newCoords, Direction, Bounds, fKeepAspectRatio);
ACropArea.ScaledArea := Rect(newCoords.x1, newCoords.y1, newCoords.x2, newCoords.y2);
end;
end;
{ Calculate the Aspect Ratio for size limits}
procedure TBGRAImageManipulation.findSizeLimits;
var
WorkRect: TRect;
begin
// Find the working area of the component
WorkRect := getWorkRect;
with fSizeLimits do
begin
minWidth := fAspectX;
maxWidth := WorkRect.Right - WorkRect.Left;
minHeight := fAspectY;
maxHeight := WorkRect.Bottom - WorkRect.Top;
end;
end;
{ Get the direction of movement }
function TBGRAImageManipulation.getDirection(const Point1, Point2: TPoint): TDirection;
begin
Result := [];
if (Point1.X > Point2.X) then
Result := Result + [WEST];
if (Point1.X < Point2.X) then
Result := Result + [EAST];
if (Point1.Y > Point2.Y) then
Result := Result + [NORTH];
if (Point1.Y < Point2.Y) then
Result := Result + [SOUTH];
end;
{ Get image rectangle }
function TBGRAImageManipulation.getImageRect(Picture: TBGRABitmap): TRect;
var
calcWidth, calcHeight, finalWidth, finalHeight, imageWidth, imageHeight: integer;
WorkRect: TRect;
begin
// Determine picture size
imageWidth := Picture.Width;
imageHeight := Picture.Height;
// Determine Work rectangle to final size
WorkRect := getWorkRect;
finalWidth := WorkRect.Right - WorkRect.Left;
finalHeight := WorkRect.Bottom - WorkRect.Top;
// Recalculate image dimensions
calcHeight := (finalWidth * imageHeight) div imageWidth;
calcWidth := finalWidth;
if (calcHeight > finalHeight) then
begin
calcHeight := finalHeight;
calcWidth := (calcHeight * imageWidth) div imageHeight;
end;
with Result do
begin
Left := 0;
Top := 0;
Right := calcWidth;
Bottom := calcHeight;
end;
end;
{ Get work area rectangle }
function TBGRAImageManipulation.getWorkRect: TRect;
begin
// Get the coordinates of the control
if (fVirtualScreen <> nil) then
Result := Rect(0, 0, fVirtualScreen.Width, fVirtualScreen.Height)
else
Result := GetClientRect;
// Remove the non-work areas from our work rectangle
InflateRect(Result, -fBorderSize, -fBorderSize);
end;
{ Check if mouse is over any anchor }
function TBGRAImageManipulation.isOverAnchor(APoint :TPoint; var AnchorSelected :TDirection; var ACursor :TCursor):TCropArea;
var
i :Integer;
function _isOverAnchor(APoint: TPoint; Corner: TPoint): boolean;
begin
Result := ((APoint.X >= (Corner.X - AnchorSize)) and
(APoint.X <= (Corner.X + AnchorSize)) and
(APoint.Y >= (Corner.Y - AnchorSize)) and
(APoint.Y <= (Corner.Y + AnchorSize)));
end;
function TestArea(rCropArea :TCropArea):TCropArea;
var
rCropRect,
rCropRectI :TRect;
begin
Result :=nil;
rCropRectI :=rCropArea.ScaledArea;
InflateRect(rCropRectI, AnchorSize, AnchorSize);
if ({$IFNDEF FPC}BGRAGraphics.{$ENDIF}PtInRect(rCropRectI, APoint)) then
begin
rCropRect :=rCropArea.ScaledArea;
// Verifies that is positioned on an anchor
// NW
if (_isOverAnchor(APoint, rCropRect.TopLeft)) then
begin
AnchorSelected := [NORTH, WEST];
ACursor := crSizeNW;
Result :=rCropArea; exit;
end;
// W
if (_isOverAnchor(APoint, Point(rCropRect.Left, rCropRect.Top +
(rCropRect.Bottom - rCropRect.Top) div 2))) then
begin
AnchorSelected := [WEST];
ACursor := crSizeWE;
Result :=rCropArea; exit;
end;
// SW
if (_isOverAnchor(APoint, Point(rCropRect.Left, rCropRect.Bottom))) then
begin
AnchorSelected := [SOUTH, WEST];
ACursor := crSizeSW;
Result :=rCropArea; exit;
end;
// S
if (_isOverAnchor(APoint, Point(rCropRect.Left +
((rCropRect.Right - rCropRect.Left) div 2), rCropRect.Bottom))) then
begin
AnchorSelected := [SOUTH];
ACursor := crSizeNS;
Result :=rCropArea; exit;
end;
// SE
if (_isOverAnchor(APoint, rCropRect.BottomRight)) then
begin
AnchorSelected := [SOUTH, EAST];
ACursor := crSizeSE;
Result :=rCropArea; exit;
end;
// E
if (_isOverAnchor(APoint, Point(rCropRect.Right, rCropRect.Top +
((rCropRect.Bottom - rCropRect.Top) div 2)))) then
begin
AnchorSelected := [EAST];
ACursor := crSizeWE;
Result :=rCropArea; exit;
end;
// NE
if (_isOverAnchor(APoint, Point(rCropRect.Right, rCropRect.Top))) then
begin
AnchorSelected := [NORTH, EAST];
ACursor := crSizeNE;
Result :=rCropArea; exit;
end;
// N
if (_isOverAnchor(APoint, Point(rCropRect.Left +
((rCropRect.Right - rCropRect.Left) div 2), rCropRect.Top))) then
begin
AnchorSelected := [NORTH];
ACursor := crSizeNS;
Result :=rCropArea; exit;
end;
// Verifies that is positioned on a cropping area
if (AnchorSelected = []) then
begin
if ((APoint.X >= rCropRect.Left) and (APoint.X <= rCropRect.Right) and
(APoint.Y >= rCropRect.Top) and (APoint.Y <= rCropRect.Bottom)) then
begin
AnchorSelected := [NORTH, SOUTH, EAST, WEST];
ACursor := crSizeAll;
Result :=rCropArea; exit;
end;
end;
end;
end;
begin
AnchorSelected :=[];
ACursor :=crDefault;
Result :=Nil;
if (SelectedCropArea=nil)
then for i:=rCropAreas.Count-1 downto 0 do //downto so respect ZOrder
begin
Result :=TestArea(rCropAreas[i]);
if (Result<>nil) then break;
end
else begin
//Gives precedence to the selected area
Result :=TestArea(SelectedCropArea);
if (Result=nil) then
for i:=rCropAreas.Count-1 downto 0 do
begin
if (rCropAreas[i]<>SelectedCropArea) then
begin
Result :=TestArea(rCropAreas[i]);
if (Result<>nil) then break;
end;
end;
end;
end;
procedure TBGRAImageManipulation.CreateEmptyImage;
begin
fImageBitmap.Free;
fImageBitmap :=TBGRABitmap.Create(EmptyImage.Width, EmptyImage.Height);
fImageBitmap.ResolutionUnit :=ruPixelsPerInch;
fImageBitmap.ResolutionX :=Self.PixelsPerInch;
fImageBitmap.ResolutionY :=fImageBitmap.ResolutionX;
end;
procedure TBGRAImageManipulation.CreateResampledBitmap;
var
DestinationRect: TRect;
ResampledBitmap: TBGRACustomBitmap;
begin
// Get the resampled dimensions to scale image for draw in component
DestinationRect := getImageRect(fImageBitmap);
// Recreate resampled bitmap
try
fResampledBitmap.Free;
fResampledBitmap := TBGRABitmap.Create(DestinationRect.Right - DestinationRect.Left,
DestinationRect.Bottom - DestinationRect.Top);
ResampledBitmap := fImageBitmap.Resample(DestinationRect.Right - DestinationRect.Left,
DestinationRect.Bottom - DestinationRect.Top, rmFineResample);
fResampledBitmap.BlendImage(0, 0, ResampledBitmap, boLinearBlend);
finally
ResampledBitmap.Free;
end;
end;
procedure TBGRAImageManipulation.Loaded;
begin
inherited Loaded;
if Self.Empty and rEmptyImage.Allow then
begin
CreateEmptyImage;
CreateResampledBitmap;
end;
rLoading:=False;
// Force Render Struct
RepaintBackground;
Render;
end;
{ ============================================================================ }
{ =====[ Component Definition ]=============================================== }
{ ============================================================================ }
constructor TBGRAImageManipulation.Create(AOwner: TComponent);
var
fGCD :integer;
begin
inherited Create(AOwner);
//MaxM: csLoading in ComponentState does not work?
rLoading :=True;
// Set default component values
inherited Width := 320;
inherited Height := 240;
// Default property values
fAnchorSize := 5;
fAnchorSelected := [];
fBorderSize := 2;
fAspectRatio := '3:4';
fAspectX := 3;
fAspectY := 4;
fKeepAspectRatio := True;
// Default control values
ControlStyle := ControlStyle + [csReplicatable];
Cursor := crDefault;
// Calculate the ratio
fGCD := getGCD(fAspectX, fAspectY);
// Determine the ratio of scale per axle
with fRatio do
begin
Horizontal := fAspectX div fGCD;
Vertical := fAspectY div fGCD;
end;
// Find size limits
findSizeLimits;
// Create the Image Bitmap
fImageBitmap := TBGRABitmap.Create;
// Create the Resampled Bitmap
fResampledBitmap := TBGRABitmap.Create;
// Create the Background
fBackground := TBGRABitmap.Create(Width, Height);
// Create render surface
fVirtualScreen := TBGRABitmap.Create(Width, Height);
rEmptyImage :=TBGRAEmptyImage.Create(Self);
rNewCropAreaDefault :=TBGRANewCropAreaDefault.Create(Self);
// Initialize crop area
rCropAreas :=TCropAreaList.Create(Self);
rCropAreas.Name:='CropAreas';
rNewCropArea :=Nil;
rSelectedCropArea :=Nil;
fMouseCaught := False;
end;
destructor TBGRAImageManipulation.Destroy;
begin
fImageBitmap.Free;
fResampledBitmap.Free;
fBackground.Free;
fVirtualScreen.Free;
rEmptyImage.Free;
rNewCropAreaDefault.Free;
rCropAreas.Free;
inherited Destroy;
end;
procedure TBGRAImageManipulation.Invalidate;
begin
inherited Invalidate;
end;
procedure TBGRAImageManipulation.Paint;
begin
inherited Paint;
fVirtualScreen.Draw(Canvas, 0, 0, True);
end;
{ This function repaint the background only when necessary to avoid unnecessary
redraws. Contain a function called DrawCheckers that draws the Background like
checkers game. Also included was a function that draws 3D effects changed to
allow color changes. }
procedure TBGRAImageManipulation.RepaintBackground;
procedure DrawCheckers(bmp: TBGRABitmap; ARect: TRect);
const
tx = 8;
ty = 8;
var
xb, yb, xdest, ydest, nbx, nby: integer;
oddColor, evenColor: TBGRAPixel;
begin
oddColor := BGRA(220, 220, 220);
evenColor := BGRA(255, 255, 255);
bmp.ClipRect := ARect;
xdest := ARect.Left;
nbx := ((ARect.Right - ARect.Left) + tx - 1) div tx;
nby := ((ARect.Bottom - ARect.Top) + ty - 1) div ty;
for xb := 0 to nbx - 1 do
begin
ydest := ARect.Top;
for yb := 0 to nby - 1 do
begin
if odd(xb + yb) then
bmp.FillRect(xdest, ydest, xdest + tx, ydest + ty, oddColor, dmSet)
else
bmp.FillRect(xdest, ydest, xdest + tx, ydest + ty, evenColor, dmSet);
Inc(ydest, ty);
end;
Inc(xdest, tx);
end;
bmp.NoClip;
end;
var
Border: TRect;
Grad: TBGRAGradientScanner;
begin
// Resize background
fBackground.SetSize(fVirtualScreen.Width, fVirtualScreen.Height);
// Draw the outer bevel
Border := Rect(0, 0, fVirtualScreen.Width, fVirtualScreen.Height);
// Draw the rectangle around image
if (fBorderSize > 2) then
begin
// Draw the border gradient
Grad := TBGRAGradientScanner.Create(BGRA(245, 245, 245),
BGRA(205, 204, 203), gtLinear, PointF(0, 0), PointF(0, fBackground.Height));
fBackground.FillRect(0, 0, fBackground.Width, fBorderSize - 2, Grad, dmSet);
fBackground.FillRect(0, fBorderSize - 2, fBorderSize - 2,
fBackground.Height - fBorderSize + 2, Grad, dmSet);
fBackground.FillRect(fBackground.Width - fBorderSize + 2, fBorderSize - 2,
fBackground.Width, fBackground.Height - fBorderSize + 2,
Grad, dmSet);
fBackground.FillRect(0, fBackground.Height - fBorderSize + 2,
fBackground.Width, fBackground.Height, Grad, dmSet);
Grad.Free;
InflateRect(Border, -(fBorderSize - 2), -(fBorderSize - 2));
end;
// Draw 3D border
fBackground.CanvasBGRA.Frame3D(Border, 1, bvLowered,
clBtnHighlight, cl3DDkShadow);
fBackground.CanvasBGRA.Frame3D(Border, 1, bvLowered,
cl3DLight, clBtnShadow);
DrawCheckers(fBackground, Border);
end;
{ Resize the component, recalculating the proportions }
procedure TBGRAImageManipulation.Resize;
function min(const Value: integer; const MinValue: integer): integer;
begin
if (Value < MinValue) then
Result := MinValue
else
Result := Value;
end;
var
i :Integer;
curCropArea :TCropArea;
begin
inherited Resize;
//MaxM: Maybe csLoading in ComponentState but it does not work
if rLoading then exit;
if (fVirtualScreen <> nil) then
begin
fVirtualScreen.SetSize(min(Self.Width, (fBorderSize * 2 + fAnchorSize + fMinWidth)),
min(Self.Height, (fBorderSize * 2 + fAnchorSize + fMinHeight)));
fVirtualScreen.InvalidateBitmap;
if Self.Empty and rEmptyImage.Allow
then CreateEmptyImage;
CreateResampledBitmap;
for i:=0 to rCropAreas.Count-1 do
begin
curCropArea :=rCropAreas[i];
curCropArea.CalculateScaledAreaFromArea;
if curCropArea.isNullSize then
begin
// A Null-size crop selection (delete it or assign max size?)
//CalcMaxSelection(curCropArea);
end;
end;
// Force Render Struct
RepaintBackground;
Render;
end;
Invalidate;
end;
{ Function responsible for rendering the content of the component, including
the selection border and anchors. The selected area is painted with a
different transparency level for easy viewing of what will be cut. }
procedure TBGRAImageManipulation.Render;
var
WorkRect, emptyRect: TRect;
Mask: TBGRABitmap;
BorderColor, SelectColor,
FillColor, IcoColor: TBGRAPixel;
curCropArea :TCropArea;
curCropAreaRect :TRect;
i: Integer;
TextS:TTextStyle;
begin
// This procedure render main feature of engine
// Render background
fVirtualScreen.BlendImage(0, 0, fBackground, boLinearBlend);
// Render the image
// Find the working area of the component
WorkRect := getWorkRect;
try
// Draw image
fVirtualScreen.BlendImage(WorkRect.Left, WorkRect.Top, fResampledBitmap, boLinearBlend);
// Render the selection background area
BorderColor := BGRAWhite;
FillColor := BGRA(0, 0, 0, 128);
Mask := TBGRABitmap.Create(WorkRect.Right - WorkRect.Left, WorkRect.Bottom - WorkRect.Top, FillColor);
if Self.Empty and rEmptyImage.Allow and rEmptyImage.ShowBorder then
begin
emptyRect :=Rect(0,0,fResampledBitmap.Width-1, fResampledBitmap.Height-1);
Mask.CanvasBGRA.Frame3d(emptyRect, 1, bvRaised, BGRA(255, 255, 255, 180), BGRA(0, 0, 0, 160));
//Mask.Rectangle(emptyRect, BorderColor, BGRAPixelTransparent); //wich one?
end;
for i:=0 to rCropAreas.Count-1 do
begin
curCropArea :=rCropAreas[i];
curCropAreaRect :=curCropArea.ScaledArea;
//Colors
SelectColor := BGRA(255, 255, 0, 255);
FillColor := BGRA(255, 255, 0, 128);
if (curCropArea = SelectedCropArea)
then begin
BorderColor := BGRA(255, 0, 0, 255);
IcoColor :=BorderColor;
end
else begin
if (curCropArea = rNewCropArea)
then BorderColor := BGRA(255, 0, 255, 255)
else BorderColor := curCropArea.BorderColor;
IcoColor :=SelectColor;
end;
Mask.EraseRectAntialias(curCropAreaRect.Left, curCropAreaRect.Top, curCropAreaRect.Right-1,
curCropAreaRect.Bottom-1, 255);
// Draw a selection box
with Rect(curCropAreaRect.Left, curCropAreaRect.Top, curCropAreaRect.Right-1, curCropAreaRect.Bottom-1) do
Mask.DrawPolyLineAntialias([Point(Left, Top), Point(Right, Top), Point(Right, Bottom), Point(Left, Bottom), Point(Left, Top)],
BorderColor, BGRAPixelTransparent, 1, False);
//Draw Icons
if (cIcoIndex in curCropArea.Icons) then
begin
TextS.Alignment:=taCenter;
TextS.SystemFont:=True;
TextS.Layout:=tlCenter;
TextS.SingleLine:=True;
Mask.FontHeight:=12;
Mask.FontStyle:=[fsBold];
Mask.EllipseAntialias(curCropAreaRect.Right-12, curCropAreaRect.Top+12, 4,4, IcoColor, 8);
Mask.TextRect(Rect(curCropAreaRect.Right-18, curCropAreaRect.Top+2, curCropAreaRect.Right-4, curCropAreaRect.Top+24),
curCropAreaRect.Right-12, curCropAreaRect.Top+12,
IntToStr(curCropArea.getIndex), TextS, BGRAWhite);
end;
// Draw anchors
BorderColor := BGRABlack;
// NW
Mask.Rectangle(curCropAreaRect.Left-fAnchorSize, curCropAreaRect.Top-fAnchorSize,
curCropAreaRect.Left+fAnchorSize+1, curCropAreaRect.Top+fAnchorSize+1,
BorderColor, FillColor, dmSet);
// W
Mask.Rectangle(curCropAreaRect.Left-fAnchorSize,
(curCropAreaRect.Top+((curCropAreaRect.Bottom - curCropAreaRect.Top) div 2))-fAnchorSize,
curCropAreaRect.Left+fAnchorSize+1,
(curCropAreaRect.Top+((curCropAreaRect.Bottom - curCropAreaRect.Top) div 2))+fAnchorSize+1,
BorderColor, FillColor, dmSet);
// SW
Mask.Rectangle(curCropAreaRect.Left-fAnchorSize, curCropAreaRect.Bottom-fAnchorSize-1,
curCropAreaRect.Left+fAnchorSize+1, curCropAreaRect.Bottom+fAnchorSize,
BorderColor, FillColor, dmSet);
// S
if ((fAnchorSelected = [NORTH]) and (curCropAreaRect.Top < curCropAreaRect.Bottom) and
(fStartPoint.Y = curCropAreaRect.Top)) or ((fAnchorSelected = [NORTH]) and
(curCropAreaRect.Top > curCropAreaRect.Bottom) and (fStartPoint.Y = curCropAreaRect.Top)) or
((fAnchorSelected = [SOUTH]) and (curCropAreaRect.Top < curCropAreaRect.Bottom) and
(fStartPoint.Y = curCropAreaRect.Top)) or ((fAnchorSelected = [SOUTH]) and
(curCropAreaRect.Top > curCropAreaRect.Bottom) and (fStartPoint.Y = curCropAreaRect.Top))
then Mask.Rectangle((curCropAreaRect.Left+((curCropAreaRect.Right-curCropAreaRect.Left) div 2))-fAnchorSize,
curCropAreaRect.Bottom-fAnchorSize-1, (curCropAreaRect.Left+((curCropAreaRect.Right - curCropAreaRect.Left) div 2))+fAnchorSize+1,
curCropAreaRect.Bottom+fAnchorSize,
BorderColor, SelectColor, dmSet)
else Mask.Rectangle((curCropAreaRect.Left+((curCropAreaRect.Right-curCropAreaRect.Left) div 2))-fAnchorSize,
curCropAreaRect.Bottom-fAnchorSize-1, (curCropAreaRect.Left+((curCropAreaRect.Right-curCropAreaRect.Left) div 2))+fAnchorSize+1,
curCropAreaRect.Bottom+fAnchorSize,
BorderColor, FillColor, dmSet);
// SE
if ((fAnchorSelected = [NORTH, WEST]) and
((curCropAreaRect.Left > curCropAreaRect.Right) and (curCropAreaRect.Top > curCropAreaRect.Bottom))) or
((fAnchorSelected = [NORTH, WEST]) and
((curCropAreaRect.Left < curCropAreaRect.Right) and (curCropAreaRect.Top < curCropAreaRect.Bottom))) or
((fAnchorSelected = [NORTH, WEST]) and
((curCropAreaRect.Left > curCropAreaRect.Right) and (curCropAreaRect.Top < curCropAreaRect.Bottom))) or
((fAnchorSelected = [NORTH, WEST]) and
((curCropAreaRect.Left < curCropAreaRect.Right) and (curCropAreaRect.Top > curCropAreaRect.Bottom))) or
((fAnchorSelected = [NORTH, EAST]) and
((curCropAreaRect.Left < curCropAreaRect.Right) and (curCropAreaRect.Top > curCropAreaRect.Bottom))) or
((fAnchorSelected = [NORTH, EAST]) and
((curCropAreaRect.Left > curCropAreaRect.Right) and (curCropAreaRect.Top < curCropAreaRect.Bottom))) or
((fAnchorSelected = [NORTH, EAST]) and
((curCropAreaRect.Left < curCropAreaRect.Right) and (curCropAreaRect.Top < curCropAreaRect.Bottom))) or
((fAnchorSelected = [NORTH, EAST]) and
((curCropAreaRect.Left > curCropAreaRect.Right) and (curCropAreaRect.Top > curCropAreaRect.Bottom))) or
((fAnchorSelected = [SOUTH, EAST]) and
((curCropAreaRect.Left > curCropAreaRect.Right) and (curCropAreaRect.Top > curCropAreaRect.Bottom))) or
((fAnchorSelected = [SOUTH, EAST]) and
((curCropAreaRect.Left < curCropAreaRect.Right) and (curCropAreaRect.Top < curCropAreaRect.Bottom))) or
((fAnchorSelected = [SOUTH, EAST]) and
((curCropAreaRect.Left > curCropAreaRect.Right) and (curCropAreaRect.Top < curCropAreaRect.Bottom))) or
((fAnchorSelected = [SOUTH, EAST]) and
((curCropAreaRect.Left < curCropAreaRect.Right) and (curCropAreaRect.Top > curCropAreaRect.Bottom))) or
((fAnchorSelected = [SOUTH, WEST]) and
((curCropAreaRect.Left > curCropAreaRect.Right) and (curCropAreaRect.Top < curCropAreaRect.Bottom))) or
((fAnchorSelected = [SOUTH, WEST]) and
((curCropAreaRect.Left < curCropAreaRect.Right) and (curCropAreaRect.Top > curCropAreaRect.Bottom))) or
((fAnchorSelected = [SOUTH, WEST]) and
((curCropAreaRect.Left > curCropAreaRect.Right) and (curCropAreaRect.Top > curCropAreaRect.Bottom))) or
((fAnchorSelected = [SOUTH, WEST]) and
((curCropAreaRect.Left < curCropAreaRect.Right) and (curCropAreaRect.Top < curCropAreaRect.Bottom)))
then Mask.Rectangle(curCropAreaRect.Right-fAnchorSize-1,
curCropAreaRect.Bottom-fAnchorSize-1, curCropAreaRect.Right+fAnchorSize, curCropAreaRect.Bottom+fAnchorSize,
BorderColor, SelectColor, dmSet)
else Mask.Rectangle(curCropAreaRect.Right-fAnchorSize-1,
curCropAreaRect.Bottom-fAnchorSize-1, curCropAreaRect.Right+fAnchorSize, curCropAreaRect.Bottom+fAnchorSize,
BorderColor, FillColor, dmSet);
// E
if ((fAnchorSelected = [EAST]) and (curCropAreaRect.Left < curCropAreaRect.Right) and
(fStartPoint.X = curCropAreaRect.Left)) or ((fAnchorSelected = [EAST]) and
(curCropAreaRect.Left > curCropAreaRect.Right) and (fStartPoint.X = curCropAreaRect.Left)) or
((fAnchorSelected = [WEST]) and (curCropAreaRect.Left < curCropAreaRect.Right) and
(fStartPoint.X = curCropAreaRect.Left)) or ((fAnchorSelected = [WEST]) and
(curCropAreaRect.Left > curCropAreaRect.Right) and (fStartPoint.X = curCropAreaRect.Left))
then Mask.Rectangle(curCropAreaRect.Right-fAnchorSize-1,
(curCropAreaRect.Top+((curCropAreaRect.Bottom - curCropAreaRect.Top) div 2))-fAnchorSize,
curCropAreaRect.Right+fAnchorSize, (curCropAreaRect.Top+((curCropAreaRect.Bottom-curCropAreaRect.Top) div 2))+fAnchorSize+1,
BorderColor, SelectColor, dmSet)
else Mask.Rectangle(curCropAreaRect.Right-fAnchorSize-1, (curCropAreaRect.Top+((curCropAreaRect.Bottom-curCropAreaRect.Top) div 2))-fAnchorSize,
curCropAreaRect.Right+fAnchorSize, (curCropAreaRect.Top+((curCropAreaRect.Bottom-curCropAreaRect.Top) div 2))+fAnchorSize+1,
BorderColor, FillColor, dmSet);
// NE
Mask.Rectangle(curCropAreaRect.Right-fAnchorSize-1, curCropAreaRect.Top-fAnchorSize,
curCropAreaRect.Right+fAnchorSize, curCropAreaRect.Top+fAnchorSize+1,
BorderColor, FillColor, dmSet);
// N
Mask.Rectangle((curCropAreaRect.Left+((curCropAreaRect.Right-curCropAreaRect.Left) div 2))-fAnchorSize,
curCropAreaRect.Top-fAnchorSize, (curCropAreaRect.Left+((curCropAreaRect.Right-curCropAreaRect.Left) div 2))+fAnchorSize+1,
curCropAreaRect.Top+fAnchorSize+1,
BorderColor, FillColor, dmSet);
end;
finally
fVirtualScreen.BlendImage(WorkRect.Left, WorkRect.Top, Mask, boLinearBlend);
Mask.Free;
end;
end;
{ ============================================================================ }
{ =====[ Properties Manipulation ]============================================ }
{ ============================================================================ }
function TBGRAImageManipulation.getAnchorSize: byte;
begin
Result := fAnchorSize * 2 + 1;
end;
function TBGRAImageManipulation.getPixelsPerInch: Integer;
begin
if (Owner is TCustomForm)
then Result :=TCustomForm(Owner).PixelsPerInch
else Result :=96;
end;
procedure TBGRAImageManipulation.setAnchorSize(const Value: byte);
const
MinSize = 3;
MaxSize = 9;
begin
if (Value <> getAnchorSize) then
begin
if (Value < MinSize) then
begin
raise ERangeError.CreateFmt(SAnchorSizeIsTooSmall,
[Value, MinSize, MaxSize]);
end
else
begin
if (Value > MaxSize) then
begin
raise ERangeError.CreateFmt(SAnchorSizeIsTooLarge,
[Value, MinSize, MaxSize]);
end
else
begin
if ((Value mod 2) = 0) then
begin
raise EInvalidArgument.CreateFmt(SAnchorSizeIsNotOdd, [Value]);
end
else
begin
fAnchorSize := (Value div 2);
Render;
Refresh;
end;
end;
end;
end;
end;
function TBGRAImageManipulation.getEmpty: boolean;
begin
Result := fImageBitmap.Empty or (fImageBitmap.Width = 0) or (fImageBitmap.Height = 0);
end;
function TBGRAImageManipulation.getResampledBitmap(ACropArea :TCropArea = Nil; ACopyProperties: Boolean=False): TBGRABitmap;
begin
Result := fImageBitmap;
if not (fImageBitmap.Empty) then
begin
if (ACropArea = Nil)
then ACropArea := Self.SelectedCropArea;
if (ACropArea <> Nil)
then Result :=ACropArea.getResampledBitmap(ACopyProperties);
end;
end;
function TBGRAImageManipulation.getBitmap(ACropArea :TCropArea = Nil; ACopyProperties: Boolean=False): TBGRABitmap;
begin
Result := fImageBitmap;
if not (fImageBitmap.Empty) then
begin
if (ACropArea = Nil)
then ACropArea := Self.SelectedCropArea;
if (ACropArea <> Nil)
then Result :=ACropArea.getBitmap(ACopyProperties);
end;
end;
procedure TBGRAImageManipulation.setBitmap(const Value: TBGRABitmap);
var
curCropArea: TCropArea;
i: Integer;
begin
if (Value <> fImageBitmap) then
begin
try
if Value.Empty or (Value.Width = 0) or (Value.Height = 0)
then begin
if EmptyImage.Allow
then CreateEmptyImage
else exit;
end
else begin
// Clear actual image
fImageBitmap.Free;
fImageBitmap :=TBGRABitmap.Create(Value.Width, Value.Height);
fImageBitmap.Assign(Value, True); // Associate the new bitmap
end;
CreateResampledBitmap;
for i:=0 to rCropAreas.Count-1 do
begin
curCropArea :=rCropAreas[i];
curCropArea.CalculateScaledAreaFromArea;
if curCropArea.isNullSize then
begin
// A Null-size crop selection (delete it or assign max size?)
//CalcMaxSelection(curCropArea);
end;
end;
finally
// Force Render Struct
Render;
inherited Invalidate;
end;
end;
end;
procedure TBGRAImageManipulation.rotateLeft(ACopyProperties: Boolean=False);
var
TempBitmap: TBGRACustomBitmap;
curCropArea :TCropArea;
i :Integer;
begin
try
// Prevent empty image if not Allowed
if Self.Empty and not(rEmptyImage.Allow)
then exit;
// Rotate bitmap
TempBitmap := fImageBitmap.RotateCCW(ACopyProperties);
fImageBitmap.Assign(TempBitmap);
CreateResampledBitmap;
{ #todo -oMaxM : Rotate the Crop Areas? a bool published property? }
for i:=0 to rCropAreas.Count-1 do
begin
curCropArea :=rCropAreas[i];
curCropArea.CalculateScaledAreaFromArea;
if curCropArea.isNullSize then
begin
// A Null-size crop selection (delete it or assign max size?)
//CalcMaxSelection(curCropArea);
end;
end;
finally
// Force Render Struct
Render;
inherited Invalidate;
TempBitmap.Free;
end;
end;
procedure TBGRAImageManipulation.rotateRight(ACopyProperties: Boolean=False);
var
TempBitmap: TBGRACustomBitmap;
curCropArea :TCropArea;
i :Integer;
begin
try
// Prevent empty image if not Allowed
if Self.Empty and not(rEmptyImage.Allow)
then exit;
// Rotate bitmap
TempBitmap := fImageBitmap.RotateCW(ACopyProperties);
fImageBitmap.Assign(TempBitmap);
CreateResampledBitmap;
{ #todo -oMaxM : Rotate the Crop Areas? a bool published property? }
for i:=0 to rCropAreas.Count-1 do
begin
curCropArea :=rCropAreas[i];
curCropArea.CalculateScaledAreaFromArea;
if curCropArea.isNullSize then
begin
// A Null-size crop selection (delete it or assign max size?)
//CalcMaxSelection(curCropArea);
end;
end;
finally
// Force Render Struct
Render;
inherited Invalidate;
TempBitmap.Free;
end;
end;
procedure TBGRAImageManipulation.tests;
begin
// Self.AutoSize:=False;
// Render;
// Refresh;
end;
function TBGRAImageManipulation.addCropArea(AArea: TRectF; AAreaUnit: TResolutionUnit;
AUserData: Integer): TCropArea;
var
newCropArea :TCropArea;
begin
try
newCropArea :=TCropArea.Create(Self, AArea, AAreaUnit, AUserData);
newCropArea.BorderColor :=BGRAWhite;
rCropAreas.add(newCropArea);
if (rSelectedCropArea = nil)
then rSelectedCropArea :=newCropArea;
newCropArea.CalculateScaledAreaFromArea;
Result :=newCropArea;
except
if (newCropArea <> Nil)
then newCropArea.Free;
Result :=Nil;
end;
Render;
Invalidate;
end;
function TBGRAImageManipulation.addScaledCropArea(AArea: TRect; AUserData: Integer): TCropArea;
begin
Result :=Self.addCropArea(RectF(0,0,0,0), rNewCropAreaDefault.rResolutionUnit, AUserData);
Result.rAspectRatio:=rNewCropAreaDefault.rAspectRatio;
Result.KeepAspectRatio:=rNewCropAreaDefault.rKeepAspectRatio;
Result.ScaledArea :=AArea;
if (fMouseCaught)
then Result.CalculateAreaFromScaledArea;
Render;
Invalidate;
end;
procedure TBGRAImageManipulation.delCropArea(ACropArea: TCropArea);
var
curIndex, newIndex :Integer;
begin
if (ACropArea <> Nil) then
begin
curIndex :=rCropAreas.IndexOf(ACropArea);
//determines the new SelectedCropArea
if (ACropArea = SelectedCropArea) then
begin
if (rCropAreas.Count = 1)
then SelectedCropArea :=nil
else begin
newIndex :=curIndex-1;
if (newIndex < 0)
then newIndex :=rCropAreas.Count-1;
SelectedCropArea :=rCropAreas.items[newIndex];
end;
end;
rCropAreas.Delete(curIndex);
Render;
Invalidate;
end;
end;
procedure TBGRAImageManipulation.clearCropAreas;
begin
rCropAreas.Clear;
Render;
Invalidate;
end;
procedure TBGRAImageManipulation.getAllResampledBitmaps(ACallBack: TgetAllBitmapsCallback; AUserData:Integer; ACopyProperties: Boolean=False);
var
i :Integer;
curBitmap :TBGRABitmap;
begin
//Get Resampled Bitmap of each CropArea and pass it to CallBack
for i:=0 to rCropAreas.Count-1 do
try
curBitmap :=rCropAreas[i].getResampledBitmap(ACopyProperties);
ACallBack(curBitmap, rCropAreas[i], AUserData);
finally
if (curBitmap<>nil)
then curBitmap.Free;
end;
end;
procedure TBGRAImageManipulation.getAllBitmaps(ACallBack: TgetAllBitmapsCallback; AUserData:Integer; ACopyProperties: Boolean=False);
var
i :Integer;
curBitmap :TBGRABitmap;
begin
//Get Bitmap of each CropArea and pass it to CallBack
for i:=0 to rCropAreas.Count-1 do
try
curBitmap :=rCropAreas[i].getBitmap(ACopyProperties);
ACallBack(curBitmap, rCropAreas[i], AUserData);
finally
if (curBitmap<>nil)
then curBitmap.Free;
end;
end;
procedure TBGRAImageManipulation.SetEmptyImageSizeToCropAreas(ReduceLarger: Boolean);
var
i :Integer;
curCropAreaRect :TRectF;
curCropArea :TCropArea;
mWidth, mHeight:Single;
xRatio, yRatio, resX :Single;
begin
if Self.Empty and rEmptyImage.Allow and (rCropAreas.Count>0) then
begin
if ReduceLarger
then begin
mWidth:=0;
mHeight:=0;
end
else begin
mWidth:=EmptyImage.ResolutionWidth;
mHeight:=EmptyImage.ResolutionHeight;
if (mWidth=0) or (mHeight=0) then
begin
mWidth :=ResolutionUnitConvert(fImageBitmap.Width, ruNone, EmptyImage.ResolutionUnit, Self.PixelsPerInch);
mHeight :=ResolutionUnitConvert(fImageBitmap.Height, ruNone, EmptyImage.ResolutionUnit, Self.PixelsPerInch);
end;
end;
for i:=0 to rCropAreas.Count-1 do
begin
curCropArea :=rCropAreas[i];
curCropAreaRect :=curCropArea.Area;
curCropAreaRect.Right :=ResolutionUnitConvert(curCropAreaRect.Right, curCropArea.rAreaUnit,
EmptyImage.ResolutionUnit, Self.PixelsPerInch);
curCropAreaRect.Bottom :=ResolutionUnitConvert(curCropAreaRect.Bottom, curCropArea.rAreaUnit,
EmptyImage.ResolutionUnit, Self.PixelsPerInch);
if (curCropAreaRect.Right > mWidth)
then mWidth :=curCropAreaRect.Right;
if (curCropAreaRect.Bottom > mHeight)
then mHeight :=curCropAreaRect.Bottom;
end;
EmptyImage.ResolutionWidth :=mWidth;
EmptyImage.ResolutionHeight :=mHeight;
Resize;
end;
end;
procedure TBGRAImageManipulation.SetEmptyImageSizeToNull;
begin
SetEmptyImageSize(ruPixelsPerInch, 0, 0);
end;
procedure TBGRAImageManipulation.SetEmptyImageSize(AResolutionUnit: TResolutionUnit; AResolutionWidth,
AResolutionHeight: Single);
begin
EmptyImage.ResolutionUnit:=AResolutionUnit;
EmptyImage.rResolutionWidth:=AResolutionWidth;
EmptyImage.rResolutionHeight:=AResolutionHeight;
Resize;
end;
procedure TBGRAImageManipulation.setBorderSize(const Value: byte);
const
MinSize = 2;
MaxSize = 10;
begin
if (Value <> fBorderSize) then
begin
if (Value < MinSize) then
begin
raise ERangeError.CreateFmt(SBorderSizeIsTooSmall,
[Value, MinSize, MaxSize]);
end
else
begin
if (Value > MaxSize) then
begin
raise ERangeError.CreateFmt(SBorderSizeIsTooLarge,
[Value, MinSize, MaxSize]);
end
else
begin
fBorderSize := Value;
Resize;
end;
end;
end;
end;
procedure TBGRAImageManipulation.setKeepAspectRatio(const Value: boolean);
var
i :Integer;
curCropArea :TCropArea;
imgPresent :Boolean;
begin
if (Value = fKeepAspectRatio) then Exit;
fKeepAspectRatio :=Value;
imgPresent :=not(fImageBitmap.Empty);
//Change all the Crop Area with KeepAspectRatio=bParent
for i:=0 to rCropAreas.Count-1 do
begin
curCropArea :=rCropAreas[i];
if (curCropArea<>nil) and (curCropArea.KeepAspectRatio=bParent) then
begin
if fKeepAspectRatio
then curCropArea.CopyAspectFromParent;
if imgPresent
then ApplyRatioToArea(curCropArea);
end;
end;
if imgPresent
then Render;
Invalidate;
end;
function TBGRAImageManipulation.getAspectRatioFromImage(
const Value: TBGRABitmap): string;
var
GCD: integer;
begin
GCD := getGCD(Value.Width, Value.Height);
Result := IntToStr(Value.Width div GCD) + ':' + IntToStr(Value.Height div GCD);
end;
procedure TBGRAImageManipulation.setAspectRatio(const Value: string);
var
XValue, YValue: integer;
AspectRatioText: string;
i :Integer;
fGCD :integer;
imgPresent :Boolean;
curCropArea :TCropArea;
begin
if (Value <> fAspectRatio) then
begin
// Check if value contain a valid string
CheckAspectRatio(Value, AspectRatioText, XValue, YValue);
// Set new Aspect Ratio
fAspectRatio := AspectRatioText;
fAspectX := XValue;
fAspectY := YValue;
// Calculate the ratio
fGCD := getGCD(fAspectX, fAspectY);
// Determine the ratio of scale per axle
with fRatio do
begin
Horizontal := fAspectX div fGCD;
Vertical := fAspectY div fGCD;
end;
// Set minimun size
if ((fRatio.Horizontal < fAnchorSize + 10) or
(fRatio.Vertical < fAnchorSize + 10)) then
begin
fMinWidth := fRatio.Horizontal * 10;
fMinHeight := fRatio.Vertical * 10;
end
else
begin
fMinWidth := fRatio.Horizontal;
fMinHeight := fRatio.Vertical;
end;
imgPresent :=not(fImageBitmap.Empty);
//Change all the Crop Area with KeepAspectRatio=bParent
for i:=0 to rCropAreas.Count-1 do
begin
curCropArea :=rCropAreas[i];
if (curCropArea<>nil) and (curCropArea.KeepAspectRatio=bParent) then
begin
if fKeepAspectRatio
then curCropArea.CopyAspectFromParent;
if imgPresent
then ApplyRatioToArea(curCropArea);
end;
end;
if imgPresent
then Render;
Invalidate;
end;
end;
procedure TBGRAImageManipulation.setEmptyImage(AValue: TBGRAEmptyImage);
begin
rEmptyImage.Assign(AValue);
end;
procedure TBGRAImageManipulation.setMinHeight(const Value: integer);
begin
if (Value <> fMinHeight) then
begin
if (Value < fSizeLimits.minHeight) then
begin
fMinHeight := fSizeLimits.minHeight;
end
else
begin
if (Value > fSizeLimits.maxHeight) then
begin
fMinHeight := fSizeLimits.maxHeight;
end
else
begin
fMinHeight := Value;
end;
end;
if (fKeepAspectRatio) then
begin
// Recalculates the width value based on height
fMinWidth := Trunc(fMinHeight * (fRatio.Horizontal / fRatio.Vertical));
end;
Render;
Invalidate;
end;
end;
procedure TBGRAImageManipulation.setMinWidth(const Value: integer);
begin
if (Value <> fMinWidth) then
begin
if (Value < fSizeLimits.minWidth) then
begin
fMinWidth := fSizeLimits.minWidth;
end
else
begin
if (Value > fSizeLimits.maxWidth) then
begin
fMinWidth := fSizeLimits.maxWidth;
end
else
begin
fMinWidth := Value;
end;
end;
if (fKeepAspectRatio) then
begin
// Recalculates the height value based on width
fMinHeight := Trunc(fMinWidth * (fRatio.Vertical / fRatio.Horizontal));
end;
Render;
Invalidate;
end;
end;
procedure TBGRAImageManipulation.setSelectedCropArea(AValue: TCropArea);
var
oldSelected :TCropArea;
begin
if rSelectedCropArea=AValue then Exit;
oldSelected :=rSelectedCropArea;
rSelectedCropArea:=AValue;
Render;
Invalidate;
if assigned(rOnSelectedCropAreaChanged)
then rOnSelectedCropAreaChanged(Self, oldSelected);
end;
{ ============================================================================ }
{ =====[ Event Control ]====================================================== }
{ ============================================================================ }
//Controllare tutte e 3
procedure TBGRAImageManipulation.MouseDown(Button: TMouseButton;
Shift: TShiftState; X, Y: integer);
var
WorkRect: TRect;
ACursor :TCursor;
begin
// Call the inherited MouseDown() procedure
inherited MouseDown(Button, Shift, X, Y);
// Find the working area of the control
WorkRect := getWorkRect;
// If over control
if (((X >= WorkRect.Left) and (X <= WorkRect.Right) and
(Y >= WorkRect.Top) and (Y <= WorkRect.Bottom)) and
(Button = mbLeft) and (not (ssDouble in Shift))) then
begin
// If this was the left mouse button and nor double click
fMouseCaught := True;
fStartPoint := Point(X - WorkRect.Left, Y - WorkRect.Top);
//rNewCropArea :=nil;
SelectedCropArea :=Self.isOverAnchor(fStartPoint, fAnchorSelected, {%H-}ACursor);
if (SelectedCropArea<>nil)
then fStartArea :=SelectedCropArea.ScaledArea;
if (fAnchorSelected = [NORTH, SOUTH, EAST, WEST])
then begin // Move the cropping area
fStartPoint :=Point(X - SelectedCropArea.ScaledArea.Left, Y-SelectedCropArea.ScaledArea.Top);
end
else begin // Resize the cropping area from cornes
// Get the coordinate corresponding to the opposite quadrant and
// set into fStartPoint
if ((fAnchorSelected = [NORTH]) or (fAnchorSelected = [WEST]) or
(fAnchorSelected = [NORTH, WEST]))
then fStartPoint := Point(SelectedCropArea.ScaledArea.Right, SelectedCropArea.ScaledArea.Bottom);
if (fAnchorSelected = [SOUTH, WEST])
then fStartPoint := Point(SelectedCropArea.ScaledArea.Right, SelectedCropArea.ScaledArea.Top);
if ((fAnchorSelected = [SOUTH]) or (fAnchorSelected = [EAST]) or
(fAnchorSelected = [SOUTH, EAST]))
then fStartPoint := Point(SelectedCropArea.ScaledArea.Left, SelectedCropArea.ScaledArea.Top);
if (fAnchorSelected = [NORTH, EAST])
then fStartPoint := Point(SelectedCropArea.ScaledArea.Left, SelectedCropArea.ScaledArea.Bottom);
end;
end;
end;
procedure TBGRAImageManipulation.MouseMove(Shift: TShiftState; X, Y: integer);
var
needRepaint: boolean;
WorkRect: TRect;
newCoords: TCoord;
Direction: TDirection;
Bounds: TRect;
{%H-}overCropArea :TCropArea;
ACursor :TCursor;
procedure newSelection;
begin
// Starts a new selection of cropping area
try
Cursor := crCross;
fEndPoint := Point(X - WorkRect.Left, Y - WorkRect.Top);
// Copy coord
with newCoords do
begin
x1 := fStartPoint.X;
y1 := fStartPoint.Y;
x2 := fEndPoint.X;
y2 := fEndPoint.Y;
end;
// Determine direction
Direction := getDirection(fStartPoint, fEndPoint);
// Apply the ratio, if necessary
newCoords := ApplyRatioToAxes(newCoords, Direction, Bounds, rNewCropArea);
// Determines minimum value on both axes
// new Area have KeepAspectRatio setted to bParent by default
newCoords := ApplyDimRestriction(newCoords, Direction, Bounds, fKeepAspectRatio);
if (rNewCropArea = Nil)
then begin
rNewCropArea :=addScaledCropArea(Rect(newCoords.x1, newCoords.y1, newCoords.x2, newCoords.y2));
SelectedCropArea :=rNewCropArea;
end
else rNewCropArea.ScaledArea :=Rect(newCoords.x1, newCoords.y1, newCoords.x2, newCoords.y2);
finally
needRepaint := True;
end;
end;
procedure moveCropping;
begin
Cursor := crSizeAll;
// Move the cropping area
try
WorkRect :=SelectedCropArea.ScaledArea;
WorkRect.Left :=fEndPoint.X-fStartPoint.X; //fStartPoint is Relative to CropArea
WorkRect.Top :=fEndPoint.Y-fStartPoint.Y;
//Out of Bounds check
if (WorkRect.Left<0)
then WorkRect.Left :=0;
if (WorkRect.Top<0)
then WorkRect.Top :=0;
if (WorkRect.Left+fStartArea.Width>Bounds.Right)
then WorkRect.Left :=Bounds.Right-fStartArea.Width;
if (WorkRect.Top+fStartArea.Height>Bounds.Bottom)
then WorkRect.Top :=Bounds.Bottom-fStartArea.Height;
WorkRect.Width :=fStartArea.Width;
WorkRect.Height:=fStartArea.Height;
SelectedCropArea.ScaledArea :=WorkRect;
finally
needRepaint := True;
end;
end;
procedure resizeCropping;
begin
// Resize the cropping area
try
if ((fAnchorSelected = [EAST]) or (fAnchorSelected = [WEST]))
then Cursor := crSizeWE
else if (NORTH in fAnchorSelected)
then begin
if (WEST in fAnchorSelected)
then Cursor := crSizeNW
else if (EAST in fAnchorSelected)
then Cursor := crSizeNE
else Cursor := crSizeNS;
end
else begin
if (WEST in fAnchorSelected)
then Cursor := crSizeSW
else if (EAST in fAnchorSelected)
then Cursor := crSizeSE
else Cursor := crSizeNS;
end;
// Copy coord
with newCoords do
begin
x1 := fStartPoint.X;
y1 := fStartPoint.Y;
if (fAnchorSelected = [NORTH]) then
begin
x2 := fEndPoint.X - Abs(SelectedCropArea.ScaledArea.Right - SelectedCropArea.ScaledArea.Left) div 2;
y2 := fEndPoint.Y;
end
else
if (fAnchorSelected = [SOUTH]) then
begin
x2 := fEndPoint.X + Abs(SelectedCropArea.ScaledArea.Right - SelectedCropArea.ScaledArea.Left) div 2;
y2 := fEndPoint.Y;
end
else
if (fAnchorSelected = [EAST]) then
begin
x2 := fEndPoint.X;
y2 := fEndPoint.Y + Abs(SelectedCropArea.ScaledArea.Bottom - SelectedCropArea.ScaledArea.Top) div 2;
end
else
if (fAnchorSelected = [WEST]) then
begin
x2 := fEndPoint.X;
y2 := fEndPoint.Y - Abs(SelectedCropArea.ScaledArea.Bottom - SelectedCropArea.ScaledArea.Top) div 2;
end
else
begin
x2 := fEndPoint.X;
y2 := fEndPoint.Y;
end;
end;
// Determine direction
Direction := getDirection(fStartPoint, fEndPoint);
// Apply the ratio, if necessary
newCoords := ApplyRatioToAxes(newCoords, Direction, Bounds, SelectedCropArea);
// Determines minimum value on both axes
newCoords := ApplyDimRestriction(newCoords, Direction, Bounds, SelectedCropArea.getRealKeepAspectRatio);
SelectedCropArea.ScaledArea := Rect(newCoords.x1, newCoords.y1, newCoords.x2, newCoords.y2);
finally
needRepaint := True;
end;
end;
begin
// Call the inherited MouseMove() procedure
inherited MouseMove(Shift, X, Y);
// Set default cursor
Cursor := crDefault;
// Find the working area of the component
WorkRect := GetWorkRect;
// If the mouse was originally clicked on the control
if fMouseCaught
then begin
// Assume we don't need to repaint the control
needRepaint := False;
// Determines limite values
Bounds := getImageRect(fResampledBitmap);
// If no anchor selected
if (fAnchorSelected = [])
then newSelection
else begin
// Get the actual point
fEndPoint := Point(X - WorkRect.Left, Y - WorkRect.Top);
// Check what the anchor was dragged
if (fAnchorSelected = [NORTH, SOUTH, EAST, WEST])
then moveCropping
else resizeCropping;
end;
// If we need to repaint
if needRepaint then
begin
SelectedCropArea.CalculateAreaFromScaledArea;
if assigned(rOnCropAreaChanged)
then rOnCropAreaChanged(Self, SelectedCropArea);
// Invalidate the control for repainting
Render;
Refresh;
end;
end
else begin
// If the mouse is just moving over the control, and wasn't originally click in the control
if ((X >= WorkRect.Left) and (X <= WorkRect.Right) and
(Y >= WorkRect.Top) and (Y <= WorkRect.Bottom)) then
begin
// Mouse is inside the pressable part of the control
Cursor := crCross;
fAnchorSelected := [];
fEndPoint := Point(X - WorkRect.Left, Y - WorkRect.Top);
// Verifies that is positioned on an anchor
ACursor := crDefault;
overCropArea :=Self.isOverAnchor(fEndPoint, fAnchorSelected, ACursor);
Cursor :=ACursor;
end;
end;
end;
procedure TBGRAImageManipulation.MouseUp(Button: TMouseButton;
Shift: TShiftState; X, Y: integer);
var
needRepaint: boolean;
temp: integer;
curCropAreaRect :TRect;
begin
// Call the inherited MouseUp() procedure
inherited MouseUp(Button, Shift, X, Y);
// If the mouse was originally clicked over the control
if (fMouseCaught) then
begin
// Show that the mouse is no longer caught
fMouseCaught := False;
// Assume we don't need to repaint the control
needRepaint := False;
if (rNewCropArea = Nil)
then begin
if (ssAlt in Shift)
then begin
SelectedCropArea.ScaledArea :=fStartArea;
needRepaint :=True;
end
end
else begin // Ends a new selection of cropping area
if (ssAlt in Shift)
then begin
delCropArea(rNewCropArea);
rNewCropArea :=Nil;
needRepaint :=False;
end
else begin
SelectedCropArea :=rNewCropArea;
rNewCropArea :=Nil;
curCropAreaRect :=SelectedCropArea.ScaledArea;
if (curCropAreaRect.Left > curCropAreaRect.Right) then
begin
// Swap left and right coordinates
temp := curCropAreaRect.Left;
curCropAreaRect.Left := curCropAreaRect.Right;
curCropAreaRect.Right := temp;
end;
if (curCropAreaRect.Top > curCropAreaRect.Bottom) then
begin
// Swap Top and Bottom coordinates
temp := curCropAreaRect.Top;
curCropAreaRect.Top := curCropAreaRect.Bottom;
curCropAreaRect.Bottom := temp;
end;
needRepaint :=True;
end;
end;
fAnchorSelected := [];
// If we need to repaint
if needRepaint then
begin
SelectedCropArea.CalculateAreaFromScaledArea;
if assigned(rOnCropAreaChanged)
then rOnCropAreaChanged(Self, SelectedCropArea);
// Invalidate the control for repainting
Render;
Refresh;
end;
end;
end;
procedure TBGRAImageManipulation.DoContextPopup(MousePos: TPoint; var Handled: Boolean);
var
xAnchorSelected :TDirection;
xCursor :TCursor;
mouseCropArea:TCropArea;
begin
if Assigned(rOnContextPopup) then
begin
mouseCropArea :=Self.isOverAnchor(MousePos, xAnchorSelected, {%H-}xCursor);
rOnContextPopup(Self, mouseCropArea, xAnchorSelected, MousePos, Handled);
end;
end;
{ ============================================================================ }
{ =====[ Register Function ]================================================== }
{ ============================================================================ }
{$IFDEF FPC}
procedure Register;
begin
RegisterComponents('BGRA Controls', [TBGRAImageManipulation]);
end;
{$ENDIF}
end.