From f202d75958adfd90b7fbafddafe6bccaa438ce2f Mon Sep 17 00:00:00 2001 From: circular17 Date: Wed, 9 Aug 2023 16:43:24 +0200 Subject: [PATCH 01/34] compatibility with older versions of FPC and BGRABitmap --- bgraimagemanipulation.pas | 4 ++++ bgraknob.pas | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index 1af95f4..ddc218f 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -831,9 +831,11 @@ function TCropArea.getResampledBitmap(OriginalRect :TRect): TBGRABitmap; // Create bitmap to put image on final scale Result := TBGRABitmap.Create(rScaledArea.Width, rScaledArea.Height); + {$if BGRABitmapVersion > 11050400} Result.ResolutionUnit:=CropBitmap.ResolutionUnit; Result.ResolutionX:=CropBitmap.ResolutionX; Result.ResolutionY:=CropBitmap.ResolutionY; + {$endif} // Resize the cropped image to final scale ResampledBitmap := CropBitmap.Resample(rScaledArea.Width, rScaledArea.Height, rmFineResample); @@ -856,9 +858,11 @@ function TCropArea.getBitmap: TBGRABitmap; try // Get the cropped image on selected region in original scale Result :=fOwner.fImageBitmap.GetPart(rArea); + {$if BGRABitmapVersion > 11050400} Result.ResolutionUnit:=fOwner.fImageBitmap.ResolutionUnit; Result.ResolutionX:=fOwner.fImageBitmap.ResolutionX; Result.ResolutionY:=fOwner.fImageBitmap.ResolutionY; + {$endif} except if (Result<>nil) then FreeAndNil(Result); diff --git a/bgraknob.pas b/bgraknob.pas index a93caa2..f0e979b 100644 --- a/bgraknob.pas +++ b/bgraknob.pas @@ -160,7 +160,7 @@ procedure TBGRAKnob.CreateKnobBmp; v.x := v.x /(tx / 2 + 1); v.y := v.y / (ty / 2 + 1); //compute squared distance with scalar product - d2 := v ** v; + d2 := v {$if FPC_FULLVERSION < 030301}*{$ELSE}**{$ENDIF} v; //interpolate as quadratic curve and apply power function if d2 > 1 then h := 0 From 76dad2dd9f65a390180b6eadb4230455e57a3138 Mon Sep 17 00:00:00 2001 From: Massimo Magnano Date: Tue, 22 Aug 2023 12:52:46 +0200 Subject: [PATCH 02/34] CropArea Null Rect; Load/Save Events; Nullsize Bitmap Test Crop.Area Null Rect when created with mouse; Load/Save Events; Test if Bitmap is Nullsize in Calculate Areas --- bgraimagemanipulation.pas | 66 +++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index ddc218f..4a9baf4 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -226,6 +226,10 @@ TCropAreaList = class(TObjectList) { TBGRAImageManipulation } TCropAreaEvent = procedure (AOwner: TBGRAImageManipulation; CropArea: TCropArea) of object; + TCropAreaLoadEvent = function (AOwner: TBGRAImageManipulation; CropArea: TCropArea; + const XMLConf: TXMLConfig; const Path:String):Integer of object; + TCropAreaSaveEvent = procedure (AOwner: TBGRAImageManipulation; CropArea: TCropArea; + const XMLConf: TXMLConfig; const Path:String) of object; TBGRAImageManipulation = class(TBGRAGraphicCtrl) private @@ -268,6 +272,8 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) rOnCropAreaDeleted: TCropAreaEvent; rOnCropAreaChanged: TCropAreaEvent; rOnSelectedCropAreaChanged: TCropAreaEvent; + rOnCropAreaLoad: TCropAreaLoadEvent; + rOnCropAreaSave: TCropAreaSaveEvent; function ApplyDimRestriction(Coords: TCoord; Direction: TDirection; Bounds: TRect; AKeepAspectRatio:Boolean): TCoord; function ApplyRatioToAxes(Coords: TCoord; Direction: TDirection; Bounds: TRect; ACropArea :TCropArea = Nil): TCoord; @@ -332,6 +338,8 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) 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; @@ -563,15 +571,20 @@ procedure TCropArea.CalculateScaledAreaFromArea; begin if not(isNullSize) then begin - { #todo 2 : Test for Null Bitmap } - // Calculate source rectangle in original scale - xRatio := fOwner.fResampledBitmap.Width / fOwner.fImageBitmap.Width; - yRatio := fOwner.fResampledBitmap.Height / fOwner.fImageBitmap.Height; - - rScaledArea.Left := Round(rArea.Left * xRatio); - rScaledArea.Right := Round(rArea.Right * xRatio); - rScaledArea.Top := Round(rArea.Top * yRatio); - rScaledArea.Bottom := Round(rArea.Bottom * yRatio); + if (fOwner.fImageBitmap.Width=0) or (fOwner.fImageBitmap.Height=0) + then begin // Null size Image Area=ScaledArea + xRatio :=1; + yRatio :=1; + end + else begin // Calculate Scale + xRatio := fOwner.fResampledBitmap.Width / fOwner.fImageBitmap.Width; + yRatio := fOwner.fResampledBitmap.Height / fOwner.fImageBitmap.Height; + end; + + rScaledArea.Left := Round(rArea.Left * xRatio); + rScaledArea.Right := Round(rArea.Right * xRatio); + rScaledArea.Top := Round(rArea.Top * yRatio); + rScaledArea.Bottom := Round(rArea.Bottom * yRatio); end; end; @@ -580,20 +593,21 @@ procedure TCropArea.CalculateAreaFromScaledArea; xRatio, yRatio: double; begin - if not(isNullSize) then - begin - { #todo 2 : Test for Null Bitmap } - // Calculate source rectangle in original scale - xRatio := fOwner.fResampledBitmap.Width / fOwner.fImageBitmap.Width; - yRatio := fOwner.fResampledBitmap.Height / fOwner.fImageBitmap.Height; - - rArea.Left := Round(rScaledArea.Left / xRatio); - rArea.Right := Round(rScaledArea.Right / xRatio); - rArea.Top := Round(rScaledArea.Top / yRatio); - rArea.Bottom := Round(rScaledArea.Bottom / yRatio); - end; -end; + 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; + rArea.Left := Round(rScaledArea.Left / xRatio); + rArea.Right := Round(rScaledArea.Right / xRatio); + rArea.Top := Round(rScaledArea.Top / yRatio); + rArea.Bottom := Round(rScaledArea.Bottom / yRatio); +end; procedure TCropArea.CopyAspectFromParent; begin @@ -966,6 +980,10 @@ procedure TCropAreaList.Load(const XMLConf: TXMLConfig); newCropArea.KeepAspectRatio :=BoolParent(XMLConf.GetValue('KeepAspectRatio', Integer(bParent))); newCropArea.AspectRatio :=XMLConf.GetValue('AspectRatio', '3:4'); newCropArea.Rotate :=StrToFloat(XMLConf.GetValue('Rotate', '0')); + + if assigned(fOwner.rOnCropAreaLoad) + then newCropArea.UserData :=fOwner.rOnCropAreaLoad(fOwner, newCropArea, XMLConf, + fOwner.Name+'.'+Self.Name+'/'+curItemPath); XMLConf.CloseKey; add(newCropArea); @@ -1008,7 +1026,9 @@ procedure TCropAreaList.Save(const XMLConf: TXMLConfig); XMLConf.SetValue('Width', Items[i].Area.Width); XMLConf.SetValue('Height', Items[i].Area.Height); XMLConf.CloseKey; - XMLConf.CloseKey; + if assigned(fOwner.rOnCropAreaSave) + then fOwner.rOnCropAreaSave(fOwner, Items[i], XMLConf, fOwner.Name+'.'+Self.Name+'/'+curItemPath); + XMLConf.CloseKey; end; XMLConf.CloseKey; end; From b554f98f06253081a259fd8c600b09aabd3ee6f3 Mon Sep 17 00:00:00 2001 From: Massimo Magnano Date: Fri, 25 Aug 2023 13:50:36 +0200 Subject: [PATCH 03/34] CropArea.Area property can be specified in Pixels,Cm,Inch the CropArea.Area property can be specified in Pixels,Cm,Inch Updated Demo --- bgraimagemanipulation.pas | 263 +++++++--- .../unitbgraimagemanipulationdemo.lfm | 460 ++++-------------- .../unitbgraimagemanipulationdemo.pas | 66 ++- 3 files changed, 351 insertions(+), 438 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index 4a9baf4..986230c 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -65,6 +65,7 @@ - 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 ============================================================================ } @@ -78,9 +79,9 @@ interface uses - Classes, Contnrs, SysUtils, {$IFDEF FPC}LCLIntf, LResources,{$ENDIF} + Classes, Contnrs, SysUtils, {$IFDEF FPC}LCLIntf, LResources, FPImage, {$ENDIF} Forms, Controls, Graphics, Dialogs, - {$IFNDEF FPC}Windows, Messages, BGRAGraphics, GraphType, FPImage, {$ENDIF} + {$IFNDEF FPC}Windows, Messages, BGRAGraphics, GraphType, {$ENDIF} XMLConf, BCBaseCtrls, BGRABitmap, BGRABitmapTypes, BGRAGradientScanner; {$IFNDEF FPC} @@ -133,8 +134,9 @@ TBGRAImageManipulation = class; TCropArea = class(TObject) protected fOwner :TBGRAImageManipulation; - rScaledArea, - rArea :TRect; + rScaledArea:TRect; + rArea :TRectF; + rAreaUnit:TResolutionUnit; rRatio :TRatio; rAspectX, rAspectY, @@ -148,24 +150,27 @@ TCropArea = class(TObject) procedure setAspectRatio(AValue: string); procedure setKeepAspectRatio(AValue: BoolParent); procedure setScaledArea(AValue: TRect); - function getLeft: Longint; - procedure setLeft(AValue: Longint); - function getTop: Longint; - procedure setTop(AValue: Longint); - function getWidth: Longint; - procedure setWidth(AValue: Longint); - function getHeight: Longint; - procedure setHeight(AValue: Longint); + 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 getRealAspectRatio(var ARatio: TRatio):Boolean; //return Real KeepAspect function getRealKeepAspectRatio:Boolean; function getIndex: Longint; function getIsNullSize: Boolean; - procedure setArea(AValue: TRect); + procedure setArea(AValue: TRectF); + procedure setAreaUnit(AValue: TResolutionUnit); procedure setName(AValue: String); procedure Render_Refresh; + procedure CalculateScaledAreaFromArea; procedure CalculateAreaFromScaledArea; + function GetPixelArea(const AValue: TRectF):TRect; property ScaledArea :TRect read rScaledArea write setScaledArea; @@ -177,16 +182,18 @@ TCropArea = class(TObject) function getResampledBitmap(OriginalRect: TRect): TBGRABitmap; function getBitmap: TBGRABitmap; - constructor Create(AOwner: TBGRAImageManipulation; AArea: TRect; + constructor Create(AOwner: TBGRAImageManipulation; AArea: TRectF; + AAreaUnit: TResolutionUnit = ruNone; //Pixels ARotate: double = 0; AUserData: Integer = 0); destructor Destroy; override; - property Area :TRect read rArea write setArea; - property Top:Longint read getTop write setTop; - property Left:Longint read getLeft write setLeft; - property Width:Longint read getWidth write setWidth; - property Height:Longint read getHeight write setHeight; + 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 AspectRatio: string read rAspectRatio write setAspectRatio; property KeepAspectRatio: BoolParent read rKeepAspectRatio write setKeepAspectRatio default bParent; property Index:Longint read getIndex; @@ -309,7 +316,8 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) procedure tests; //Crop Areas Manipulation functions - function addCropArea(AArea : TRect; ARotate :double = 0; AUserData: Integer = 0) :TCropArea; + function addCropArea(AArea : TRectF; AAreaUnit: TResolutionUnit = ruNone; + ARotate :double = 0; AUserData: Integer = 0) :TCropArea; function addScaledCropArea(AArea : TRect; ARotate :double = 0; AUserData: Integer = 0) :TCropArea; procedure delCropArea(ACropArea :TCropArea); procedure clearCropAreas; @@ -328,8 +336,7 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) 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 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; @@ -449,14 +456,14 @@ procedure TCropArea.setName(AValue: String); then fOwner.rOnCropAreaChanged(fOwner, Self); end; -function TCropArea.getTop: Longint; +function TCropArea.getTop: Single; begin Result :=rArea.Top; end; -procedure TCropArea.setTop(AValue: Longint); +procedure TCropArea.setTop(AValue: Single); var - tempHeight :Longint; + tempHeight :Single; begin if AValue=rArea.Top then Exit; @@ -473,14 +480,14 @@ procedure TCropArea.setTop(AValue: Longint); then fOwner.rOnCropAreaChanged(fOwner, Self); end; -function TCropArea.getLeft: Longint; +function TCropArea.getLeft: Single; begin Result :=rArea.Left; end; -procedure TCropArea.setLeft(AValue: Longint); +procedure TCropArea.setLeft(AValue: Single); var - tempWidth :Longint; + tempWidth :Single; begin if AValue=rArea.Left then Exit; @@ -497,12 +504,12 @@ procedure TCropArea.setLeft(AValue: Longint); then fOwner.rOnCropAreaChanged(fOwner, Self); end; -function TCropArea.getHeight: Longint; +function TCropArea.getHeight: Single; begin Result :=rArea.Height; end; -procedure TCropArea.setHeight(AValue: Longint); +procedure TCropArea.setHeight(AValue: Single); var curKeepAspectRatio :Boolean; curRatio :TRatio; @@ -528,12 +535,12 @@ procedure TCropArea.setHeight(AValue: Longint); then fOwner.rOnCropAreaChanged(fOwner, Self); end; -function TCropArea.getWidth: Longint; +function TCropArea.getWidth: Single; begin Result :=rArea.Width; end; -procedure TCropArea.setWidth(AValue: Longint); +procedure TCropArea.setWidth(AValue: Single); var curKeepAspectRatio :Boolean; curRatio :TRatio; @@ -566,47 +573,144 @@ function TCropArea.getIndex: Longint; procedure TCropArea.CalculateScaledAreaFromArea; var - xRatio, yRatio: double; + xRatio, yRatio: Single; + resX, resY: Single; begin if not(isNullSize) then begin if (fOwner.fImageBitmap.Width=0) or (fOwner.fImageBitmap.Height=0) - then begin // Null size Image Area=ScaledArea - xRatio :=1; - yRatio :=1; + then begin // Null size Image ScaledArea=Area + rScaledArea.Left := Trunc(rArea.Left); + rScaledArea.Right := Trunc(rArea.Right); + rScaledArea.Top := Trunc(rArea.Top); + rScaledArea.Bottom := Trunc(rArea.Bottom); end - else begin // Calculate Scale + else begin // Calculate Scaled Area given Scale and Resolution xRatio := fOwner.fResampledBitmap.Width / fOwner.fImageBitmap.Width; yRatio := fOwner.fResampledBitmap.Height / fOwner.fImageBitmap.Height; - end; + resX :=1; + resY :=1; + + if (rAreaUnit<>ruNone) then + begin + if (fOwner.fImageBitmap.ResolutionX>0) + then resX :=fOwner.fImageBitmap.ResolutionX; + if (fOwner.fImageBitmap.ResolutionY>0) + then resY :=fOwner.fImageBitmap.ResolutionY; + + //Do Conversion from/to inch/cm + if (rAreaUnit <> fOwner.fImageBitmap.ResolutionUnit) then + begin + if (rAreaUnit=ruPixelsPerInch) + then begin //Bitmap is in Cm + resX :=resX/2.54; + resY :=resY/2.54; + end + else begin //Bitmap is in Inch + resX :=resX*2.54; + resY :=resY*2.54; + end + end; + end; - rScaledArea.Left := Round(rArea.Left * xRatio); - rScaledArea.Right := Round(rArea.Right * xRatio); - rScaledArea.Top := Round(rArea.Top * yRatio); - rScaledArea.Bottom := Round(rArea.Bottom * yRatio); + rScaledArea.Left := Round(rArea.Left * resX * xRatio); + rScaledArea.Right := Round(rArea.Right* resX * xRatio); + rScaledArea.Top := Round(rArea.Top * resY * yRatio); + rScaledArea.Bottom := Round(rArea.Bottom * resY * yRatio); + end; end; end; procedure TCropArea.CalculateAreaFromScaledArea; var - xRatio, yRatio: double; + xRatio, yRatio: Single; + resX, resY: Single; begin if (fOwner.fImageBitmap.Width=0) or (fOwner.fImageBitmap.Height=0) - then begin - xRatio :=1; - yRatio :=1; + then begin // Null size Image Area=ScaledArea + { #note : In wich Resolution Unit? } + rArea.Left := rScaledArea.Left; + rArea.Right := rScaledArea.Right; + rArea.Top := rScaledArea.Top; + rArea.Bottom := rScaledArea.Bottom; end - else begin + else begin // Calculate Scaled Area given Scale and Resolution xRatio := fOwner.fResampledBitmap.Width / fOwner.fImageBitmap.Width; yRatio := fOwner.fResampledBitmap.Height / fOwner.fImageBitmap.Height; - end; + resX :=1; + resY :=1; - rArea.Left := Round(rScaledArea.Left / xRatio); - rArea.Right := Round(rScaledArea.Right / xRatio); - rArea.Top := Round(rScaledArea.Top / yRatio); - rArea.Bottom := Round(rScaledArea.Bottom / yRatio); + if (rAreaUnit<>ruNone) then + begin + if (fOwner.fImageBitmap.ResolutionX>0) + then resX :=fOwner.fImageBitmap.ResolutionX; + if (fOwner.fImageBitmap.ResolutionY>0) + then resY :=fOwner.fImageBitmap.ResolutionY; + + //Do Conversion from/to inch/cm + if (rAreaUnit <> fOwner.fImageBitmap.ResolutionUnit) then + begin + if (rAreaUnit=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; + + 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; +end; + +function TCropArea.GetPixelArea(const AValue: TRectF): TRect; +var + resX, resY: Single; + +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 + resX :=1; + resY :=1; + + if (fOwner.fImageBitmap.ResolutionX>0) + then resX :=fOwner.fImageBitmap.ResolutionX; + if (fOwner.fImageBitmap.ResolutionY>0) + then resY :=fOwner.fImageBitmap.ResolutionY; + + //Do Conversion from/to inch/cm + if (rAreaUnit <> fOwner.fImageBitmap.ResolutionUnit) then + begin + if (rAreaUnit=ruPixelsPerInch) + then begin //Bitmap is in Cm + resX :=resX/2.54; + resY :=resY/2.54; + end + else begin //Bitmap is in Inch + resX :=resX*2.54; + resY :=resY*2.54; + end + end; + + Result.Left := Round(AValue.Left * resX); + Result.Right := Round(AValue.Right* resX); + Result.Top := Round(AValue.Top * resY); + Result.Bottom := Round(AValue.Bottom * resY); + end; end; procedure TCropArea.CopyAspectFromParent; @@ -689,11 +793,11 @@ procedure TCropArea.setKeepAspectRatio(AValue: BoolParent); Render_Refresh; end; -procedure TCropArea.setArea(AValue: TRect); +procedure TCropArea.setArea(AValue: TRectF); var curKeepAspectRatio :Boolean; curRatio :TRatio; - calcHeight, calcWidth, swapV :Longint; + calcHeight, calcWidth, swapV :Single; begin if rArea=AValue then Exit; @@ -747,6 +851,19 @@ procedure TCropArea.setArea(AValue: TRect); then fOwner.rOnCropAreaChanged(fOwner, Self); end; +procedure TCropArea.setAreaUnit(AValue: TResolutionUnit); +begin + if rAreaUnit=AValue then Exit; + + rAreaUnit:=AValue; + + { #todo 2 : Fare conversione Area } + + if assigned(fOwner.rOnCropAreaChanged) + then fOwner.rOnCropAreaChanged(fOwner, Self); +end; + + procedure TCropArea.setScaledArea(AValue: TRect); var curKeepAspectRatio :Boolean; @@ -871,7 +988,7 @@ function TCropArea.getBitmap: TBGRABitmap; if not (fOwner.fImageBitmap.Empty) then try // Get the cropped image on selected region in original scale - Result :=fOwner.fImageBitmap.GetPart(rArea); + Result :=fOwner.fImageBitmap.GetPart(GetPixelArea(rArea)); {$if BGRABitmapVersion > 11050400} Result.ResolutionUnit:=fOwner.fImageBitmap.ResolutionUnit; Result.ResolutionX:=fOwner.fImageBitmap.ResolutionX; @@ -883,14 +1000,14 @@ function TCropArea.getBitmap: TBGRABitmap; end; end; -constructor TCropArea.Create(AOwner: TBGRAImageManipulation; AArea :TRect; - ARotate :double = 0; - AUserData : Integer = 0); +constructor TCropArea.Create(AOwner: TBGRAImageManipulation; AArea: TRectF; + AAreaUnit: TResolutionUnit; ARotate: double; AUserData: Integer); begin inherited Create; if (AOwner = Nil) then raise Exception.Create('Owner TBGRAImageManipulation is Nil'); fOwner :=AOwner; + rAreaUnit :=AAreaUnit; Area := AArea; Rotate := ARotate; UserData := AUserData; @@ -950,7 +1067,8 @@ procedure TCropAreaList.Load(const XMLConf: TXMLConfig); i, newCount, newSelected: integer; curItemPath: String; newCropArea: TCropArea; - newArea: TRect; + newArea: TRectF; + newAreaUnit:TResolutionUnit; begin try @@ -970,12 +1088,13 @@ procedure TCropAreaList.Load(const XMLConf: TXMLConfig); newArea :=Rect(0,0,0,0); XMLConf.OpenKey(curItemPath); XMLConf.OpenKey('Area'); - newArea.Left :=XMLConf.GetValue('Left', 0); - newArea.Top :=XMLConf.GetValue('Top', 0); - newArea.Width :=XMLConf.GetValue('Width', fOwner.MinWidth); - newArea.Height :=XMLConf.GetValue('Height', fOwner.MinHeight); + newArea.Left :=StrToFloat(XMLConf.GetValue('Left', '0')); + newArea.Top :=StrToFloat(XMLConf.GetValue('Top', '0')); + newArea.Width :=StrToFloat(XMLConf.GetValue('Width', IntToStr(fOwner.MinWidth))); + newArea.Height :=StrToFloat(XMLConf.GetValue('Height', IntToStr(fOwner.MinHeight))); XMLConf.CloseKey; - newCropArea :=TCropArea.Create(Self.fOwner, newArea); + newAreaUnit :=TResolutionUnit(XMLConf.GetValue('AreaUnit', 0)); + newCropArea :=TCropArea.Create(Self.fOwner, newArea ); newCropArea.Name :=XMLConf.GetValue('Name', 'Name '+IntToStr(i)); newCropArea.KeepAspectRatio :=BoolParent(XMLConf.GetValue('KeepAspectRatio', Integer(bParent))); newCropArea.AspectRatio :=XMLConf.GetValue('AspectRatio', '3:4'); @@ -1020,11 +1139,12 @@ procedure TCropAreaList.Save(const XMLConf: TXMLConfig); XMLConf.SetValue('KeepAspectRatio', Integer(Items[i].KeepAspectRatio)); XMLConf.SetValue('AspectRatio', Items[i].AspectRatio); XMLConf.SetValue('Rotate', FloatToStr(Items[i].Rotate)); + XMLConf.SetValue('AreaUnit', Integer(Items[i].AreaUnit)); XMLConf.OpenKey('Area'); - XMLConf.SetValue('Left', Items[i].Area.Left); - XMLConf.SetValue('Top', Items[i].Area.Top); - XMLConf.SetValue('Width', Items[i].Area.Width); - XMLConf.SetValue('Height', Items[i].Area.Height); + XMLConf.SetValue('Left', FloatToStr(Items[i].Area.Left)); + XMLConf.SetValue('Top', FloatToStr(Items[i].Area.Top)); + XMLConf.SetValue('Width', FloatToStr(Items[i].Area.Width)); + XMLConf.SetValue('Height', FloatToStr(Items[i].Area.Height)); XMLConf.CloseKey; if assigned(fOwner.rOnCropAreaSave) then fOwner.rOnCropAreaSave(fOwner, Items[i], XMLConf, fOwner.Name+'.'+Self.Name+'/'+curItemPath); @@ -2608,13 +2728,14 @@ procedure TBGRAImageManipulation.tests; // Refresh; end; -function TBGRAImageManipulation.addCropArea(AArea: TRect; ARotate: double; AUserData: Integer): TCropArea; +function TBGRAImageManipulation.addCropArea(AArea: TRectF; AAreaUnit: TResolutionUnit; + ARotate: double; AUserData: Integer): TCropArea; var newCropArea :TCropArea; begin try - newCropArea :=TCropArea.Create(Self, AArea, ARotate, AUserData); + newCropArea :=TCropArea.Create(Self, AArea, AAreaUnit, ARotate, AUserData); newCropArea.BorderColor :=BGRAWhite; rCropAreas.add(newCropArea); @@ -2637,7 +2758,7 @@ function TBGRAImageManipulation.addCropArea(AArea: TRect; ARotate: double; AUser function TBGRAImageManipulation.addScaledCropArea(AArea: TRect; ARotate: double; AUserData: Integer): TCropArea; begin - Result :=Self.addCropArea(Rect(0,0,0,0), ARotate, AUserData); + Result :=Self.addCropArea(Rect(0,0,0,0), ruNone, ARotate, AUserData); Result.ScaledArea :=AArea; if (fMouseCaught) diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm index 34deeeb..523f945 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm @@ -1,18 +1,18 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Left = 417 - Height = 460 + Height = 486 Top = 136 Width = 847 Caption = 'Demonstration of TBGRAImageManipulation' - ClientHeight = 460 + ClientHeight = 486 ClientWidth = 847 OnCloseQuery = FormCloseQuery OnCreate = FormCreate ShowHint = True - LCLVersion = '2.2.6.0' + LCLVersion = '3.99.0.0' object Background: TBCPanel Left = 627 - Height = 460 + Height = 486 Top = 0 Width = 220 Align = alRight @@ -58,10 +58,10 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo TabOrder = 0 end object KeepAspectRatio: TCheckBox - Left = 20 + Left = 25 Height = 19 - Top = 104 - Width = 117 + Top = 176 + Width = 115 Caption = 'Keep aspect ratio' Checked = True Color = clWhite @@ -87,9 +87,9 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Transparent = False end object lbCompression: TLabel - Left = 20 + Left = 25 Height = 15 - Top = 208 + Top = 280 Width = 113 Caption = 'Compression adjust :' Font.Color = clWhite @@ -98,17 +98,17 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo ParentFont = False end object edAspectRatio: TEdit - Left = 118 + Left = 123 Height = 23 - Top = 132 + Top = 204 Width = 44 TabOrder = 2 Text = '3:4' end object lbAspectRatio: TLabel - Left = 20 + Left = 25 Height = 15 - Top = 136 + Top = 208 Width = 76 Caption = 'Aspect Ratio :' Color = clBlack @@ -286,9 +286,9 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo MemoryUsage = bmuHigh end object btnSavePicture: TBCButton - Left = 10 + Left = 15 Height = 40 - Top = 368 + Top = 440 Width = 200 StateClicked.Background.Gradient1.StartColor = 8404992 StateClicked.Background.Gradient1.EndColor = 4194304 @@ -453,10 +453,10 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo MemoryUsage = bmuHigh end object btnSetAspectRatio: TBCButton - Left = 170 + Left = 175 Height = 28 Hint = 'Apply new aspect ratio' - Top = 132 + Top = 204 Width = 40 StateClicked.Background.Gradient1.StartColor = 8404992 StateClicked.Background.Gradient1.EndColor = 4194304 @@ -620,10 +620,10 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo MemoryUsage = bmuHigh end object btnGetAspectRatioFromImage: TBCButton - Left = 20 + Left = 25 Height = 30 Hint = 'Get aspect ratio from image' - Top = 168 + Top = 240 Width = 30 StateClicked.Background.Gradient1.StartColor = 8404992 StateClicked.Background.Gradient1.EndColor = 4194304 @@ -787,10 +787,10 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo MemoryUsage = bmuHigh end object btnRotateLeft: TBCButton - Left = 64 + Left = 69 Height = 30 Hint = 'Rotate Left' - Top = 168 + Top = 240 Width = 30 StateClicked.Background.Gradient1.StartColor = 8404992 StateClicked.Background.Gradient1.EndColor = 4194304 @@ -954,10 +954,10 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo MemoryUsage = bmuHigh end object btnRotateRight: TBCButton - Left = 100 + Left = 105 Height = 30 Hint = 'Rotate Right' - Top = 168 + Top = 240 Width = 30 StateClicked.Background.Gradient1.StartColor = 8404992 StateClicked.Background.Gradient1.EndColor = 4194304 @@ -1121,9 +1121,9 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo MemoryUsage = bmuHigh end object btnShape: TBCButton - Left = 10 + Left = 15 Height = 48 - Top = 224 + Top = 296 Width = 200 StateClicked.Background.Gradient1.StartColor = 8404992 StateClicked.Background.Gradient1.EndColor = 4194304 @@ -1211,9 +1211,9 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo MemoryUsage = bmuHigh end object btnSavePictureAll: TBCButton - Left = 10 + Left = 15 Height = 40 - Top = 320 + Top = 392 Width = 200 StateClicked.Background.Gradient1.StartColor = 8404992 StateClicked.Background.Gradient1.EndColor = 4194304 @@ -1378,19 +1378,26 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo MemoryUsage = bmuHigh end object chkFullSize: TCheckBox - Left = 24 + Left = 29 Height = 19 - Top = 288 - Width = 152 + Top = 360 + Width = 150 Caption = 'Save Original Size picture' Checked = True State = cbChecked TabOrder = 3 end + object lbResolution: TLabel + Left = 24 + Height = 15 + Top = 95 + Width = 65 + Caption = 'Resolution : ' + end end object BGRAImageManipulation: TBGRAImageManipulation Left = 170 - Height = 460 + Height = 486 Top = 0 Width = 457 Align = alClient @@ -1405,7 +1412,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo end object BCPanelCropAreas: TBCPanel Left = 0 - Height = 460 + Height = 486 Top = 0 Width = 170 Align = alLeft @@ -1492,7 +1499,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo object BCPanelCropAreaLoad: TBCPanel Left = 1 Height = 106 - Top = 353 + Top = 379 Width = 168 Align = alBottom Background.Color = clBtnFace @@ -1976,7 +1983,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo end object BCPanelCropArea: TBCPanel Left = 0 - Height = 300 + Height = 320 Top = 48 Width = 170 Background.Color = clBtnFace @@ -2044,82 +2051,6 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Rounding.RoundX = 1 Rounding.RoundY = 1 end - object edLeft: TBCTrackbarUpdown - Left = 56 - Height = 24 - Top = 64 - Width = 103 - AllowNegativeValues = False - BarExponent = 1 - Increment = 1 - LongTimeInterval = 400 - MinValue = 0 - MaxValue = 100 - OnChange = edLeftChange - Value = 50 - ShortTimeInterval = 100 - Background.Color = clWindow - Background.Gradient1.StartColor = clWhite - Background.Gradient1.EndColor = clBlack - Background.Gradient1.GradientType = gtLinear - Background.Gradient1.Point1XPercent = 0 - Background.Gradient1.Point1YPercent = 0 - Background.Gradient1.Point2XPercent = 0 - Background.Gradient1.Point2YPercent = 100 - Background.Gradient2.StartColor = clWhite - Background.Gradient2.EndColor = clBlack - Background.Gradient2.GradientType = gtLinear - Background.Gradient2.Point1XPercent = 0 - Background.Gradient2.Point1YPercent = 0 - Background.Gradient2.Point2XPercent = 0 - Background.Gradient2.Point2YPercent = 100 - Background.Gradient1EndPercent = 35 - Background.Style = bbsColor - ButtonBackground.Gradient1.StartColor = clBtnShadow - ButtonBackground.Gradient1.EndColor = clBtnFace - ButtonBackground.Gradient1.GradientType = gtLinear - ButtonBackground.Gradient1.Point1XPercent = 0 - ButtonBackground.Gradient1.Point1YPercent = -50 - ButtonBackground.Gradient1.Point2XPercent = 0 - ButtonBackground.Gradient1.Point2YPercent = 50 - ButtonBackground.Gradient2.StartColor = clBtnFace - ButtonBackground.Gradient2.EndColor = clBtnShadow - ButtonBackground.Gradient2.GradientType = gtLinear - ButtonBackground.Gradient2.Point1XPercent = 0 - ButtonBackground.Gradient2.Point1YPercent = 50 - ButtonBackground.Gradient2.Point2XPercent = 0 - ButtonBackground.Gradient2.Point2YPercent = 150 - ButtonBackground.Gradient1EndPercent = 50 - ButtonBackground.Style = bbsGradient - ButtonDownBackground.Color = clBtnShadow - ButtonDownBackground.Gradient1.StartColor = clWhite - ButtonDownBackground.Gradient1.EndColor = clBlack - ButtonDownBackground.Gradient1.GradientType = gtLinear - ButtonDownBackground.Gradient1.Point1XPercent = 0 - ButtonDownBackground.Gradient1.Point1YPercent = 0 - ButtonDownBackground.Gradient1.Point2XPercent = 0 - ButtonDownBackground.Gradient1.Point2YPercent = 100 - ButtonDownBackground.Gradient2.StartColor = clWhite - ButtonDownBackground.Gradient2.EndColor = clBlack - ButtonDownBackground.Gradient2.GradientType = gtLinear - ButtonDownBackground.Gradient2.Point1XPercent = 0 - ButtonDownBackground.Gradient2.Point1YPercent = 0 - ButtonDownBackground.Gradient2.Point2XPercent = 0 - ButtonDownBackground.Gradient2.Point2YPercent = 100 - ButtonDownBackground.Gradient1EndPercent = 35 - ButtonDownBackground.Style = bbsColor - Border.Color = clWindowText - Border.Style = bboSolid - Rounding.RoundX = 1 - Rounding.RoundY = 1 - Font.Color = clWindowText - Font.Name = 'Arial' - HasTrackBar = True - ArrowColor = clBtnText - TabOrder = 0 - TabStop = True - UseDockManager = False - end object BCLabel2: TBCLabel Left = 20 Height = 15 @@ -2153,82 +2084,6 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Rounding.RoundX = 1 Rounding.RoundY = 1 end - object edTop: TBCTrackbarUpdown - Left = 56 - Height = 24 - Top = 92 - Width = 103 - AllowNegativeValues = False - BarExponent = 1 - Increment = 1 - LongTimeInterval = 400 - MinValue = 0 - MaxValue = 100 - OnChange = edTopChange - Value = 50 - ShortTimeInterval = 100 - Background.Color = clWindow - Background.Gradient1.StartColor = clWhite - Background.Gradient1.EndColor = clBlack - Background.Gradient1.GradientType = gtLinear - Background.Gradient1.Point1XPercent = 0 - Background.Gradient1.Point1YPercent = 0 - Background.Gradient1.Point2XPercent = 0 - Background.Gradient1.Point2YPercent = 100 - Background.Gradient2.StartColor = clWhite - Background.Gradient2.EndColor = clBlack - Background.Gradient2.GradientType = gtLinear - Background.Gradient2.Point1XPercent = 0 - Background.Gradient2.Point1YPercent = 0 - Background.Gradient2.Point2XPercent = 0 - Background.Gradient2.Point2YPercent = 100 - Background.Gradient1EndPercent = 35 - Background.Style = bbsColor - ButtonBackground.Gradient1.StartColor = clBtnShadow - ButtonBackground.Gradient1.EndColor = clBtnFace - ButtonBackground.Gradient1.GradientType = gtLinear - ButtonBackground.Gradient1.Point1XPercent = 0 - ButtonBackground.Gradient1.Point1YPercent = -50 - ButtonBackground.Gradient1.Point2XPercent = 0 - ButtonBackground.Gradient1.Point2YPercent = 50 - ButtonBackground.Gradient2.StartColor = clBtnFace - ButtonBackground.Gradient2.EndColor = clBtnShadow - ButtonBackground.Gradient2.GradientType = gtLinear - ButtonBackground.Gradient2.Point1XPercent = 0 - ButtonBackground.Gradient2.Point1YPercent = 50 - ButtonBackground.Gradient2.Point2XPercent = 0 - ButtonBackground.Gradient2.Point2YPercent = 150 - ButtonBackground.Gradient1EndPercent = 50 - ButtonBackground.Style = bbsGradient - ButtonDownBackground.Color = clBtnShadow - ButtonDownBackground.Gradient1.StartColor = clWhite - ButtonDownBackground.Gradient1.EndColor = clBlack - ButtonDownBackground.Gradient1.GradientType = gtLinear - ButtonDownBackground.Gradient1.Point1XPercent = 0 - ButtonDownBackground.Gradient1.Point1YPercent = 0 - ButtonDownBackground.Gradient1.Point2XPercent = 0 - ButtonDownBackground.Gradient1.Point2YPercent = 100 - ButtonDownBackground.Gradient2.StartColor = clWhite - ButtonDownBackground.Gradient2.EndColor = clBlack - ButtonDownBackground.Gradient2.GradientType = gtLinear - ButtonDownBackground.Gradient2.Point1XPercent = 0 - ButtonDownBackground.Gradient2.Point1YPercent = 0 - ButtonDownBackground.Gradient2.Point2XPercent = 0 - ButtonDownBackground.Gradient2.Point2YPercent = 100 - ButtonDownBackground.Gradient1EndPercent = 35 - ButtonDownBackground.Style = bbsColor - Border.Color = clWindowText - Border.Style = bboSolid - Rounding.RoundX = 1 - Rounding.RoundY = 1 - Font.Color = clWindowText - Font.Name = 'Arial' - HasTrackBar = True - ArrowColor = clBtnText - TabOrder = 1 - TabStop = True - UseDockManager = False - end object BCLabel3: TBCLabel Left = 8 Height = 15 @@ -2262,82 +2117,6 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Rounding.RoundX = 1 Rounding.RoundY = 1 end - object edWidth: TBCTrackbarUpdown - Left = 56 - Height = 24 - Top = 120 - Width = 103 - AllowNegativeValues = False - BarExponent = 1 - Increment = 1 - LongTimeInterval = 400 - MinValue = 0 - MaxValue = 100 - OnChange = edWidthChange - Value = 50 - ShortTimeInterval = 100 - Background.Color = clWindow - Background.Gradient1.StartColor = clWhite - Background.Gradient1.EndColor = clBlack - Background.Gradient1.GradientType = gtLinear - Background.Gradient1.Point1XPercent = 0 - Background.Gradient1.Point1YPercent = 0 - Background.Gradient1.Point2XPercent = 0 - Background.Gradient1.Point2YPercent = 100 - Background.Gradient2.StartColor = clWhite - Background.Gradient2.EndColor = clBlack - Background.Gradient2.GradientType = gtLinear - Background.Gradient2.Point1XPercent = 0 - Background.Gradient2.Point1YPercent = 0 - Background.Gradient2.Point2XPercent = 0 - Background.Gradient2.Point2YPercent = 100 - Background.Gradient1EndPercent = 35 - Background.Style = bbsColor - ButtonBackground.Gradient1.StartColor = clBtnShadow - ButtonBackground.Gradient1.EndColor = clBtnFace - ButtonBackground.Gradient1.GradientType = gtLinear - ButtonBackground.Gradient1.Point1XPercent = 0 - ButtonBackground.Gradient1.Point1YPercent = -50 - ButtonBackground.Gradient1.Point2XPercent = 0 - ButtonBackground.Gradient1.Point2YPercent = 50 - ButtonBackground.Gradient2.StartColor = clBtnFace - ButtonBackground.Gradient2.EndColor = clBtnShadow - ButtonBackground.Gradient2.GradientType = gtLinear - ButtonBackground.Gradient2.Point1XPercent = 0 - ButtonBackground.Gradient2.Point1YPercent = 50 - ButtonBackground.Gradient2.Point2XPercent = 0 - ButtonBackground.Gradient2.Point2YPercent = 150 - ButtonBackground.Gradient1EndPercent = 50 - ButtonBackground.Style = bbsGradient - ButtonDownBackground.Color = clBtnShadow - ButtonDownBackground.Gradient1.StartColor = clWhite - ButtonDownBackground.Gradient1.EndColor = clBlack - ButtonDownBackground.Gradient1.GradientType = gtLinear - ButtonDownBackground.Gradient1.Point1XPercent = 0 - ButtonDownBackground.Gradient1.Point1YPercent = 0 - ButtonDownBackground.Gradient1.Point2XPercent = 0 - ButtonDownBackground.Gradient1.Point2YPercent = 100 - ButtonDownBackground.Gradient2.StartColor = clWhite - ButtonDownBackground.Gradient2.EndColor = clBlack - ButtonDownBackground.Gradient2.GradientType = gtLinear - ButtonDownBackground.Gradient2.Point1XPercent = 0 - ButtonDownBackground.Gradient2.Point1YPercent = 0 - ButtonDownBackground.Gradient2.Point2XPercent = 0 - ButtonDownBackground.Gradient2.Point2YPercent = 100 - ButtonDownBackground.Gradient1EndPercent = 35 - ButtonDownBackground.Style = bbsColor - Border.Color = clWindowText - Border.Style = bboSolid - Rounding.RoundX = 1 - Rounding.RoundY = 1 - Font.Color = clWindowText - Font.Name = 'Arial' - HasTrackBar = True - ArrowColor = clBtnText - TabOrder = 2 - TabStop = True - UseDockManager = False - end object BCLabel4: TBCLabel Left = 4 Height = 15 @@ -2371,82 +2150,6 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Rounding.RoundX = 1 Rounding.RoundY = 1 end - object edHeight: TBCTrackbarUpdown - Left = 56 - Height = 24 - Top = 148 - Width = 103 - AllowNegativeValues = False - BarExponent = 1 - Increment = 1 - LongTimeInterval = 400 - MinValue = 0 - MaxValue = 100 - OnChange = edHeightChange - Value = 50 - ShortTimeInterval = 100 - Background.Color = clWindow - Background.Gradient1.StartColor = clWhite - Background.Gradient1.EndColor = clBlack - Background.Gradient1.GradientType = gtLinear - Background.Gradient1.Point1XPercent = 0 - Background.Gradient1.Point1YPercent = 0 - Background.Gradient1.Point2XPercent = 0 - Background.Gradient1.Point2YPercent = 100 - Background.Gradient2.StartColor = clWhite - Background.Gradient2.EndColor = clBlack - Background.Gradient2.GradientType = gtLinear - Background.Gradient2.Point1XPercent = 0 - Background.Gradient2.Point1YPercent = 0 - Background.Gradient2.Point2XPercent = 0 - Background.Gradient2.Point2YPercent = 100 - Background.Gradient1EndPercent = 35 - Background.Style = bbsColor - ButtonBackground.Gradient1.StartColor = clBtnShadow - ButtonBackground.Gradient1.EndColor = clBtnFace - ButtonBackground.Gradient1.GradientType = gtLinear - ButtonBackground.Gradient1.Point1XPercent = 0 - ButtonBackground.Gradient1.Point1YPercent = -50 - ButtonBackground.Gradient1.Point2XPercent = 0 - ButtonBackground.Gradient1.Point2YPercent = 50 - ButtonBackground.Gradient2.StartColor = clBtnFace - ButtonBackground.Gradient2.EndColor = clBtnShadow - ButtonBackground.Gradient2.GradientType = gtLinear - ButtonBackground.Gradient2.Point1XPercent = 0 - ButtonBackground.Gradient2.Point1YPercent = 50 - ButtonBackground.Gradient2.Point2XPercent = 0 - ButtonBackground.Gradient2.Point2YPercent = 150 - ButtonBackground.Gradient1EndPercent = 50 - ButtonBackground.Style = bbsGradient - ButtonDownBackground.Color = clBtnShadow - ButtonDownBackground.Gradient1.StartColor = clWhite - ButtonDownBackground.Gradient1.EndColor = clBlack - ButtonDownBackground.Gradient1.GradientType = gtLinear - ButtonDownBackground.Gradient1.Point1XPercent = 0 - ButtonDownBackground.Gradient1.Point1YPercent = 0 - ButtonDownBackground.Gradient1.Point2XPercent = 0 - ButtonDownBackground.Gradient1.Point2YPercent = 100 - ButtonDownBackground.Gradient2.StartColor = clWhite - ButtonDownBackground.Gradient2.EndColor = clBlack - ButtonDownBackground.Gradient2.GradientType = gtLinear - ButtonDownBackground.Gradient2.Point1XPercent = 0 - ButtonDownBackground.Gradient2.Point1YPercent = 0 - ButtonDownBackground.Gradient2.Point2XPercent = 0 - ButtonDownBackground.Gradient2.Point2YPercent = 100 - ButtonDownBackground.Gradient1EndPercent = 35 - ButtonDownBackground.Style = bbsColor - Border.Color = clWindowText - Border.Style = bboSolid - Rounding.RoundX = 1 - Rounding.RoundY = 1 - Font.Color = clWindowText - Font.Name = 'Arial' - HasTrackBar = True - ArrowColor = clBtnText - TabOrder = 3 - TabStop = True - UseDockManager = False - end object edUnit_Type: TComboBox Left = 56 Height = 23 @@ -2457,11 +2160,12 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo ItemIndex = 0 Items.Strings = ( 'pixels' - 'mm' - 'percent' + 'inchs' + 'cm' ) + OnChange = edUnit_TypeChange Style = csDropDownList - TabOrder = 4 + TabOrder = 0 Text = 'pixels' end object BCLabel5: TBCLabel @@ -2537,20 +2241,20 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Width = 103 AutoSize = False OnEditingDone = edNameChange - TabOrder = 5 + TabOrder = 1 end object edAspectPersonal: TEdit - Left = 47 + Left = 48 Height = 23 - Top = 256 + Top = 282 Width = 87 AutoSize = False - TabOrder = 6 + TabOrder = 2 end object rgAspect: TRadioGroup - Left = 22 + Left = 23 Height = 76 - Top = 174 + Top = 200 Width = 137 AutoFill = True Caption = 'Aspect Ratio' @@ -2569,12 +2273,12 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo 'Personal' ) OnSelectionChanged = rgAspectSelectionChanged - TabOrder = 7 + TabOrder = 3 end object btApplyAspectRatio: TSpeedButton - Left = 135 + Left = 136 Height = 22 - Top = 257 + Top = 283 Width = 23 Glyph.Data = { C6070000424DC607000000000000360000002800000016000000160000000100 @@ -2644,14 +2348,66 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo OnClick = btApplyAspectRatioClick end object SpeedButton1: TSpeedButton - Left = 8 + Left = 9 Height = 19 - Top = 280 + Top = 306 Width = 32 AutoSize = True Caption = ':Tests' OnClick = SpeedButton1Click end + object edLeft: TFloatSpinEdit + Left = 56 + Height = 23 + Top = 64 + Width = 103 + Font.Color = clWindowText + Font.Name = 'Arial' + MaxValue = 100 + OnChange = edLeftChange + ParentFont = False + TabOrder = 4 + Value = 50 + end + object edTop: TFloatSpinEdit + Left = 56 + Height = 23 + Top = 92 + Width = 103 + Font.Color = clWindowText + Font.Name = 'Arial' + MaxValue = 100 + OnChange = edTopChange + ParentFont = False + TabOrder = 5 + Value = 50 + end + object edWidth: TFloatSpinEdit + Left = 56 + Height = 23 + Top = 120 + Width = 103 + Font.Color = clWindowText + Font.Name = 'Arial' + MaxValue = 100 + OnChange = edWidthChange + ParentFont = False + TabOrder = 6 + Value = 50 + end + object edHeight: TFloatSpinEdit + Left = 56 + Height = 23 + Top = 148 + Width = 103 + Font.Color = clWindowText + Font.Name = 'Arial' + MaxValue = 100 + OnChange = edHeightChange + ParentFont = False + TabOrder = 7 + Value = 50 + end end end object OpenPictureDialog: TOpenPictureDialog diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas index ea23b77..bbd9398 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas @@ -42,6 +42,7 @@ 2013-10-13 - Massimo Magnano - Add multi crop demo + 2023-08 - Resolution ============================================================================ } @@ -52,9 +53,9 @@ interface uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, - Buttons, ExtDlgs, ComCtrls, ExtCtrls, Menus, BGRAImageManipulation, - BGRABitmap, BGRABitmapTypes, BCPanel, BCButton, BGRASpeedButton, BCLabel, - BCTrackbarUpdown{, BGRATrackBar}; + Buttons, ExtDlgs, ComCtrls, ExtCtrls, Menus, Spin, + {$IFDEF FPC} FPImage,{$ENDIF} BGRAImageManipulation, + BGRABitmap, BGRABitmapTypes, BCPanel, BCButton, BGRASpeedButton, BCLabel; type @@ -89,13 +90,14 @@ TFormBGRAImageManipulationDemo = class(TForm) chkFullSize: TCheckBox; edAspectPersonal: TEdit; edAspectRatio: TEdit; - edHeight: TBCTrackbarUpdown; - edLeft: TBCTrackbarUpdown; + edHeight: TFloatSpinEdit; + edLeft: TFloatSpinEdit; edName: TEdit; - edTop: TBCTrackbarUpdown; + edTop: TFloatSpinEdit; edUnit_Type: TComboBox; - edWidth: TBCTrackbarUpdown; + edWidth: TFloatSpinEdit; KeepAspectRatio: TCheckBox; + lbResolution: TLabel; lbAspectRatio: TLabel; lbOptions: TLabel; lbCompression: TLabel; @@ -118,6 +120,7 @@ TFormBGRAImageManipulationDemo = class(TForm) procedure btnSavePictureClick(Sender: TObject); procedure btnSetAspectRatioClick(Sender: TObject); procedure edNameChange(Sender: TObject); + procedure edUnit_TypeChange(Sender: TObject); procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); procedure KeepAspectRatioClick(Sender: TObject); @@ -160,6 +163,8 @@ implementation procedure TFormBGRAImageManipulationDemo.btnOpenPictureClick(Sender: TObject); var Bitmap: TBGRABitmap; + test:Integer; + begin // To put a new image in the component, you will simply need execute open // picture dialog to locate an image... @@ -171,11 +176,19 @@ procedure TFormBGRAImageManipulationDemo.btnOpenPictureClick(Sender: TObject); // Finally, associate the image into component BGRAImageManipulation.Bitmap := Bitmap; Bitmap.Free; - edLeft.MaxValue:=BGRAImageManipulation.Bitmap.Width; - edTop.MaxValue:=BGRAImageManipulation.Bitmap.Height; - edWidth.MaxValue:=BGRAImageManipulation.Bitmap.Width; - edHeight.MaxValue:=BGRAImageManipulation.Bitmap.Height; - BGRAImageManipulation.addCropArea(Rect(100,100,220,260)); + edUnit_Type.ItemIndex:=Integer(BGRAImageManipulation.Bitmap.ResolutionUnit); + + lbResolution.Caption:='Resolution : '+#13#10+' '+ + FloatToStr(BGRAImageManipulation.Bitmap.ResolutionX)+' x '+ + FloatToStr(BGRAImageManipulation.Bitmap.ResolutionY)+' '+edUnit_Type.Items[edUnit_Type.ItemIndex]+#13#10+ + ' '+FloatToStr(BGRAImageManipulation.Bitmap.ResolutionWidth)+' x '+FloatToStr(BGRAImageManipulation.Bitmap.ResolutionHeight)+#13#10+ + ' pixels '+IntToStr(BGRAImageManipulation.Bitmap.Width)+' x '+IntToStr(BGRAImageManipulation.Bitmap.Height); + + edLeft.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionWidth; + edTop.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionHeight; + edWidth.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionWidth; + edHeight.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionHeight; + //BGRAImageManipulation.addCropArea(Rect(100,100,220,260)); end; end; @@ -292,6 +305,19 @@ procedure TFormBGRAImageManipulationDemo.edNameChange(Sender: TObject); CropArea.Name :=edName.Text; end; +procedure TFormBGRAImageManipulationDemo.edUnit_TypeChange(Sender: TObject); +var + CropArea :TCropArea; + +begin + CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); + if CropArea<>nil then + begin + CropArea.AreaUnit:=TResolutionUnit(edUnit_Type.ItemIndex); + FillBoxUI(CropArea); + end; +end; + procedure TFormBGRAImageManipulationDemo.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin @@ -310,7 +336,10 @@ procedure TFormBGRAImageManipulationDemo.btBox_AddClick(Sender: TObject); newCropArea :TCropArea; begin - newCropArea :=BGRAImageManipulation.addCropArea(Rect(50, 50, 100, 100), 0 (*, Integer(newBox)*)); + if edUnit_Type.ItemIndex=0 + then newCropArea :=BGRAImageManipulation.addCropArea(Rect(50, 50, 100, 100)) + else newCropArea :=BGRAImageManipulation.addCropArea(Rect(1, 1, 2, 2), TResolutionUnit(edUnit_Type.ItemIndex)); + newCropArea.BorderColor :=VGALime; end; @@ -437,6 +466,7 @@ procedure TFormBGRAImageManipulationDemo.AddedCrop(AOwner: TBGRAImageManipulatio cbBoxList.AddItem(CropArea.Name, CropArea); cbBoxList.ItemIndex:=cbBoxList.Items.IndexOfObject(CropArea); + CropArea.AreaUnit:=BGRAImageManipulation.Bitmap.ResolutionUnit; FillBoxUI(CropArea); end; @@ -491,6 +521,14 @@ procedure TFormBGRAImageManipulationDemo.FillBoxUI(ABox: TCropArea); then begin BCPanelCropArea.Enabled :=True; edName.Text :=ABox.Name; + edUnit_Type.ItemIndex :=Integer(ABox.AreaUnit); + + //really maybe converted to Area Resolution Unit + edLeft.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionWidth; + edTop.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionHeight; + edWidth.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionWidth; + edHeight.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionHeight; + edLeft.Value :=ABox.Left; edTop.Value :=ABox.Top; edWidth.Value :=ABox.Width; @@ -505,8 +543,6 @@ procedure TFormBGRAImageManipulationDemo.FillBoxUI(ABox: TCropArea); end; edAspectPersonal.Text:=ABox.AspectRatio; changingAspect:=False; - - //edUnit_Type.ItemIndex :=Integer(ABox^.UnitType); end else BCPanelCropArea.Enabled :=False; end; From f3a81c68610b7130c8fd4e676ffbdaed5e5df139 Mon Sep 17 00:00:00 2001 From: Massimo Magnano Date: Mon, 28 Aug 2023 13:56:04 +0200 Subject: [PATCH 04/34] CropArea Resolution Convert/Load; getAllBitmaps rewrite; Demo Update CropArea Resolution Convert/Load; getAllBitmaps rewrite; Demo Update --- bgraimagemanipulation.pas | 199 ++++++++++++++---- .../unitbgraimagemanipulationdemo.lfm | 99 ++++++--- .../unitbgraimagemanipulationdemo.pas | 65 +++--- 3 files changed, 264 insertions(+), 99 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index 986230c..e88ebe1 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -145,6 +145,7 @@ TCropArea = class(TObject) rAspectRatio, rName: String; rKeepAspectRatio: BoolParent; + Loading :Boolean; procedure CopyAspectFromParent; procedure setAspectRatio(AValue: string); @@ -168,6 +169,7 @@ TCropArea = class(TObject) procedure Render_Refresh; + procedure GetImageResolution(var resX, resY:Single); procedure CalculateScaledAreaFromArea; procedure CalculateAreaFromScaledArea; function GetPixelArea(const AValue: TRectF):TRect; @@ -179,7 +181,7 @@ TCropArea = class(TObject) UserData :Integer; BorderColor :TBGRAPixel; - function getResampledBitmap(OriginalRect: TRect): TBGRABitmap; + function getResampledBitmap: TBGRABitmap; function getBitmap: TBGRABitmap; constructor Create(AOwner: TBGRAImageManipulation; AArea: TRectF; @@ -207,17 +209,17 @@ TCropAreaList = class(TObjectList) protected fOwner :TBGRAImageManipulation; rName :String; - loading :Boolean; + 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); - property items[aIndex: integer] : TCropArea read getCropArea write setCropArea; default; - property Name:String read rName write rName; function add(aCropArea: TCropArea): integer; procedure Load(const XMLConf: TXMLConfig); @@ -226,6 +228,9 @@ TCropAreaList = class(TObjectList) procedure LoadFromFile(const FileName: string); procedure SaveToStream(Stream: TStream); procedure SaveToFile(const FileName: string); + + property items[aIndex: integer] : TCropArea read getCropArea write setCropArea; default; + property Name:String read rName write rName; end; TgetAllBitmapsCallback = procedure (Bitmap :TBGRABitmap; CropArea: TCropArea) of object; @@ -441,6 +446,23 @@ procedure TCropArea.Render_Refresh; end; end; +procedure TCropArea.GetImageResolution(var resX, resY: Single); +begin + resX :=fOwner.fImageBitmap.ResolutionX; + resY :=fOwner.fImageBitmap.ResolutionY; + + //No Resolution use predefined Monitor Values + if (resX=0) + then if (rAreaUnit=ruPixelsPerInch) + then resX :=96 + else resX :=96/2.54; + + if (resY=0) + then if (rAreaUnit=ruPixelsPerInch) + then resY :=96 + else resY :=96/2.54; +end; + function TCropArea.getIsNullSize: Boolean; begin Result := not((abs(rArea.Right - rArea.Left) > 0) and (abs(rArea.Bottom - rArea.Top) > 0)); @@ -589,15 +611,12 @@ procedure TCropArea.CalculateScaledAreaFromArea; else begin // Calculate Scaled Area given Scale and Resolution xRatio := fOwner.fResampledBitmap.Width / fOwner.fImageBitmap.Width; yRatio := fOwner.fResampledBitmap.Height / fOwner.fImageBitmap.Height; - resX :=1; + resX :=1; //if rAreaUnit=ruNone use only Ratio resY :=1; if (rAreaUnit<>ruNone) then begin - if (fOwner.fImageBitmap.ResolutionX>0) - then resX :=fOwner.fImageBitmap.ResolutionX; - if (fOwner.fImageBitmap.ResolutionY>0) - then resY :=fOwner.fImageBitmap.ResolutionY; + GetImageResolution(resX, resY); //Do Conversion from/to inch/cm if (rAreaUnit <> fOwner.fImageBitmap.ResolutionUnit) then @@ -614,9 +633,11 @@ procedure TCropArea.CalculateScaledAreaFromArea; end; end; - rScaledArea.Left := Round(rArea.Left * resX * xRatio); + //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.Top := Round(rArea.Top * resY * yRatio); rScaledArea.Bottom := Round(rArea.Bottom * resY * yRatio); end; end; @@ -639,15 +660,12 @@ procedure TCropArea.CalculateAreaFromScaledArea; else begin // Calculate Scaled Area given Scale and Resolution xRatio := fOwner.fResampledBitmap.Width / fOwner.fImageBitmap.Width; yRatio := fOwner.fResampledBitmap.Height / fOwner.fImageBitmap.Height; - resX :=1; + resX :=1; //if rAreaUnit=ruNone use only Ratio resY :=1; if (rAreaUnit<>ruNone) then begin - if (fOwner.fImageBitmap.ResolutionX>0) - then resX :=fOwner.fImageBitmap.ResolutionX; - if (fOwner.fImageBitmap.ResolutionY>0) - then resY :=fOwner.fImageBitmap.ResolutionY; + GetImageResolution(resX, resY); //Do Conversion from/to inch/cm if (rAreaUnit <> fOwner.fImageBitmap.ResolutionUnit) then @@ -684,13 +702,12 @@ function TCropArea.GetPixelArea(const AValue: TRectF): TRect; Result.Bottom := Trunc(AValue.Bottom); end else begin - resX :=1; - resY :=1; - - if (fOwner.fImageBitmap.ResolutionX>0) - then resX :=fOwner.fImageBitmap.ResolutionX; - if (fOwner.fImageBitmap.ResolutionY>0) - then resY :=fOwner.fImageBitmap.ResolutionY; + if (rAreaUnit=ruNone) + then begin + resX :=1; + resY :=1; + end + else GetImageResolution(resX, resY); //Do Conversion from/to inch/cm if (rAreaUnit <> fOwner.fImageBitmap.ResolutionUnit) then @@ -706,9 +723,9 @@ function TCropArea.GetPixelArea(const AValue: TRectF): TRect; end end; - Result.Left := Round(AValue.Left * resX); + Result.Left := Trunc(AValue.Left * resX); + Result.Top := Trunc(AValue.Top * resY); Result.Right := Round(AValue.Right* resX); - Result.Top := Round(AValue.Top * resY); Result.Bottom := Round(AValue.Bottom * resY); end; end; @@ -852,12 +869,87 @@ procedure TCropArea.setArea(AValue: TRectF); end; procedure TCropArea.setAreaUnit(AValue: TResolutionUnit); +var + imgResX, imgResY :Single; + begin if rAreaUnit=AValue then Exit; - rAreaUnit:=AValue; + 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 :=96; + imgResY :=96; + end; + end; + + //Paranoid test to avoid zero divisions + if (imgResX=0) then imgResX :=96; + if (imgResY=0) then imgResY :=96; + + 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; - { #todo 2 : Fare conversione Area } + rAreaUnit:=AValue; if assigned(fOwner.rOnCropAreaChanged) then fOwner.rOnCropAreaChanged(fOwner, Self); @@ -947,7 +1039,7 @@ function TCropArea.getRealKeepAspectRatio: Boolean; end; //Get Resampled Bitmap (Scaled to current scale) -function TCropArea.getResampledBitmap(OriginalRect :TRect): TBGRABitmap; +function TCropArea.getResampledBitmap: TBGRABitmap; var ResampledBitmap: TBGRACustomBitmap; CropBitmap: TBGRABitmap; @@ -1010,10 +1102,11 @@ constructor TCropArea.Create(AOwner: TBGRAImageManipulation; AArea: TRectF; rAreaUnit :=AAreaUnit; Area := AArea; Rotate := ARotate; - UserData := AUserData; - rAspectX := 3; - rAspectY := 4; - rKeepAspectRatio := bParent; + UserData :=AUserData; + rAspectX :=3; + rAspectY :=4; + rKeepAspectRatio :=bParent; + Loading:=False; CopyAspectFromParent; end; @@ -1024,6 +1117,15 @@ destructor TCropArea.Destroy; { 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; @@ -1072,14 +1174,13 @@ procedure TCropAreaList.Load(const XMLConf: TXMLConfig); begin try - loading :=True; - XMLConf.OpenKey(fOwner.Name+'.'+Self.Name); newCount := XMLConf.GetValue('Count', -1); if (newCount=-1) then raise Exception.Create('XML Path not Found'); Clear; + Loading :=True; newSelected := XMLConf.GetValue('Selected', 0); for i :=0 to newCount-1 do @@ -1094,7 +1195,8 @@ procedure TCropAreaList.Load(const XMLConf: TXMLConfig); newArea.Height :=StrToFloat(XMLConf.GetValue('Height', IntToStr(fOwner.MinHeight))); XMLConf.CloseKey; newAreaUnit :=TResolutionUnit(XMLConf.GetValue('AreaUnit', 0)); - newCropArea :=TCropArea.Create(Self.fOwner, newArea ); + newCropArea :=TCropArea.Create(Self.fOwner, newArea, newAreaUnit); + newCropArea.Loading:=True; newCropArea.Name :=XMLConf.GetValue('Name', 'Name '+IntToStr(i)); newCropArea.KeepAspectRatio :=BoolParent(XMLConf.GetValue('KeepAspectRatio', Integer(bParent))); newCropArea.AspectRatio :=XMLConf.GetValue('AspectRatio', '3:4'); @@ -1103,6 +1205,7 @@ procedure TCropAreaList.Load(const XMLConf: TXMLConfig); if assigned(fOwner.rOnCropAreaLoad) then newCropArea.UserData :=fOwner.rOnCropAreaLoad(fOwner, newCropArea, XMLConf, fOwner.Name+'.'+Self.Name+'/'+curItemPath); + newCropArea.Loading:=False; XMLConf.CloseKey; add(newCropArea); @@ -2422,9 +2525,9 @@ function TBGRAImageManipulation.getResampledBitmap(ACropArea :TCropArea = Nil): if not (fImageBitmap.Empty) then begin if (ACropArea = Nil) - then ACropArea := Self.SelectedCropArea; + then ACropArea := Self.SelectedCropArea; if (ACropArea <> Nil) - then Result :=ACropArea.getResampledBitmap(getImageRect(fImageBitmap)); + then Result :=ACropArea.getResampledBitmap; end; end; @@ -2806,25 +2909,35 @@ procedure TBGRAImageManipulation.clearCropAreas; procedure TBGRAImageManipulation.getAllResampledBitmaps(ACallBack: TgetAllBitmapsCallback); var - i :Integer; + i :Integer; + curBitmap :TBGRABitmap; begin //Get Resampled Bitmap of each CropArea and pass it to CallBack for i:=0 to rCropAreas.Count-1 do - begin - ACallBack(rCropAreas[i].getResampledBitmap(getImageRect(fImageBitmap)), rCropAreas[i]); + try + curBitmap :=rCropAreas[i].getResampledBitmap; + ACallBack(curBitmap, rCropAreas[i]); + finally + if (curBitmap<>nil) + then curBitmap.Free; end; end; procedure TBGRAImageManipulation.getAllBitmaps(ACallBack: TgetAllBitmapsCallback); var - i :Integer; + i :Integer; + curBitmap :TBGRABitmap; begin //Get Bitmap of each CropArea and pass it to CallBack for i:=0 to rCropAreas.Count-1 do - begin - ACallBack(rCropAreas[i].getBitmap, rCropAreas[i]); + try + curBitmap :=rCropAreas[i].getBitmap; + ACallBack(curBitmap, rCropAreas[i]); + finally + if (curBitmap<>nil) + then curBitmap.Free; end; end; diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm index 523f945..4e459f0 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm @@ -1,20 +1,20 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo - Left = 417 - Height = 486 - Top = 136 - Width = 847 + Left = 236 + Height = 480 + Top = 116 + Width = 898 Caption = 'Demonstration of TBGRAImageManipulation' - ClientHeight = 486 - ClientWidth = 847 + ClientHeight = 480 + ClientWidth = 898 OnCloseQuery = FormCloseQuery OnCreate = FormCreate ShowHint = True LCLVersion = '3.99.0.0' object Background: TBCPanel - Left = 627 - Height = 486 + Left = 650 + Height = 480 Top = 0 - Width = 220 + Width = 248 Align = alRight Background.Color = clSilver Background.Gradient1.StartColor = clWhite @@ -49,9 +49,9 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Rounding.RoundY = 1 TabOrder = 0 object RateCompression: TTrackBar - Left = 20 - Height = 45 - Top = 227 + Left = 40 + Height = 24 + Top = 336 Width = 180 Max = 100 Position = 80 @@ -89,9 +89,9 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo object lbCompression: TLabel Left = 25 Height = 15 - Top = 280 - Width = 113 - Caption = 'Compression adjust :' + Top = 320 + Width = 77 + Caption = 'Compression :' Font.Color = clWhite Font.Style = [fsBold] ParentColor = False @@ -1121,9 +1121,9 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo MemoryUsage = bmuHigh end object btnShape: TBCButton - Left = 15 - Height = 48 - Top = 296 + Left = 32 + Height = 24 + Top = 336 Width = 200 StateClicked.Background.Gradient1.StartColor = 8404992 StateClicked.Background.Gradient1.EndColor = 4194304 @@ -1378,9 +1378,9 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo MemoryUsage = bmuHigh end object chkFullSize: TCheckBox - Left = 29 + Left = 45 Height = 19 - Top = 360 + Top = 368 Width = 150 Caption = 'Save Original Size picture' Checked = True @@ -1394,12 +1394,55 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Width = 65 Caption = 'Resolution : ' end + object BCLabel7: TBCLabel + Left = 45 + Height = 15 + Top = 300 + Width = 44 + Background.Gradient1.StartColor = clWhite + Background.Gradient1.EndColor = clBlack + Background.Gradient1.GradientType = gtLinear + Background.Gradient1.Point1XPercent = 0 + Background.Gradient1.Point1YPercent = 0 + Background.Gradient1.Point2XPercent = 0 + Background.Gradient1.Point2YPercent = 100 + Background.Gradient2.StartColor = clWhite + Background.Gradient2.EndColor = clBlack + Background.Gradient2.GradientType = gtLinear + Background.Gradient2.Point1XPercent = 0 + Background.Gradient2.Point1YPercent = 0 + Background.Gradient2.Point2XPercent = 0 + Background.Gradient2.Point2YPercent = 100 + Background.Gradient1EndPercent = 35 + Background.Style = bbsClear + Border.Style = bboNone + Caption = 'Format :' + FontEx.Color = clDefault + FontEx.FontQuality = fqSystemClearType + FontEx.Shadow = False + FontEx.ShadowRadius = 5 + FontEx.ShadowOffsetX = 5 + FontEx.ShadowOffsetY = 5 + FontEx.Style = [] + Rounding.RoundX = 1 + Rounding.RoundY = 1 + end + object edSaveFormat: TEdit + Left = 93 + Height = 23 + Top = 296 + Width = 103 + AutoSize = False + OnEditingDone = edNameChange + TabOrder = 4 + Text = 'jpg' + end end object BGRAImageManipulation: TBGRAImageManipulation Left = 170 - Height = 486 + Height = 480 Top = 0 - Width = 457 + Width = 480 Align = alClient AnchorSize = 9 AspectRatio = '3:4' @@ -1412,7 +1455,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo end object BCPanelCropAreas: TBCPanel Left = 0 - Height = 486 + Height = 480 Top = 0 Width = 170 Align = alLeft @@ -1499,7 +1542,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo object BCPanelCropAreaLoad: TBCPanel Left = 1 Height = 106 - Top = 379 + Top = 373 Width = 168 Align = alBottom Background.Color = clBtnFace @@ -2425,12 +2468,16 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Top = 74 end object SaveCropList: TSaveDialog - Filter = 'All Files (*.*)|*.*|Crop List File (*.clf)|*.clf' + DefaultExt = '.clf' + Filter = 'Crop List File (*.clf)|*.clf|All Files (*.*)|*.*' + FilterIndex = 0 Left = 191 Top = 368 end object OpenCropList: TOpenDialog - Filter = 'All Files (*.*)|*.*|Crop List File (*.clf)|*.clf' + DefaultExt = '.clf' + Filter = 'Crop List File (*.clf)|*.clf|All Files (*.*)|*.*' + FilterIndex = 0 Left = 192 Top = 418 end diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas index bbd9398..8c3aa64 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas @@ -69,6 +69,7 @@ TFormBGRAImageManipulationDemo = class(TForm) BCLabel4: TBCLabel; BCLabel5: TBCLabel; BCLabel6: TBCLabel; + BCLabel7: TBCLabel; BCPanelCropAreaLoad: TBCPanel; BCPanelCropArea: TBCPanel; BCPanelCropAreas: TBCPanel; @@ -93,6 +94,7 @@ TFormBGRAImageManipulationDemo = class(TForm) edHeight: TFloatSpinEdit; edLeft: TFloatSpinEdit; edName: TEdit; + edSaveFormat: TEdit; edTop: TFloatSpinEdit; edUnit_Type: TComboBox; edWidth: TFloatSpinEdit; @@ -121,6 +123,7 @@ TFormBGRAImageManipulationDemo = class(TForm) procedure btnSetAspectRatioClick(Sender: TObject); procedure edNameChange(Sender: TObject); procedure edUnit_TypeChange(Sender: TObject); + procedure edWidthEditingDone(Sender: TObject); procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); procedure KeepAspectRatioClick(Sender: TObject); @@ -232,45 +235,29 @@ procedure TFormBGRAImageManipulationDemo.btnSaveCropListClick(Sender: TObject); procedure TFormBGRAImageManipulationDemo.btnSavePictureClick(Sender: TObject); var - JpegImage: TJpegImage; -begin - // This example save image compress in JPEG format + curBitmap :TBGRABitmap; - // Execute our Save Picture Dialog - SavePictureDialog.Filter := 'JPEG Image File (*.jpg, *.jpeg)'; +begin + { #note -oMaxM : Get the format name } + // SavePictureDialog.Filter:='File Format name (*.'+edSaveFormat.Text+')|*.'+edSaveFormat.Text; + SavePictureDialog.DefaultExt:='.'+edSaveFormat.Text; if SavePictureDialog.Execute then begin try - // Compress - JpegImage := TJpegImage.Create; if (chkFullSize.Checked) - then JpegImage.Assign(BGRAImageManipulation.getBitmap) - else JpegImage.Assign(BGRAImageManipulation.getResampledBitmap); - JpegImage.CompressionQuality := RateCompression.Position; + then curBitmap :=BGRAImageManipulation.getBitmap + else curBitmap :=BGRAImageManipulation.getResampledBitmap; - // And save to file - JpegImage.SaveToFile(SavePictureDialog.FileName); + curBitmap.SaveToFile(SavePictureDialog.FileName); finally - JpegImage.Free; + curBitmap.Free; end; end; end; procedure TFormBGRAImageManipulationDemo.SaveCallBack(Bitmap :TBGRABitmap; CropArea: TCropArea); -var - JpegImage: TJpegImage; begin - try - // Compress - JpegImage := TJpegImage.Create; - JpegImage.Assign(Bitmap); - JpegImage.CompressionQuality := RateCompression.Position; - - // And save to file - JpegImage.SaveToFile(SelectDirectoryDialog1.FileName+DirectorySeparator+CropArea.Name+'.jpg'); - finally - JpegImage.Free; - end; + Bitmap.SaveToFile(SelectDirectoryDialog1.FileName+DirectorySeparator+CropArea.Name+'.'+edSaveFormat.Text); end; procedure TFormBGRAImageManipulationDemo.btnSavePictureAllClick(Sender: TObject); @@ -318,6 +305,15 @@ procedure TFormBGRAImageManipulationDemo.edUnit_TypeChange(Sender: TObject); end; end; +procedure TFormBGRAImageManipulationDemo.edWidthEditingDone(Sender: TObject); +var + CropArea :TCropArea; + +begin + CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); + CropArea.Width :=edWidth.Value; +end; + procedure TFormBGRAImageManipulationDemo.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin @@ -524,10 +520,19 @@ procedure TFormBGRAImageManipulationDemo.FillBoxUI(ABox: TCropArea); edUnit_Type.ItemIndex :=Integer(ABox.AreaUnit); //really maybe converted to Area Resolution Unit - edLeft.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionWidth; - edTop.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionHeight; - edWidth.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionWidth; - edHeight.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionHeight; + if (ABox.AreaUnit=ruNone) + then begin + edLeft.MaxValue:=BGRAImageManipulation.Bitmap.Width; + edTop.MaxValue:=BGRAImageManipulation.Bitmap.Height; + edWidth.MaxValue:=BGRAImageManipulation.Bitmap.Width; + edHeight.MaxValue:=BGRAImageManipulation.Bitmap.Height; + end + else begin + edLeft.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionWidth; + edTop.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionHeight; + edWidth.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionWidth; + edHeight.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionHeight; + end; edLeft.Value :=ABox.Left; edTop.Value :=ABox.Top; From 3a5142feb59d12420e4ebad6215ff00d4e03dc79 Mon Sep 17 00:00:00 2001 From: Massimo Magnano Date: Tue, 29 Aug 2023 10:57:00 +0200 Subject: [PATCH 05/34] ImageManipulation Demo Save in various formats --- bgraimagemanipulation.pas | 4 +- .../unitbgraimagemanipulationdemo.lfm | 23 ++++--- .../unitbgraimagemanipulationdemo.pas | 61 ++++++++++++++----- 3 files changed, 64 insertions(+), 24 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index e88ebe1..fa513f7 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -3303,6 +3303,8 @@ procedure TBGRAImageManipulation.MouseMove(Shift: TShiftState; X, Y: integer); begin Cursor := crSizeAll; + { #todo 9 -oMaxM : Moving the Area Left<->Right Increase Width, Top<->Bottom Increase Height } + // Move the cropping area try WorkRect :=SelectedCropArea.ScaledArea; @@ -3502,7 +3504,7 @@ procedure TBGRAImageManipulation.MouseUp(Button: TMouseButton; // If we need to repaint if needRepaint then begin - SelectedCropArea.CalculateAreaFromScaledArea; + //SelectedCropArea.CalculateAreaFromScaledArea; if assigned(rOnCropAreaChanged) then rOnCropAreaChanged(Self, SelectedCropArea); diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm index 4e459f0..30b457c 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm @@ -8,6 +8,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo ClientWidth = 898 OnCloseQuery = FormCloseQuery OnCreate = FormCreate + OnShow = FormShow ShowHint = True LCLVersion = '3.99.0.0' object Background: TBCPanel @@ -1395,9 +1396,9 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Caption = 'Resolution : ' end object BCLabel7: TBCLabel - Left = 45 + Left = 11 Height = 15 - Top = 300 + Top = 296 Width = 44 Background.Gradient1.StartColor = clWhite Background.Gradient1.EndColor = clBlack @@ -1427,15 +1428,14 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Rounding.RoundX = 1 Rounding.RoundY = 1 end - object edSaveFormat: TEdit - Left = 93 + object cbSaveFormat: TComboBox + Left = 60 Height = 23 - Top = 296 - Width = 103 - AutoSize = False - OnEditingDone = edNameChange + Top = 288 + Width = 180 + ItemHeight = 15 + Style = csDropDownList TabOrder = 4 - Text = 'jpg' end end object BGRAImageManipulation: TBGRAImageManipulation @@ -2404,6 +2404,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Height = 23 Top = 64 Width = 103 + DecimalPlaces = 3 Font.Color = clWindowText Font.Name = 'Arial' MaxValue = 100 @@ -2417,6 +2418,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Height = 23 Top = 92 Width = 103 + DecimalPlaces = 3 Font.Color = clWindowText Font.Name = 'Arial' MaxValue = 100 @@ -2430,6 +2432,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Height = 23 Top = 120 Width = 103 + DecimalPlaces = 3 Font.Color = clWindowText Font.Name = 'Arial' MaxValue = 100 @@ -2443,6 +2446,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Height = 23 Top = 148 Width = 103 + DecimalPlaces = 3 Font.Color = clWindowText Font.Name = 'Arial' MaxValue = 100 @@ -2460,6 +2464,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo end object SavePictureDialog: TSavePictureDialog Title = 'Save image as' + DefaultExt = '.jpg' Left = 312 Top = 16 end diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas index 8c3aa64..2c62d62 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas @@ -89,12 +89,12 @@ TFormBGRAImageManipulationDemo = class(TForm) btnRotateRight: TBCButton; cbBoxList: TComboBox; chkFullSize: TCheckBox; + cbSaveFormat: TComboBox; edAspectPersonal: TEdit; edAspectRatio: TEdit; edHeight: TFloatSpinEdit; edLeft: TFloatSpinEdit; edName: TEdit; - edSaveFormat: TEdit; edTop: TFloatSpinEdit; edUnit_Type: TComboBox; edWidth: TFloatSpinEdit; @@ -125,6 +125,7 @@ TFormBGRAImageManipulationDemo = class(TForm) procedure edUnit_TypeChange(Sender: TObject); procedure edWidthEditingDone(Sender: TObject); procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); + procedure FormShow(Sender: TObject); procedure KeepAspectRatioClick(Sender: TObject); procedure btBox_AddClick(Sender: TObject); @@ -238,9 +239,6 @@ procedure TFormBGRAImageManipulationDemo.btnSavePictureClick(Sender: TObject); curBitmap :TBGRABitmap; begin - { #note -oMaxM : Get the format name } - // SavePictureDialog.Filter:='File Format name (*.'+edSaveFormat.Text+')|*.'+edSaveFormat.Text; - SavePictureDialog.DefaultExt:='.'+edSaveFormat.Text; if SavePictureDialog.Execute then begin try @@ -256,8 +254,15 @@ procedure TFormBGRAImageManipulationDemo.btnSavePictureClick(Sender: TObject); end; procedure TFormBGRAImageManipulationDemo.SaveCallBack(Bitmap :TBGRABitmap; CropArea: TCropArea); +var + ext:String; + i:Integer; + begin - Bitmap.SaveToFile(SelectDirectoryDialog1.FileName+DirectorySeparator+CropArea.Name+'.'+edSaveFormat.Text); + ext:=ImageHandlers.Extensions[cbSaveFormat.Items[cbSaveFormat.ItemIndex]]; + i :=Pos(';', ext); + if (i>0) then ext :=Copy(ext, 1, i-1); + Bitmap.SaveToFile(SelectDirectoryDialog1.FileName+DirectorySeparator+CropArea.Name+'.'+ext); end; procedure TFormBGRAImageManipulationDemo.btnSavePictureAllClick(Sender: TObject); @@ -297,11 +302,14 @@ procedure TFormBGRAImageManipulationDemo.edUnit_TypeChange(Sender: TObject); CropArea :TCropArea; begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - if CropArea<>nil then + if (cbBoxList.ItemIndex>-1) then begin - CropArea.AreaUnit:=TResolutionUnit(edUnit_Type.ItemIndex); - FillBoxUI(CropArea); + CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); + if CropArea<>nil then + begin + CropArea.AreaUnit:=TResolutionUnit(edUnit_Type.ItemIndex); + FillBoxUI(CropArea); + end; end; end; @@ -320,6 +328,10 @@ procedure TFormBGRAImageManipulationDemo.FormCloseQuery(Sender: TObject; closing :=True; end; +procedure TFormBGRAImageManipulationDemo.FormShow(Sender: TObject); +begin +end; + procedure TFormBGRAImageManipulationDemo.KeepAspectRatioClick(Sender: TObject); begin BGRAImageManipulation.KeepAspectRatio := KeepAspectRatio.Checked; @@ -365,7 +377,7 @@ procedure TFormBGRAImageManipulationDemo.edHeightChange(Sender: TObject; AByUser CropArea :TCropArea; begin - if AByUser then + if (*AByUser*) (cbBoxList.ItemIndex>-1) then begin CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); if (CropArea<>nil) @@ -378,7 +390,7 @@ procedure TFormBGRAImageManipulationDemo.edLeftChange(Sender: TObject; AByUser: CropArea :TCropArea; begin - if AByUser then + if (*AByUser*) (cbBoxList.ItemIndex>-1) then begin CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); if (CropArea<>nil) @@ -391,7 +403,7 @@ procedure TFormBGRAImageManipulationDemo.edTopChange(Sender: TObject; AByUser: b CropArea :TCropArea; begin - if AByUser then + if (*AByUser*) (cbBoxList.ItemIndex>-1) then begin CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); if (CropArea<>nil) @@ -404,7 +416,7 @@ procedure TFormBGRAImageManipulationDemo.edWidthChange(Sender: TObject; AByUser: CropArea :TCropArea; begin - if AByUser then + if (*AByUser*) (cbBoxList.ItemIndex>-1) then begin CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); if (CropArea<>nil) @@ -414,13 +426,26 @@ procedure TFormBGRAImageManipulationDemo.edWidthChange(Sender: TObject; AByUser: procedure TFormBGRAImageManipulationDemo.FormCreate(Sender: TObject); var - i :Integer; + i,j :Integer; + t,e:String; begin closing :=False; changingAspect :=False; lastNewBoxNum :=0; TStringList(cbBoxList.Items).OwnsObjects:=False; + j:=0; + for i :=0 to ImageHandlers.Count-1 do + begin + t :=ImageHandlers.TypeNames[i]; + e :=ImageHandlers.Extensions[t]; + if (ImageHandlers.ImageWriter[t]<>nil) then + begin + cbSaveFormat.Items.Add(t); + if (Pos('jpg', e)>0) then j:=i; + end; + end; + cbSaveFormat.ItemIndex:=j-1; end; procedure TFormBGRAImageManipulationDemo.rgAspectSelectionChanged(Sender: TObject); @@ -522,12 +547,20 @@ procedure TFormBGRAImageManipulationDemo.FillBoxUI(ABox: TCropArea); //really maybe converted to Area Resolution Unit if (ABox.AreaUnit=ruNone) then begin + edLeft.DecimalPlaces:=0; + edTop.DecimalPlaces:=0; + edWidth.DecimalPlaces:=0; + edHeight.DecimalPlaces:=0; edLeft.MaxValue:=BGRAImageManipulation.Bitmap.Width; edTop.MaxValue:=BGRAImageManipulation.Bitmap.Height; edWidth.MaxValue:=BGRAImageManipulation.Bitmap.Width; edHeight.MaxValue:=BGRAImageManipulation.Bitmap.Height; end else begin + edLeft.DecimalPlaces:=3; + edTop.DecimalPlaces:=3; + edWidth.DecimalPlaces:=3; + edHeight.DecimalPlaces:=3; edLeft.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionWidth; edTop.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionHeight; edWidth.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionWidth; From 1ffbdfc9ae5a288d194a942ba3b2978477028ce1 Mon Sep 17 00:00:00 2001 From: Massimo Magnano Date: Wed, 30 Aug 2023 16:18:57 +0200 Subject: [PATCH 06/34] Solved bug Moving the Crop Area; Alt on MouseUp Undo Solved bug Moving the Crop Area Left<->Right Increase Width, Top<->Bottom Increase Height; Alt on MouseUp Undo the Crop Area Changes; Optimized mouse events; --- bgraimagemanipulation.pas | 481 +++++++++--------- .../unitbgraimagemanipulationdemo.lfm | 4 + .../unitbgraimagemanipulationdemo.pas | 58 ++- 3 files changed, 295 insertions(+), 248 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index fa513f7..0cadae5 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -257,9 +257,9 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) fMinHeight: integer; fMinWidth: integer; fMouseCaught: boolean; - fStartPoint: TPoint; + fStartPoint, fEndPoint: TPoint; - + fStartArea: TRect; fRatio: TRatio; fSizeLimits: TSizeLimits; @@ -1983,6 +1983,7 @@ function TBGRAImageManipulation.isOverAnchor(APoint :TPoint; var AnchorSelected AnchorSelected :=[]; ACursor :=crDefault; Result :=Nil; + { #todo 1 -oMaxM : the point can be on different areas (Z Order?) } for i:=0 to rCropAreas.Count-1 do begin rCropArea :=rCropAreas[i]; @@ -3168,7 +3169,6 @@ procedure TBGRAImageManipulation.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: integer); var WorkRect: TRect; - overControl: boolean; ACursor :TCursor; begin @@ -3178,25 +3178,25 @@ procedure TBGRAImageManipulation.MouseDown(Button: TMouseButton; // Find the working area of the control WorkRect := getWorkRect; - // See if the mouse is inside the pressable part of the control - overControl := ((X >= WorkRect.Left) and (X <= WorkRect.Right) and - (Y >= WorkRect.Top) and (Y <= WorkRect.Bottom)); - // If over control - if ((overControl) and (Button = mbLeft) and (not (ssDouble in Shift))) then + 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 @@ -3223,221 +3223,217 @@ procedure TBGRAImageManipulation.MouseMove(Shift: TShiftState; X, Y: integer); newCoords: TCoord; Direction: TDirection; Bounds: TRect; - overControl: boolean; {%H-}overCropArea :TCropArea; ACursor :TCursor; -begin - // Call the inherited MouseMove() procedure - inherited MouseMove(Shift, X, Y); - - // Set default cursor - Cursor := crDefault; - - // Assume we don't need to repaint the control - needRepaint := False; + procedure newSelection; + begin + // Starts a new selection of cropping area + try + Cursor := crCross; + fEndPoint := Point(X - WorkRect.Left, Y - WorkRect.Top); - // Find the working area of the component - WorkRect := GetWorkRect; + // Copy coord + with newCoords do + begin + x1 := fStartPoint.X; + y1 := fStartPoint.Y; - // See if the mouse is inside the pressable part of the control - overControl := ((X >= WorkRect.Left) and (X <= WorkRect.Right) and - (Y >= WorkRect.Top) and (Y <= WorkRect.Bottom)); + x2 := fEndPoint.X; + y2 := fEndPoint.Y; + end; - // If image empty - if (fImageBitmap.Empty) then - exit; + // Determine direction + Direction := getDirection(fStartPoint, fEndPoint); - // If the mouse was originally clicked on the control - if (fMouseCaught) then - begin - // Determines limite values - Bounds := getImageRect(fResampledBitmap); + // Apply the ratio, if necessary + newCoords := ApplyRatioToAxes(newCoords, Direction, Bounds, rNewCropArea); - // If no anchor selected - if (fAnchorSelected = []) then - begin - // Starts a new selection of cropping area - try - Cursor := crCross; - fEndPoint := Point(X - WorkRect.Left, Y - WorkRect.Top); + // Determines minimum value on both axes + // new Area have KeepAspectRatio setted to bParent by default + newCoords := ApplyDimRestriction(newCoords, Direction, Bounds, fKeepAspectRatio); - // Copy coord - with newCoords do - begin - x1 := fStartPoint.X; - y1 := fStartPoint.Y; + 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); - x2 := fEndPoint.X; - y2 := fEndPoint.Y; - end; + finally + needRepaint := True; + end; + end; - // Determine direction - Direction := getDirection(fStartPoint, fEndPoint); + procedure moveCropping; + begin + Cursor := crSizeAll; - // Apply the ratio, if necessary - newCoords := ApplyRatioToAxes(newCoords, Direction, Bounds, rNewCropArea); + // 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; - // Determines minimum value on both axes - // new Area have KeepAspectRatio setted to bParent by default - newCoords := ApplyDimRestriction(newCoords, Direction, Bounds, fKeepAspectRatio); + //Out of Bounds check + if (WorkRect.Left<0) + then WorkRect.Left :=0; - 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); + if (WorkRect.Top<0) + then WorkRect.Top :=0; - finally - needRepaint := True; - end; - end - else - begin - // Get the actual point - fEndPoint := Point(X - WorkRect.Left, Y - WorkRect.Top); + if (WorkRect.Left+fStartArea.Width>Bounds.Right) + then WorkRect.Left :=Bounds.Right-fStartArea.Width; - // Check what the anchor was dragged - if (fAnchorSelected = [NORTH, SOUTH, EAST, WEST]) then - begin - Cursor := crSizeAll; + if (WorkRect.Top+fStartArea.Height>Bounds.Bottom) + then WorkRect.Top :=Bounds.Bottom-fStartArea.Height; - { #todo 9 -oMaxM : Moving the Area Left<->Right Increase Width, Top<->Bottom Increase Height } + WorkRect.Width :=fStartArea.Width; + WorkRect.Height:=fStartArea.Height; + SelectedCropArea.ScaledArea :=WorkRect; - // Move the cropping area - try - WorkRect :=SelectedCropArea.ScaledArea; - newCoords.x1:=WorkRect.Width; - newCoords.y1:=WorkRect.Height; - WorkRect.Left :=fEndPoint.X-fStartPoint.X; //fStartPoint is Relative to CropArea - WorkRect.Top :=fEndPoint.Y-fStartPoint.Y; + finally + needRepaint := True; + end; + end; - //Out of Bounds check - if (WorkRect.Left<0) - then WorkRect.Left :=0; + 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 (WorkRect.Top<0) - then WorkRect.Top :=0; + 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; - if (WorkRect.Left+newCoords.x1>Bounds.Right) - then WorkRect.Left :=Bounds.Right-newCoords.x1; + // Determine direction + Direction := getDirection(fStartPoint, fEndPoint); - if (WorkRect.Top+newCoords.y1>Bounds.Bottom) - then WorkRect.Top :=Bounds.Bottom-newCoords.y1; + // Apply the ratio, if necessary + newCoords := ApplyRatioToAxes(newCoords, Direction, Bounds, SelectedCropArea); - WorkRect.Width :=newCoords.x1; - WorkRect.Height:=newCoords.y1; - SelectedCropArea.ScaledArea :=WorkRect; + // Determines minimum value on both axes + newCoords := ApplyDimRestriction(newCoords, Direction, Bounds, SelectedCropArea.getRealKeepAspectRatio); - finally - needRepaint := True; - end; - end - else - 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; + SelectedCropArea.ScaledArea := Rect(newCoords.x1, newCoords.y1, newCoords.x2, newCoords.y2); + finally + needRepaint := True; + end; + 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; +begin + // Call the inherited MouseMove() procedure + inherited MouseMove(Shift, X, Y); - // Determine direction - Direction := getDirection(fStartPoint, fEndPoint); + // Set default cursor + Cursor := crDefault; - // Apply the ratio, if necessary - newCoords := ApplyRatioToAxes(newCoords, Direction, Bounds, SelectedCropArea); + // Find the working area of the component + WorkRect := GetWorkRect; - // Determines minimum value on both axes - newCoords := ApplyDimRestriction(newCoords, Direction, Bounds, SelectedCropArea.getRealKeepAspectRatio); + // If image empty + if (fImageBitmap.Empty) then //MaxM: Maybe deleted? + exit; - SelectedCropArea.ScaledArea := Rect(newCoords.x1, newCoords.y1, newCoords.x2, newCoords.y2); - finally - needRepaint := True; - end; - end; - end; - end - else - begin - // If the mouse is just moving over the control, and wasn't originally click - // in the control - if (overControl) 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; + // 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.ScaledArea :=curCropAreaRect; - SelectedCropArea.CalculateAreaFromScaledArea; - if assigned(rOnCropAreaChanged) - then rOnCropAreaChanged(Self, SelectedCropArea); + // 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; + // 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; @@ -3451,66 +3447,67 @@ procedure TBGRAImageManipulation.MouseUp(Button: TMouseButton; // Call the inherited MouseUp() procedure inherited MouseUp(Button, Shift, X, Y); - // Assume we don't need to repaint the control - needRepaint := False; - // If the mouse was originally clicked over the control if (fMouseCaught) then begin // Show that the mouse is no longer caught fMouseCaught := False; - // Check what the anchor was dragged - if (fAnchorSelected = [NORTH, SOUTH, EAST, WEST]) then - begin - // Move the cropping area - try - finally - needRepaint := True; - end; - end - else - begin - // Ends a new selection of cropping area - if (rNewCropArea <> Nil) then - begin - SelectedCropArea :=rNewCropArea; - rNewCropArea :=Nil; - curCropAreaRect :=SelectedCropArea.ScaledArea; + // Assume we don't need to repaint the control + needRepaint := False; - 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; + 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 := []; - end; - // If we need to repaint - if needRepaint then - begin - //SelectedCropArea.CalculateAreaFromScaledArea; - if assigned(rOnCropAreaChanged) - then rOnCropAreaChanged(Self, SelectedCropArea); + // 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; + // Invalidate the control for repainting + Render; + Refresh; + end; end; end; diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm index 30b457c..a8919e8 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm @@ -2409,6 +2409,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Font.Name = 'Arial' MaxValue = 100 OnChange = edLeftChange + OnEditingDone = edLeftEditingDone ParentFont = False TabOrder = 4 Value = 50 @@ -2423,6 +2424,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Font.Name = 'Arial' MaxValue = 100 OnChange = edTopChange + OnEditingDone = edTopEditingDone ParentFont = False TabOrder = 5 Value = 50 @@ -2437,6 +2439,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Font.Name = 'Arial' MaxValue = 100 OnChange = edWidthChange + OnEditingDone = edWidthEditingDone ParentFont = False TabOrder = 6 Value = 50 @@ -2451,6 +2454,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Font.Name = 'Arial' MaxValue = 100 OnChange = edHeightChange + OnEditingDone = edHeightEditingDone ParentFont = False TabOrder = 7 Value = 50 diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas index 2c62d62..29b8577 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas @@ -121,7 +121,10 @@ TFormBGRAImageManipulationDemo = class(TForm) procedure btnSavePictureAllClick(Sender: TObject); procedure btnSavePictureClick(Sender: TObject); procedure btnSetAspectRatioClick(Sender: TObject); + procedure edHeightEditingDone(Sender: TObject); + procedure edLeftEditingDone(Sender: TObject); procedure edNameChange(Sender: TObject); + procedure edTopEditingDone(Sender: TObject); procedure edUnit_TypeChange(Sender: TObject); procedure edWidthEditingDone(Sender: TObject); procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); @@ -288,6 +291,32 @@ procedure TFormBGRAImageManipulationDemo.btnSetAspectRatioClick(Sender: TObject) end; end; +procedure TFormBGRAImageManipulationDemo.edHeightEditingDone(Sender: TObject); +var + CropArea :TCropArea; + +begin + if (cbBoxList.ItemIndex>-1) then + begin + CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); + if CropArea<>nil + then CropArea.Height :=edHeight.Value; + end; +end; + +procedure TFormBGRAImageManipulationDemo.edLeftEditingDone(Sender: TObject); +var + CropArea :TCropArea; + +begin + if (cbBoxList.ItemIndex>-1) then + begin + CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); + if CropArea<>nil + then CropArea.Left :=edLeft.Value; + end; +end; + procedure TFormBGRAImageManipulationDemo.edNameChange(Sender: TObject); var CropArea :TCropArea; @@ -297,6 +326,19 @@ procedure TFormBGRAImageManipulationDemo.edNameChange(Sender: TObject); CropArea.Name :=edName.Text; end; +procedure TFormBGRAImageManipulationDemo.edTopEditingDone(Sender: TObject); +var + CropArea :TCropArea; + +begin + if (cbBoxList.ItemIndex>-1) then + begin + CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); + if CropArea<>nil + then CropArea.Top :=edTop.Value; + end; +end; + procedure TFormBGRAImageManipulationDemo.edUnit_TypeChange(Sender: TObject); var CropArea :TCropArea; @@ -318,8 +360,12 @@ procedure TFormBGRAImageManipulationDemo.edWidthEditingDone(Sender: TObject); CropArea :TCropArea; begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - CropArea.Width :=edWidth.Value; + if (cbBoxList.ItemIndex>-1) then + begin + CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); + if CropArea<>nil + then CropArea.Width :=edWidth.Value; + end; end; procedure TFormBGRAImageManipulationDemo.FormCloseQuery(Sender: TObject; @@ -377,7 +423,7 @@ procedure TFormBGRAImageManipulationDemo.edHeightChange(Sender: TObject; AByUser CropArea :TCropArea; begin - if (*AByUser*) (cbBoxList.ItemIndex>-1) then + if AByUser and (cbBoxList.ItemIndex>-1) then begin CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); if (CropArea<>nil) @@ -390,7 +436,7 @@ procedure TFormBGRAImageManipulationDemo.edLeftChange(Sender: TObject; AByUser: CropArea :TCropArea; begin - if (*AByUser*) (cbBoxList.ItemIndex>-1) then + if AByUser and (cbBoxList.ItemIndex>-1) then begin CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); if (CropArea<>nil) @@ -403,7 +449,7 @@ procedure TFormBGRAImageManipulationDemo.edTopChange(Sender: TObject; AByUser: b CropArea :TCropArea; begin - if (*AByUser*) (cbBoxList.ItemIndex>-1) then + if AByUser and (cbBoxList.ItemIndex>-1) then begin CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); if (CropArea<>nil) @@ -416,7 +462,7 @@ procedure TFormBGRAImageManipulationDemo.edWidthChange(Sender: TObject; AByUser: CropArea :TCropArea; begin - if (*AByUser*) (cbBoxList.ItemIndex>-1) then + if AByUser and (cbBoxList.ItemIndex>-1) then begin CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); if (CropArea<>nil) From 2fa755143b83d5471c836e36c4ad23ae505ad301 Mon Sep 17 00:00:00 2001 From: Massimo Magnano Date: Thu, 31 Aug 2023 12:21:05 +0200 Subject: [PATCH 07/34] OverAnchor gives precedence to the selected area --- bgraimagemanipulation.pas | 195 +++++++++++++++++++++----------------- 1 file changed, 109 insertions(+), 86 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index 0cadae5..b99535d 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -66,6 +66,8 @@ - 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 + - OverAnchor gives precedence to the selected area ============================================================================ } @@ -1966,10 +1968,7 @@ function TBGRAImageManipulation.getWorkRect: TRect; { Check if mouse is over any anchor } function TBGRAImageManipulation.isOverAnchor(APoint :TPoint; var AnchorSelected :TDirection; var ACursor :TCursor):TCropArea; var - rCropArea :TCropArea; - rCropRect, - rCropRectI :TRect; - i :Integer; + i :Integer; function _isOverAnchor(APoint: TPoint; Corner: TPoint): boolean; begin @@ -1979,101 +1978,125 @@ function TBGRAImageManipulation.isOverAnchor(APoint :TPoint; var AnchorSelected (APoint.Y <= (Corner.Y + AnchorSize))); end; -begin - AnchorSelected :=[]; - ACursor :=crDefault; - Result :=Nil; - { #todo 1 -oMaxM : the point can be on different areas (Z Order?) } - for i:=0 to rCropAreas.Count-1 do + 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 - rCropArea :=rCropAreas[i]; - rCropRectI :=rCropArea.ScaledArea; - InflateRect(rCropRectI, AnchorSize, AnchorSize); - if ({$IFNDEF FPC}BGRAGraphics.{$ENDIF}PtInRect(rCropRectI, APoint)) then + rCropRect :=rCropArea.ScaledArea; + // Verifies that is positioned on an anchor + // NW + if (_isOverAnchor(APoint, rCropRect.TopLeft)) 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; break; - end; + 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; break; - 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; break; - 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; break; - 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; break; - 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; break; - 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; break; - 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; break; - 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 + // 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 - 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; break; - end; + AnchorSelected := [NORTH, SOUTH, EAST, WEST]; + ACursor := crSizeAll; + Result :=rCropArea; exit; end; end; - end; + end; + end; + +begin + AnchorSelected :=[]; + ACursor :=crDefault; + Result :=Nil; + { #todo 1 -oMaxM : the point can be on different areas (Z Order?) } + if (SelectedCropArea=nil) + then for i:=0 to rCropAreas.Count-1 do + 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:=0 to rCropAreas.Count-1 do + begin + if (rCropAreas[i]<>SelectedCropArea) then + begin + Result :=TestArea(rCropAreas[i]); + if (Result<>nil) then break; + end; + end; + end; end; { ============================================================================ } From 452c6e01ed7e3e6673b96e1d5a670f55e337b4cd Mon Sep 17 00:00:00 2001 From: Massimo Magnano Date: Fri, 1 Sep 2023 13:12:29 +0200 Subject: [PATCH 08/34] Crop Area Z Order OverAnchor gives precedence to the selected area than Z Order --- bgraimagemanipulation.pas | 80 ++++++- .../unitbgraimagemanipulationdemo.lfm | 213 ++++++++++++++++-- .../unitbgraimagemanipulationdemo.pas | 98 +++++++- 3 files changed, 366 insertions(+), 25 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index b99535d..9196e66 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -67,7 +67,7 @@ - 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 - - OverAnchor gives precedence to the selected area + -09 - OverAnchor gives precedence to the selected area than Z Order ============================================================================ } @@ -129,6 +129,7 @@ interface end; TBGRAImageManipulation = class; + TCropAreaList = class; { TCropArea } BoolParent = (bFalse=0, bTrue=1, bParent=2); @@ -136,6 +137,7 @@ TBGRAImageManipulation = class; TCropArea = class(TObject) protected fOwner :TBGRAImageManipulation; + OwnerList:TCropAreaList; rScaledArea:TRect; rArea :TRectF; rAreaUnit:TResolutionUnit; @@ -192,6 +194,12 @@ TCropArea = class(TObject) AUserData: Integer = 0); destructor Destroy; override; + //ZOrder + procedure BringToFront; + procedure BringToBack; + procedure BringForward; + procedure BringBackward; + property Area:TRectF read rArea write setArea; property AreaUnit:TResolutionUnit read rAreaUnit write setAreaUnit; property Top:Single read getTop write setTop; @@ -454,6 +462,7 @@ procedure TCropArea.GetImageResolution(var resX, resY: Single); resY :=fOwner.fImageBitmap.ResolutionY; //No Resolution use predefined Monitor Values + { #todo -oMaxM : Use Application Resolution? } if (resX=0) then if (rAreaUnit=ruPixelsPerInch) then resX :=96 @@ -891,7 +900,7 @@ procedure TCropArea.setAreaUnit(AValue: TResolutionUnit); end; ruNone : begin //No Image Resolution, Use predefined Monitor Values - imgResX :=96; + imgResX :=96; { #todo -oMaxM : Use Application Resolution? } imgResY :=96; end; end; @@ -1100,6 +1109,7 @@ constructor TCropArea.Create(AOwner: TBGRAImageManipulation; AArea: TRectF; inherited Create; if (AOwner = Nil) then raise Exception.Create('Owner TBGRAImageManipulation is Nil'); + OwnerList :=nil; fOwner :=AOwner; rAreaUnit :=AAreaUnit; Area := AArea; @@ -1117,6 +1127,52 @@ destructor TCropArea.Destroy; 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 (curIndexnil) then + try + curIndex :=OwnerList.IndexOf(Self); + if (curIndex>0) + then OwnerList.Move(curIndex, curIndex-1); + except + end; +end; + { TCropAreaList } procedure TCropAreaList.setLoading(AValue: Boolean); @@ -1141,10 +1197,16 @@ procedure TCropAreaList.setCropArea(aIndex: Integer; const Value: TCropArea); procedure TCropAreaList.Notify(Ptr: Pointer; Action: TListNotification); begin Case Action of - lnAdded: if assigned(fOwner.rOnCropAreaAdded) - then fOwner.rOnCropAreaAdded(fOwner, Ptr); - lnDeleted: if assigned(fOwner.rOnCropAreaDeleted) - then fOwner.rOnCropAreaDeleted(fOwner, Ptr); + 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); @@ -2077,9 +2139,8 @@ function TBGRAImageManipulation.isOverAnchor(APoint :TPoint; var AnchorSelected AnchorSelected :=[]; ACursor :=crDefault; Result :=Nil; - { #todo 1 -oMaxM : the point can be on different areas (Z Order?) } if (SelectedCropArea=nil) - then for i:=0 to rCropAreas.Count-1 do + then for i:=rCropAreas.Count-1 downto 0 do //downto so respect ZOrder begin Result :=TestArea(rCropAreas[i]); if (Result<>nil) then break; @@ -2088,7 +2149,7 @@ function TBGRAImageManipulation.isOverAnchor(APoint :TPoint; var AnchorSelected //Gives precedence to the selected area Result :=TestArea(SelectedCropArea); if (Result=nil) then - for i:=0 to rCropAreas.Count-1 do + for i:=rCropAreas.Count-1 downto 0 do begin if (rCropAreas[i]<>SelectedCropArea) then begin @@ -2363,6 +2424,7 @@ procedure TBGRAImageManipulation.Render; FillColor := BGRA(0, 0, 0, 128); Mask := TBGRABitmap.Create(WorkRect.Right - WorkRect.Left, WorkRect.Bottom - WorkRect.Top, FillColor); + { #todo 1 -oMaxM : Test Z Order Draw correctly } for i:=0 to rCropAreas.Count-1 do begin curCropArea :=rCropAreas[i]; diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm index a8919e8..6ada9bd 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm @@ -1437,6 +1437,15 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Style = csDropDownList TabOrder = 4 end + object SpeedButton1: TSpeedButton + Left = 208 + Height = 19 + Top = 48 + Width = 32 + AutoSize = True + Caption = ':Tests' + OnClick = SpeedButton1Click + end end object BGRAImageManipulation: TBGRAImageManipulation Left = 170 @@ -2287,17 +2296,17 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo TabOrder = 1 end object edAspectPersonal: TEdit - Left = 48 + Left = 47 Height = 23 - Top = 282 + Top = 298 Width = 87 AutoSize = False TabOrder = 2 end object rgAspect: TRadioGroup - Left = 23 + Left = 22 Height = 76 - Top = 200 + Top = 216 Width = 137 AutoFill = True Caption = 'Aspect Ratio' @@ -2319,9 +2328,9 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo TabOrder = 3 end object btApplyAspectRatio: TSpeedButton - Left = 136 + Left = 135 Height = 22 - Top = 283 + Top = 299 Width = 23 Glyph.Data = { C6070000424DC607000000000000360000002800000016000000160000000100 @@ -2390,15 +2399,6 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo } OnClick = btApplyAspectRatioClick end - object SpeedButton1: TSpeedButton - Left = 9 - Height = 19 - Top = 306 - Width = 32 - AutoSize = True - Caption = ':Tests' - OnClick = SpeedButton1Click - end object edLeft: TFloatSpinEdit Left = 56 Height = 23 @@ -2459,6 +2459,189 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo TabOrder = 7 Value = 50 end + object Label1: TLabel + Left = 8 + Height = 15 + Top = 176 + Width = 46 + Caption = 'Z Order :' + end + object btZFront: TSpeedButton + Left = 58 + Height = 22 + Hint = 'To Front' + Top = 176 + Width = 23 + Glyph.Data = { + 36040000424D3604000000000000360000002800000010000000100000000100 + 2000000000000004000000000000000000000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00004E00070050000900500009005000090050000900500009005000090050 + 00090050000900500009005000090050000900660003FFFFFF00FFFFFF00004E + 003C2F6B2FFE3C783CFF307930FF267C26FF1C801CFF168416FF098608FF0C90 + 0BFF10960FFF149712FF179615FF21941EFF1A9D15D6FFFFFF00FFFFFF00FFFF + FF00185D18C472A872FD5F9F5FFF469246FF2D8A2DFF198919FF008900FF0096 + 00FF009F00FF00A000FF009700FF219D1EFC038C0050FFFFFF00FFFFFF00FFFF + FF000058000C276927E864A764FE4A9B4AFF319431FF1D951DFF009600FF00A7 + 00FF00B400FF00B700FF18AD17FC088D0486FFFFFF00FFFFFF00FFFFFF00FFFF + FF00004E0007004C0031347D34F850A350FF379C37FF239E23FF009D00FF00B2 + 00FF00C500FF0EC60DFB0C910ABB0050000900660003FFFFFF00FFFFFF00004E + 003C357235FE438343FF388138FF4FA04FFF3B9F3BFF249F24FF04A004FF00B3 + 00FF00C700FF1BC11AFF17AD15FF21A41EFF1A9715D6FFFFFF00FFFFFF00FFFF + FF001A5D1AC380BA80FD72B572FF58A758FF40A040FF279D27FF089C08FF00AA + 00FF00B800FF00BC00FF00B000FF24A121FC03870052FFFFFF00FFFFFF00FFFF + FF000040000A296B29E775B475FE5DA85DFF449F44FF2B992BFF099109FF009A + 00FF00A400FF00A500FF1CA21BFC08860488FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF0000510028367836F763A863FF499B49FF309230FF0A820AFF0088 + 00FF008E00FF129411FB108A0CBEFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00004E0053408340FB4E974EFF358B35FF0B720BFF0073 + 00FF097E09FC138610E100520005FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF0004530489418541FC3A833AFF0C600CFF0361 + 03FE147D12F400780020FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000A5D0AC03A793AFC0B4C0BFF116B + 10FB0369004CFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000040000A156314E6266425FE0469 + 027EFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00005B002829782787FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00 + } + OnClick = btZFrontClick + end + object btZBack: TSpeedButton + Left = 82 + Height = 22 + Hint = 'To Back' + Top = 176 + Width = 23 + Glyph.Data = { + 36040000424D3604000000000000360000002800000010000000100000000100 + 2000000000000004000000000000000000000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00005B00280461027DFFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000040000A156714E6176416FE0469 + 027EFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000A5E0AC03A853AFC0B600BFF1174 + 10FB0369004CFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF0004530489418D41FC3A8F3AFF0C720CFF0375 + 03FE148412F400780020FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00004E0053408840FB4E9F4EFF359635FF0B830BFF0088 + 00FF099109FC138A10E100520005FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF0000510028367B36F763AD63FF49A249FF309C30FF0A910AFF009A + 00FF00A400FF12A611FB108A0CBEFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF000040000A296C29E775B675FE5DAB5DFF44A344FF2BA02BFF099C09FF00AA + 00FF00B800FF00BC00FF1CAE1BFC08860488FFFFFF00FFFFFF00FFFFFF00FFFF + FF001A5D1AC380BA80FD72B672FF58A958FF40A240FF27A127FF08A208FF00B3 + 00FF00C700FF00D300FF00BE00FF24A421FC03870052FFFFFF00FFFFFF00004D + 0021357235FE438343FF388138FF4FA04FFF3B9F3BFF249F24FF049F04FF00B2 + 00FF00C500FF1BBE1AFF17AC15FF21A31EFF058C00D2FFFFFF00FFFFFF00FFFF + FF00004E0007004C0031347B34F950A050FF379837FF239823FF009600FF00A7 + 00FF00B400FF0EB40DFB0C9B0ABB0050000900660003FFFFFF00FFFFFF00FFFF + FF000058000C276827E864A464FE4A964AFF318E31FF1D8B1DFF008900FF0096 + 00FF009F00FF00A000FF18A117FC088D0486FFFFFF00FFFFFF00FFFFFF00FFFF + FF00185C18C472A572FD5F9B5FFF468B46FF2D812DFF197C19FF007800FF0083 + 00FF008900FF008900FF008100FF21951EFC038C0050FFFFFF00FFFFFF00004D + 00212F6A2FFE3C743CFF307430FF267626FF1A781AFF117A11FF017800FF017F + 00FF018200FF028200FF178C15FF218B1EFF059100D2FFFFFF00FFFFFF00FFFF + FF00004E00070050000900500009005000090050000900500009005000090050 + 00090050000900500009005000090050000900660003FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00 + } + OnClick = btZBackClick + end + object btZDown: TSpeedButton + Left = 135 + Height = 22 + Hint = 'Down' + Top = 176 + Width = 23 + Glyph.Data = { + 36040000424D3604000000000000360000002800000010000000100000000100 + 2000000000000004000000000000000000000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000058005303610058FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00005100341E6A1DF90C620BFA0465 + 003EFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF0000420012166715EB388238FE045D04FE0E76 + 0CF0006E0018FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00008000010D5D0DCB469246FC348C34FF057005FF077A + 07FB0D7F0BD800800001FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00055205984E974EFC479C47FF2F942FFF068206FF008A + 00FF0E920DFB0C8109AEFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00004E00614D904DFC5BA95BFF43A043FF2D9D2DFF029102FF009D + 00FF00A500FF17A015FC067F0277FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF0000480032418141F970B670FF57A957FF3EA33EFF2AA22AFF009D00FF00AE + 00FF00BA00FF00BA00FF1BA118FB04830044FFFFFF00FFFFFF00FFFFFF000042 + 0012327032ED83C183FE6BB26BFF52A752FF3AA23AFF25A425FF00A300FF00B8 + 00FF00CC00FF00D000FF05B805FE1A9816F300870019FFFFFF00FFFFFF00004D + 00854A844AFF458545FF3A853AFF2D882DFF228B22FF178F17FF019000FF019E + 00FF02A900FF02AB00FF18AA16FF28A325FF038D009AFFFFFF00FFFFFF000000 + 0001005000090050000900500009005000090050000900500009005000090050 + 00090050000900500009005000090050000900000001FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00 + } + OnClick = btZDownClick + end + object btZUp: TSpeedButton + Left = 111 + Height = 22 + Hint = 'Up' + Top = 176 + Width = 23 + Glyph.Data = { + 36040000424D3604000000000000360000002800000010000000100000000100 + 2000000000000004000000000000000000000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000000 + 0001005000090050000900500009005000090050000900500009005000090050 + 00090050000900500009005000090050000900000001FFFFFF00FFFFFF00004D + 00854A844AFF458545FF3A853AFF2D892DFF258D25FF1C931CFF0A9609FF0DA4 + 0CFF11B00FFF15B413FF18AB16FF29A427FF068E039BFFFFFF00FFFFFF000042 + 0012327032ED83C083FE6BB16BFF52A552FF3A9F3AFF259F25FF009D00FF00AE + 00FF00BB00FF00BA00FF05AC05FE1A9616F300870019FFFFFF00FFFFFF00FFFF + FF0000480032418041F970B370FF57A557FF3E9D3EFF2A9A2AFF009000FF009D + 00FF00A500FF00A400FF038E00FB04830044FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00004E00614D8C4DFC5BA45BFF439943FF2D932DFF028002FF008A + 00FF008F00FF179515FC067F0277FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00055205984E924EFC479447FF2F872FFF067106FF0075 + 00FF0E810DFB0C8109AEFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00008000010D5A0DCB468946FC347F34FF055D05FF0767 + 07FB0D7D0BD800800001FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF0000420012166215EB387638FE044804FE0E71 + 0CF0006E0018FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00005100341E631DF91B661AFA0465 + 003EFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0000580053337F3064FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00 + } + OnClick = btZUpClick + end end end object OpenPictureDialog: TOpenPictureDialog diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas index 29b8577..8d9b382 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas @@ -42,7 +42,7 @@ 2013-10-13 - Massimo Magnano - Add multi crop demo - 2023-08 - Resolution + 2023-08 - Resolution, Save in various formats, Z Order ============================================================================ } @@ -99,6 +99,7 @@ TFormBGRAImageManipulationDemo = class(TForm) edUnit_Type: TComboBox; edWidth: TFloatSpinEdit; KeepAspectRatio: TCheckBox; + Label1: TLabel; lbResolution: TLabel; lbAspectRatio: TLabel; lbOptions: TLabel; @@ -112,6 +113,10 @@ TFormBGRAImageManipulationDemo = class(TForm) RateCompression: TTrackBar; SelectDirectoryDialog1: TSelectDirectoryDialog; SpeedButton1: TSpeedButton; + btZFront: TSpeedButton; + btZBack: TSpeedButton; + btZDown: TSpeedButton; + btZUp: TSpeedButton; procedure btnGetAspectRatioFromImageClick(Sender: TObject); procedure btnLoadCropListClick(Sender: TObject); procedure btnOpenPictureClick(Sender: TObject); @@ -121,6 +126,10 @@ TFormBGRAImageManipulationDemo = class(TForm) procedure btnSavePictureAllClick(Sender: TObject); procedure btnSavePictureClick(Sender: TObject); procedure btnSetAspectRatioClick(Sender: TObject); + procedure btZBackClick(Sender: TObject); + procedure btZDownClick(Sender: TObject); + procedure btZFrontClick(Sender: TObject); + procedure btZUpClick(Sender: TObject); procedure edHeightEditingDone(Sender: TObject); procedure edLeftEditingDone(Sender: TObject); procedure edNameChange(Sender: TObject); @@ -154,6 +163,7 @@ TFormBGRAImageManipulationDemo = class(TForm) procedure FillBoxUI(ABox :TCropArea); procedure SaveCallBack(Bitmap :TBGRABitmap; CropArea: TCropArea); + procedure UpdateBoxList; public { public declarations } end; @@ -268,6 +278,28 @@ procedure TFormBGRAImageManipulationDemo.SaveCallBack(Bitmap :TBGRABitmap; CropA Bitmap.SaveToFile(SelectDirectoryDialog1.FileName+DirectorySeparator+CropArea.Name+'.'+ext); end; +procedure TFormBGRAImageManipulationDemo.UpdateBoxList; +var + i :Integer; + CropArea, + SelArea:TCropArea; + +begin + cbBoxList.OnChange:=nil; + + //SelArea :=BGRAImageManipulation.SelectedCropArea; + cbBoxList.Clear; + for i:=0 to BGRAImageManipulation.CropAreas.Count-1 do + begin + CropArea :=BGRAImageManipulation.CropAreas.items[i]; + cbBoxList.AddItem(CropArea.Name, CropArea); + end; + //BGRAImageManipulation.SelectedCropArea :=SelArea; + cbBoxList.ItemIndex:=cbBoxList.Items.IndexOfObject(BGRAImageManipulation.SelectedCropArea); + + cbBoxList.OnChange:=@cbBoxListChange; +end; + procedure TFormBGRAImageManipulationDemo.btnSavePictureAllClick(Sender: TObject); begin if SelectDirectoryDialog1.Execute then @@ -291,6 +323,70 @@ procedure TFormBGRAImageManipulationDemo.btnSetAspectRatioClick(Sender: TObject) end; end; +procedure TFormBGRAImageManipulationDemo.btZBackClick(Sender: TObject); +var + CropArea :TCropArea; + +begin + if (cbBoxList.ItemIndex>-1) then + begin + CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); + if CropArea<>nil + then begin + CropArea.BringToBack; + UpdateBoxList; + end; + end; +end; + +procedure TFormBGRAImageManipulationDemo.btZDownClick(Sender: TObject); +var + CropArea :TCropArea; + +begin + if (cbBoxList.ItemIndex>-1) then + begin + CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); + if CropArea<>nil + then begin + CropArea.BringBackward; + UpdateBoxList; + end; + end; +end; + +procedure TFormBGRAImageManipulationDemo.btZFrontClick(Sender: TObject); +var + CropArea :TCropArea; + +begin + if (cbBoxList.ItemIndex>-1) then + begin + CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); + if CropArea<>nil + then begin + CropArea.BringToFront; + UpdateBoxList; + end; + end; +end; + +procedure TFormBGRAImageManipulationDemo.btZUpClick(Sender: TObject); +var + CropArea :TCropArea; + +begin + if (cbBoxList.ItemIndex>-1) then + begin + CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); + if CropArea<>nil + then begin + CropArea.BringForward; + UpdateBoxList; + end; + end; +end; + procedure TFormBGRAImageManipulationDemo.edHeightEditingDone(Sender: TObject); var CropArea :TCropArea; From 4d981b66e1a01e3d043d1672ca037e79b6a9480b Mon Sep 17 00:00:00 2001 From: Johann ELSASS Date: Fri, 1 Sep 2023 16:17:40 +0200 Subject: [PATCH 09/34] fix compilation for LCL 2.2 --- bgraimagemanipulation.pas | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index 9196e66..38b3708 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -828,7 +828,8 @@ procedure TCropArea.setArea(AValue: TRectF); calcHeight, calcWidth, swapV :Single; begin - if rArea=AValue then Exit; + if (rArea.TopLeft = AValue.TopLeft) and + (rArea.BottomRight = AValue.BottomRight) then Exit; if (AValue.Left > AValue.Right) then begin @@ -1250,7 +1251,7 @@ procedure TCropAreaList.Load(const XMLConf: TXMLConfig); for i :=0 to newCount-1 do begin curItemPath :='Item' + IntToStr(i); - newArea :=Rect(0,0,0,0); + newArea :=RectF(0,0,0,0); XMLConf.OpenKey(curItemPath); XMLConf.OpenKey('Area'); newArea.Left :=StrToFloat(XMLConf.GetValue('Left', '0')); @@ -2947,7 +2948,7 @@ function TBGRAImageManipulation.addCropArea(AArea: TRectF; AAreaUnit: TResolutio function TBGRAImageManipulation.addScaledCropArea(AArea: TRect; ARotate: double; AUserData: Integer): TCropArea; begin - Result :=Self.addCropArea(Rect(0,0,0,0), ruNone, ARotate, AUserData); + Result :=Self.addCropArea(RectF(0,0,0,0), ruNone, ARotate, AUserData); Result.ScaledArea :=AArea; if (fMouseCaught) From 5ef710f91653e1cece4f4b63f75d53d58d1fc210 Mon Sep 17 00:00:00 2001 From: Massimo Magnano Date: Tue, 5 Sep 2023 12:11:48 +0200 Subject: [PATCH 10/34] Use Form.PixelPerInch when no Resolution; deleted setBitmap old code Use Form.PixelPerInch when no Resolution; CropArea Max in it's resolution unit; AspectRatio don't Trunc calculated values; deleted setBitmap old code --- bgraimagemanipulation.pas | 313 ++++++++++-------- .../ProjectBGRAImageManipulationDemo.lpi | 1 + .../ProjectBGRAImageManipulationDemo.lpr | 2 + .../unitbgraimagemanipulationdemo.pas | 15 +- 4 files changed, 178 insertions(+), 153 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index 38b3708..2e22e10 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -163,6 +163,8 @@ TCropArea = class(TObject) 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; @@ -173,7 +175,7 @@ TCropArea = class(TObject) procedure Render_Refresh; - procedure GetImageResolution(var resX, resY:Single); + procedure GetImageResolution(var resX, resY:Single; var resUnit:TResolutionUnit); procedure CalculateScaledAreaFromArea; procedure CalculateAreaFromScaledArea; function GetPixelArea(const AValue: TRectF):TRect; @@ -206,6 +208,8 @@ TCropArea = class(TObject) 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; @@ -276,6 +280,7 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) fImageBitmap, fResampledBitmap, fBackground, fVirtualScreen: TBGRABitmap; function getAnchorSize: byte; + function getPixelsPerInch: Integer; procedure setAnchorSize(const Value: byte); function getEmpty: boolean; procedure setBitmap(const Value: TBGRABitmap); @@ -355,6 +360,7 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) property MinHeight: integer Read fMinHeight Write setMinHeight; property MinWidth: integer Read fMinWidth Write setMinWidth; property Empty: boolean Read getEmpty; + property PixelsPerInch: Integer read getPixelsPerInch; //Events property OnCropAreaAdded:TCropAreaEvent read rOnCropAreaAdded write rOnCropAreaAdded; @@ -456,22 +462,23 @@ procedure TCropArea.Render_Refresh; end; end; -procedure TCropArea.GetImageResolution(var resX, resY: Single); +procedure TCropArea.GetImageResolution(var resX, resY: Single; var resUnit: TResolutionUnit); begin resX :=fOwner.fImageBitmap.ResolutionX; resY :=fOwner.fImageBitmap.ResolutionY; + resUnit :=fOwner.fImageBitmap.ResolutionUnit; - //No Resolution use predefined Monitor Values - { #todo -oMaxM : Use Application Resolution? } - if (resX=0) - then if (rAreaUnit=ruPixelsPerInch) - then resX :=96 - else resX :=96/2.54; + 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; - if (resY=0) - then if (rAreaUnit=ruPixelsPerInch) - then resY :=96 - else resY :=96/2.54; + resY :=resX; + end; end; function TCropArea.getIsNullSize: Boolean; @@ -555,7 +562,8 @@ procedure TCropArea.setHeight(AValue: Single); if curKeepAspectRatio then begin //The Height is Changed recalculate the Width - rArea.Width :=Trunc(abs(AValue) * (curRatio.Horizontal / curRatio.Vertical)); + rArea.Width :=abs(AValue) * (curRatio.Horizontal / curRatio.Vertical); + { #todo -oMaxM : should you check the maximum Width? } end; rArea.Height:=AValue; @@ -586,7 +594,8 @@ procedure TCropArea.setWidth(AValue: Single); if curKeepAspectRatio then begin //The Width is Changed recalculate the Height - rArea.Height :=Trunc(abs(AValue) * (curRatio.Vertical / curRatio.Horizontal)); + rArea.Height :=abs(AValue) * (curRatio.Vertical / curRatio.Horizontal); + { #todo -oMaxM : should you check the maximum Height? } end; rArea.Width:=AValue; @@ -599,6 +608,48 @@ procedure TCropArea.setWidth(AValue: Single); then fOwner.rOnCropAreaChanged(fOwner, Self); 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); @@ -608,101 +659,103 @@ procedure TCropArea.CalculateScaledAreaFromArea; var xRatio, yRatio: Single; resX, resY: Single; + resUnit:TResolutionUnit; begin - if not(isNullSize) then - begin - if (fOwner.fImageBitmap.Width=0) or (fOwner.fImageBitmap.Height=0) - then begin // Null size Image ScaledArea=Area - rScaledArea.Left := Trunc(rArea.Left); - rScaledArea.Right := Trunc(rArea.Right); - rScaledArea.Top := Trunc(rArea.Top); - rScaledArea.Bottom := Trunc(rArea.Bottom); - end - else begin // Calculate Scaled Area given Scale and Resolution - xRatio := fOwner.fResampledBitmap.Width / fOwner.fImageBitmap.Width; - yRatio := fOwner.fResampledBitmap.Height / fOwner.fImageBitmap.Height; - resX :=1; //if rAreaUnit=ruNone use only Ratio - resY :=1; + 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; - if (rAreaUnit<>ruNone) then - begin - GetImageResolution(resX, resY); + resX :=1; //if rAreaUnit=ruNone use only Ratio + resY :=1; - //Do Conversion from/to inch/cm - if (rAreaUnit <> fOwner.fImageBitmap.ResolutionUnit) then - begin - if (rAreaUnit=ruPixelsPerInch) - then begin //Bitmap is in Cm - resX :=resX/2.54; - resY :=resY/2.54; - end - else begin //Bitmap is in Inch - resX :=resX*2.54; - resY :=resY*2.54; - end - end; - end; + if (rAreaUnit<>ruNone) then + begin + GetImageResolution(resX, resY, resUnit); - //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; + //Do Conversion from/to inch/cm + if (rAreaUnit <> resUnit) then + begin + if (rAreaUnit=ruPixelsPerInch) + then begin //Bitmap is in Cm, i'm in Inch + resX :=resX*2.54; + resY :=resY*2.54; + end + else begin //Bitmap is in Inch, i'm in Cm + resX :=resX/2.54; + resY :=resY/2.54; + end; + end; + 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 - if (fOwner.fImageBitmap.Width=0) or (fOwner.fImageBitmap.Height=0) - then begin // Null size Image Area=ScaledArea - { #note : In wich Resolution Unit? } - rArea.Left := rScaledArea.Left; - rArea.Right := rScaledArea.Right; - rArea.Top := rScaledArea.Top; - rArea.Bottom := rScaledArea.Bottom; - end - else begin // Calculate Scaled Area given Scale and Resolution - xRatio := fOwner.fResampledBitmap.Width / fOwner.fImageBitmap.Width; - yRatio := fOwner.fResampledBitmap.Height / fOwner.fImageBitmap.Height; - resX :=1; //if rAreaUnit=ruNone use only Ratio - resY :=1; + // 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; - if (rAreaUnit<>ruNone) then - begin - GetImageResolution(resX, resY); + resX :=1; //if rAreaUnit=ruNone use only Ratio + resY :=1; - //Do Conversion from/to inch/cm - if (rAreaUnit <> fOwner.fImageBitmap.ResolutionUnit) then - begin - if (rAreaUnit=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; + if (rAreaUnit<>ruNone) then + begin + GetImageResolution(resX, resY, resUnit); - 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; + //Do Conversion from/to inch/cm + if (rAreaUnit <> resUnit) then + begin + if (rAreaUnit=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; + + 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) @@ -718,10 +771,10 @@ function TCropArea.GetPixelArea(const AValue: TRectF): TRect; resX :=1; resY :=1; end - else GetImageResolution(resX, resY); + else GetImageResolution(resX, resY, resUnit); //Do Conversion from/to inch/cm - if (rAreaUnit <> fOwner.fImageBitmap.ResolutionUnit) then + if (rAreaUnit <> resUnit) then begin if (rAreaUnit=ruPixelsPerInch) then begin //Bitmap is in Cm @@ -901,14 +954,14 @@ procedure TCropArea.setAreaUnit(AValue: TResolutionUnit); end; ruNone : begin //No Image Resolution, Use predefined Monitor Values - imgResX :=96; { #todo -oMaxM : Use Application Resolution? } - imgResY :=96; + imgResX :=fOwner.PixelsPerInch; + imgResY :=fOwner.PixelsPerInch; end; end; //Paranoid test to avoid zero divisions - if (imgResX=0) then imgResX :=96; - if (imgResY=0) then imgResY :=96; + if (imgResX=0) then imgResX :=fOwner.PixelsPerInch; + if (imgResY=0) then imgResY :=fOwner.PixelsPerInch; Case rAreaUnit of ruPixelsPerInch : begin @@ -2402,16 +2455,23 @@ procedure TBGRAImageManipulation.Render; BorderColor, SelectColor, FillColor: TBGRAPixel; curCropArea :TCropArea; curCropAreaRect :TRect; - i :Integer; + i: Integer; + emptyImg: Boolean; begin // This procedure render main feature of engine + emptyImg :=fImageBitmap.Empty; + + { #todo 1 -oMaxM : Draw Crop Areas if emptyImg, wich size? User defined as properties? } + (*fBackground.SetSize(2472, 3496); //may be fResampledBitmap + RepaintBackground;*) + // Render background fVirtualScreen.BlendImage(0, 0, fBackground, boLinearBlend); // Render the image - if (not (fImageBitmap.Empty)) then + if not(emptyImg) then begin // Find the working area of the component WorkRect := getWorkRect; @@ -2429,6 +2489,8 @@ procedure TBGRAImageManipulation.Render; for i:=0 to rCropAreas.Count-1 do begin curCropArea :=rCropAreas[i]; + (*if emptyImg + then curCropArea.CalculateScaledAreaFromArea;*) curCropAreaRect :=curCropArea.ScaledArea; if (curCropArea = SelectedCropArea) @@ -2565,6 +2627,13 @@ function TBGRAImageManipulation.getAnchorSize: byte; 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; @@ -2632,21 +2701,10 @@ function TBGRAImageManipulation.getBitmap(ACropArea :TCropArea = Nil): TBGRABitm end; procedure TBGRAImageManipulation.setBitmap(const Value: TBGRABitmap); - - function min(const Value: integer; const MinValue: integer): integer; - begin - if (Value < MinValue) then - Result := MinValue - else - Result := Value; - end; - var - SourceRect, OriginalRect, DestinationRect: TRect; + DestinationRect: TRect; ResampledBitmap: TBGRACustomBitmap; - xRatio, yRatio: double; curCropArea :TCropArea; - curCropAreaRect :TRect; i :Integer; begin @@ -2686,47 +2744,16 @@ procedure TBGRAImageManipulation.setBitmap(const Value: TBGRABitmap); ResampledBitmap.Free; end; - // Calculate scale from original size and destination size - with OriginalRect do - begin - Left := 0; - Right := fResampledBitmap.Width; - Top := 0; - Bottom := fResampledBitmap.Height; - end; for i:=0 to rCropAreas.Count-1 do begin curCropArea :=rCropAreas[i]; - curCropAreaRect :=curCropArea.ScaledArea; + curCropArea.CalculateScaledAreaFromArea; - // Resize crop area - if ((abs(curCropAreaRect.Right - curCropAreaRect.Left) > 0) and - (abs(curCropAreaRect.Bottom - curCropAreaRect.Top) > 0)) then - begin - // Calculate source rectangle in original scale - xRatio := fImageBitmap.Width / (OriginalRect.Right - OriginalRect.Left); - yRatio := fImageBitmap.Height / (OriginalRect.Bottom - OriginalRect.Top); - with SourceRect do + if curCropArea.isNullSize then begin - Left := Round(curCropAreaRect.Left * xRatio); - Right := Round(curCropAreaRect.Right * xRatio); - Top := Round(curCropAreaRect.Top * yRatio); - Bottom := Round(curCropAreaRect.Bottom * yRatio); + // A Null-size crop selection (delete it or assign max size?) + //CalcMaxSelection(curCropArea); end; - - // Calculate destination rectangle in new scale - xRatio := fImageBitmap.Width / (DestinationRect.Right - DestinationRect.Left); - yRatio := fImageBitmap.Height / (DestinationRect.Bottom - DestinationRect.Top); - curCropArea.ScaledArea :=Rect(Round(SourceRect.Left / xRatio), - Round(SourceRect.Top / yRatio), - Round(SourceRect.Right / xRatio), - Round(SourceRect.Bottom / yRatio)); - end - else - begin - // A Null-size crop selection (delete it or assign max size?) - //CalcMaxSelection(curCropArea); - end; end; finally // Force Render Struct diff --git a/test/test_bgraimagemanipulation/ProjectBGRAImageManipulationDemo.lpi b/test/test_bgraimagemanipulation/ProjectBGRAImageManipulationDemo.lpi index a32824a..6c1da25 100644 --- a/test/test_bgraimagemanipulation/ProjectBGRAImageManipulationDemo.lpi +++ b/test/test_bgraimagemanipulation/ProjectBGRAImageManipulationDemo.lpi @@ -7,6 +7,7 @@ + diff --git a/test/test_bgraimagemanipulation/ProjectBGRAImageManipulationDemo.lpr b/test/test_bgraimagemanipulation/ProjectBGRAImageManipulationDemo.lpr index 9fcf8e6..2dccb76 100644 --- a/test/test_bgraimagemanipulation/ProjectBGRAImageManipulationDemo.lpr +++ b/test/test_bgraimagemanipulation/ProjectBGRAImageManipulationDemo.lpr @@ -12,6 +12,8 @@ {$R *.res} begin + Application.Scaled:=True; + Application.Title:=''; Application.Initialize; Application.CreateForm(TFormBGRAImageManipulationDemo, FormBGRAImageManipulationDemo); Application.Run; diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas index 8d9b382..02465ca 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas @@ -686,28 +686,23 @@ procedure TFormBGRAImageManipulationDemo.FillBoxUI(ABox: TCropArea); edName.Text :=ABox.Name; edUnit_Type.ItemIndex :=Integer(ABox.AreaUnit); - //really maybe converted to Area Resolution Unit if (ABox.AreaUnit=ruNone) then begin edLeft.DecimalPlaces:=0; edTop.DecimalPlaces:=0; edWidth.DecimalPlaces:=0; edHeight.DecimalPlaces:=0; - edLeft.MaxValue:=BGRAImageManipulation.Bitmap.Width; - edTop.MaxValue:=BGRAImageManipulation.Bitmap.Height; - edWidth.MaxValue:=BGRAImageManipulation.Bitmap.Width; - edHeight.MaxValue:=BGRAImageManipulation.Bitmap.Height; - end + end else begin edLeft.DecimalPlaces:=3; edTop.DecimalPlaces:=3; edWidth.DecimalPlaces:=3; edHeight.DecimalPlaces:=3; - edLeft.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionWidth; - edTop.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionHeight; - edWidth.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionWidth; - edHeight.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionHeight; end; + edLeft.MaxValue:=ABox.MaxWidth; + edTop.MaxValue:=ABox.MaxHeight; + edWidth.MaxValue:=edLeft.MaxValue; + edHeight.MaxValue:=edTop.MaxValue; edLeft.Value :=ABox.Left; edTop.Value :=ABox.Top; From cfe32561ab0d0216478fe1c86a80b966a6932a26 Mon Sep 17 00:00:00 2001 From: Massimo Magnano Date: Wed, 6 Sep 2023 17:06:26 +0200 Subject: [PATCH 11/34] EmptyImage property; CropAreas when Image is Empty; Old Code deleted and optimized; Added EmptyImage property; CropAreas render when Image is Empty; Old Code deleted and optimized; CreateResampledBitmap method; Resize Create ResampledBitmap before Crop Areas; --- bgraimagemanipulation.pas | 460 ++++++++---------- .../unitbgraimagemanipulationdemo.lfm | 9 +- .../unitbgraimagemanipulationdemo.pas | 17 +- 3 files changed, 231 insertions(+), 255 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index 2e22e10..b891178 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -68,6 +68,7 @@ -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; ============================================================================ } @@ -249,6 +250,32 @@ TCropAreaList = class(TObjectList) TgetAllBitmapsCallback = procedure (Bitmap :TBGRABitmap; CropArea: TCropArea) 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; + 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 rResolutionUnit default ruPixelsPerInch; + property ResolutionWidth: Single read rResolutionWidth write rResolutionWidth; + property ResolutionHeight: Single read rResolutionHeight write rResolutionHeight; + property ShowBorder: Boolean read rShowBorder write rShowBorder default False; + end; + { TBGRAImageManipulation } TCropAreaEvent = procedure (AOwner: TBGRAImageManipulation; CropArea: TCropArea) of object; @@ -286,6 +313,7 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) 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); @@ -301,6 +329,7 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) rOnSelectedCropAreaChanged: TCropAreaEvent; rOnCropAreaLoad: TCropAreaLoadEvent; rOnCropAreaSave: TCropAreaSaveEvent; + rEmptyImage: TBGRAEmptyImage; function ApplyDimRestriction(Coords: TCoord; Direction: TDirection; Bounds: TRect; AKeepAspectRatio:Boolean): TCoord; function ApplyRatioToAxes(Coords: TCoord; Direction: TDirection; Bounds: TRect; ACropArea :TCropArea = Nil): TCoord; @@ -311,7 +340,9 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) function getImageRect(Picture: TBGRABitmap): TRect; function getWorkRect: TRect; function isOverAnchor(APoint :TPoint; var AnchorSelected :TDirection; var ACursor :TCursor) :TCropArea; + procedure CreateResampledBitmap; + procedure Loaded; override; procedure Paint; override; procedure RepaintBackground; procedure Resize; override; @@ -346,6 +377,7 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) property SelectedCropArea :TCropArea read rSelectedCropArea write setSelectedCropArea; property CropAreas :TCropAreaList read rCropAreas; + property PixelsPerInch: Integer read getPixelsPerInch; published { Published declarations } @@ -360,7 +392,7 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) property MinHeight: integer Read fMinHeight Write setMinHeight; property MinWidth: integer Read fMinWidth Write setMinWidth; property Empty: boolean Read getEmpty; - property PixelsPerInch: Integer read getPixelsPerInch; + property EmptyImage: TBGRAEmptyImage read rEmptyImage write setEmptyImage stored True; //Events property OnCropAreaAdded:TCropAreaEvent read rOnCropAreaAdded write rOnCropAreaAdded; @@ -1433,6 +1465,34 @@ procedure TCropAreaList.SaveToFile(const FileName: string); end; end; +{ TBGRAEmptyImage } + +function TBGRAEmptyImage.getHeight: Integer; +begin + 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; +begin + Case rResolutionUnit of + ruNone : Result :=Trunc(rResolutionWidth); + ruPixelsPerInch : Result :=Round(fOwner.PixelsPerInch*rResolutionWidth); + ruPixelsPerCentimeter : Result :=Round((fOwner.PixelsPerInch/2.54)*rResolutionWidth); + end; +end; + +constructor TBGRAEmptyImage.Create(AOwner: TBGRAImageManipulation); +begin + inherited Create; + fOwner :=AOwner; + rAllow :=False; + rShowBorder :=False; + rResolutionUnit:=ruPixelsPerInch; +end; { TBGRAImageManipulation } @@ -2214,6 +2274,52 @@ function TBGRAImageManipulation.isOverAnchor(APoint :TPoint; var AnchorSelected end; 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 (csDesigning in ComponentState) then exit; + + if Self.Empty and rEmptyImage.Allow then + begin + fImageBitmap.Free; + fImageBitmap :=TBGRABitmap.Create(rEmptyImage.Width, rEmptyImage.Height); + fImageBitmap.ResolutionUnit :=ruPixelsPerInch; + fImageBitmap.ResolutionX :=Self.PixelsPerInch; + fImageBitmap.ResolutionY :=fImageBitmap.ResolutionX; + CreateResampledBitmap; + end; + + // Force Render Struct + RepaintBackground; + Render; +end; + { ============================================================================ } { =====[ Component Definition ]=============================================== } { ============================================================================ } @@ -2245,7 +2351,6 @@ constructor TBGRAImageManipulation.Create(AOwner: TComponent); // Calculate the ratio fGCD := getGCD(fAspectX, fAspectY); - // Determine the ratio of scale per axle with fRatio do begin @@ -2268,15 +2373,17 @@ constructor TBGRAImageManipulation.Create(AOwner: TComponent); // Create render surface fVirtualScreen := TBGRABitmap.Create(Width, Height); + rEmptyImage :=TBGRAEmptyImage.Create(Self); + // Initialize crop area rCropAreas :=TCropAreaList.Create(Self); rCropAreas.Name:='CropAreas'; rNewCropArea :=Nil; rSelectedCropArea :=Nil; - // Force Render Struct + (* // Force Render Struct RepaintBackground; - Render; + Render; *) fMouseCaught := False; end; @@ -2287,6 +2394,7 @@ destructor TBGRAImageManipulation.Destroy; fResampledBitmap.Free; fBackground.Free; fVirtualScreen.Free; + rEmptyImage.Free; rCropAreas.Free; inherited Destroy; @@ -2388,8 +2496,6 @@ procedure TBGRAImageManipulation.Resize; end; var - DestinationRect: TRect; - ResampledBitmap:TBGRACustomBitmap; i :Integer; curCropArea :TCropArea; @@ -2402,38 +2508,17 @@ procedure TBGRAImageManipulation.Resize; min(Self.Height, (fBorderSize * 2 + fAnchorSize + fMinHeight))); fVirtualScreen.InvalidateBitmap; - // Resample the image - if (not (fImageBitmap.Empty)) then + CreateResampledBitmap; + + for i:=0 to rCropAreas.Count-1 do begin - // Get the resampled dimensions to scale image for draw in component - DestinationRect := getImageRect(fImageBitmap); + curCropArea :=rCropAreas[i]; + curCropArea.CalculateScaledAreaFromArea; - for i:=0 to rCropAreas.Count-1 do + if curCropArea.isNullSize then begin - curCropArea :=rCropAreas[i]; - curCropArea.CalculateScaledAreaFromArea; - (* - if curCropArea.isNullSize then - begin begin - // A Null-size crop selection - //delCropArea(curCropArea); ? - end; - *) - end; - - // 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; + // A Null-size crop selection (delete it or assign max size?) + //CalcMaxSelection(curCropArea); end; end; @@ -2461,92 +2546,85 @@ procedure TBGRAImageManipulation.Render; begin // This procedure render main feature of engine - emptyImg :=fImageBitmap.Empty; - - { #todo 1 -oMaxM : Draw Crop Areas if emptyImg, wich size? User defined as properties? } - (*fBackground.SetSize(2472, 3496); //may be fResampledBitmap - RepaintBackground;*) - // Render background fVirtualScreen.BlendImage(0, 0, fBackground, boLinearBlend); // Render the image - if not(emptyImg) then - begin - // Find the working area of the component - WorkRect := getWorkRect; + // Find the working area of the component + WorkRect := getWorkRect; - try - // Draw image - fVirtualScreen.BlendImage(WorkRect.Left, WorkRect.Top, fResampledBitmap, boLinearBlend); + 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); + // 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); - { #todo 1 -oMaxM : Test Z Order Draw correctly } - for i:=0 to rCropAreas.Count-1 do - begin - curCropArea :=rCropAreas[i]; - (*if emptyImg - then curCropArea.CalculateScaledAreaFromArea;*) - curCropAreaRect :=curCropArea.ScaledArea; + if Self.Empty and rEmptyImage.ShowBorder + then Mask.Rectangle(0,0,fResampledBitmap.Width-1, fResampledBitmap.Height-1, BorderColor, BGRAPixelTransparent); - if (curCropArea = SelectedCropArea) - then BorderColor := BGRA(255, 0, 0, 255) - else if (curCropArea = rNewCropArea) - then BorderColor := BGRA(255, 0, 255, 255) - else BorderColor := curCropArea.BorderColor; + { #todo 1 -oMaxM : Test Z Order Draw correctly } + for i:=0 to rCropAreas.Count-1 do + begin + curCropArea :=rCropAreas[i]; + curCropAreaRect :=curCropArea.ScaledArea; - Mask.EraseRectAntialias(curCropAreaRect.Left, curCropAreaRect.Top, curCropAreaRect.Right-1, - curCropAreaRect.Bottom-1, 255); + if (curCropArea = SelectedCropArea) + then BorderColor := BGRA(255, 0, 0, 255) + else if (curCropArea = rNewCropArea) + then BorderColor := BGRA(255, 0, 255, 255) + else BorderColor := curCropArea.BorderColor; - // Draw a selection box - with Rect(curCropAreaRect.Left, curCropAreaRect.Top, curCropAreaRect.Right-1, curCropAreaRect.Bottom-1) do + 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 anchors - BorderColor := BGRABlack; - SelectColor := BGRA(255, 255, 0, 255); - FillColor := BGRA(255, 255, 0, 128); + // Draw anchors + BorderColor := BGRABlack; + SelectColor := BGRA(255, 255, 0, 255); + FillColor := BGRA(255, 255, 0, 128); - // NW - Mask.Rectangle(curCropAreaRect.Left-fAnchorSize, curCropAreaRect.Top-fAnchorSize, + // 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, + // 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, + // 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 + // 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, + 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, + 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 + // 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 @@ -2578,43 +2656,42 @@ procedure TBGRAImageManipulation.Render; ((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, + 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, + 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 + // 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, + 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, + 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, + // 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, + // 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; + finally + fVirtualScreen.BlendImage(WorkRect.Left, WorkRect.Top, Mask, boLinearBlend); + Mask.Free; end; end; @@ -2672,7 +2749,7 @@ procedure TBGRAImageManipulation.setAnchorSize(const Value: byte); function TBGRAImageManipulation.getEmpty: boolean; begin - Result := fImageBitmap.Empty; + Result := fImageBitmap.Empty or (fImageBitmap.Width = 0) or (fImageBitmap.Height = 0); end; function TBGRAImageManipulation.getResampledBitmap(ACropArea :TCropArea = Nil): TBGRABitmap; @@ -2702,10 +2779,8 @@ function TBGRAImageManipulation.getBitmap(ACropArea :TCropArea = Nil): TBGRABitm procedure TBGRAImageManipulation.setBitmap(const Value: TBGRABitmap); var - DestinationRect: TRect; - ResampledBitmap: TBGRACustomBitmap; - curCropArea :TCropArea; - i :Integer; + curCropArea: TCropArea; + i: Integer; begin if (Value <> fImageBitmap) then @@ -2713,36 +2788,25 @@ procedure TBGRAImageManipulation.setBitmap(const Value: TBGRABitmap); try // Clear actual image fImageBitmap.Free; - fImageBitmap := TBGRABitmap.Create(Value.Width, Value.Height); - - // Prevent empty image - if Value.Empty then - exit; - - // Prevent null image - if (Value.Width = 0) or (Value.Height = 0) then - exit; - - // Associate the new bitmap - fImageBitmap.Assign(Value); - - // 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; + + if Value.Empty or (Value.Width = 0) or (Value.Height = 0) + then begin + if EmptyImage.Allow + then begin + fImageBitmap :=TBGRABitmap.Create(EmptyImage.Width, EmptyImage.Height); + fImageBitmap.ResolutionUnit :=ruPixelsPerInch; + fImageBitmap.ResolutionX :=Self.PixelsPerInch; + fImageBitmap.ResolutionY :=fImageBitmap.ResolutionX; + end + else exit; + end + else begin + fImageBitmap :=TBGRABitmap.Create(Value.Width, Value.Height); + + fImageBitmap.Assign(Value); // Associate the new bitmap + end; + + CreateResampledBitmap; for i:=0 to rCropAreas.Count-1 do begin @@ -2765,11 +2829,8 @@ procedure TBGRAImageManipulation.setBitmap(const Value: TBGRABitmap); procedure TBGRAImageManipulation.rotateLeft; var - SourceRect, OriginalRect, DestinationRect: TRect; - TempBitmap, ResampledBitmap: TBGRACustomBitmap; - xRatio, yRatio: double; + TempBitmap: TBGRACustomBitmap; curCropArea :TCropArea; - curCropAreaRect :TRect; i :Integer; begin @@ -2782,65 +2843,18 @@ procedure TBGRAImageManipulation.rotateLeft; TempBitmap := fImageBitmap.RotateCCW; fImageBitmap.Assign(TempBitmap); - // Get the resampled dimensions to scale image for draw in component - DestinationRect := getImageRect(fImageBitmap); + CreateResampledBitmap; - // 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; - - // Calculate scale from original size and destination size - with OriginalRect do - begin - Left := 0; - Right := fResampledBitmap.Width; - Top := 0; - Bottom := fResampledBitmap.Height; - end; for i:=0 to rCropAreas.Count-1 do begin curCropArea :=rCropAreas[i]; - curCropAreaRect :=curCropArea.ScaledArea; + curCropArea.CalculateScaledAreaFromArea; - // Resize crop area - if ((abs(curCropAreaRect.Right - curCropAreaRect.Left) > 0) and - (abs(curCropAreaRect.Bottom - curCropAreaRect.Top) > 0)) then - begin - // Calculate source rectangle in original scale - xRatio := fImageBitmap.Width / (OriginalRect.Right - OriginalRect.Left); - yRatio := fImageBitmap.Height / (OriginalRect.Bottom - OriginalRect.Top); - with SourceRect do + if curCropArea.isNullSize then begin - Left := Round(curCropAreaRect.Left * xRatio); - Right := Round(curCropAreaRect.Right * xRatio); - Top := Round(curCropAreaRect.Top * yRatio); - Bottom := Round(curCropAreaRect.Bottom * yRatio); + // A Null-size crop selection (delete it or assign max size?) + //CalcMaxSelection(curCropArea); end; - - // Calculate destination rectangle in new scale - xRatio := fImageBitmap.Width / (DestinationRect.Right - DestinationRect.Left); - yRatio := fImageBitmap.Height / (DestinationRect.Bottom - DestinationRect.Top); - curCropArea.ScaledArea :=Rect(Round(SourceRect.Left / xRatio), - Round(SourceRect.Top / yRatio), - Round(SourceRect.Right / xRatio), - Round(SourceRect.Bottom / yRatio)); - end - else - begin - // A Null-size crop selection (delete it or assign max size?) - //CalcMaxSelection(curCropArea); - end; end; finally // Force Render Struct @@ -2852,11 +2866,8 @@ procedure TBGRAImageManipulation.rotateLeft; procedure TBGRAImageManipulation.rotateRight; var - SourceRect, OriginalRect, DestinationRect: TRect; - TempBitmap, ResampledBitmap: TBGRACustomBitmap; - xRatio, yRatio: double; + TempBitmap: TBGRACustomBitmap; curCropArea :TCropArea; - curCropAreaRect :TRect; i :Integer; begin @@ -2869,66 +2880,18 @@ procedure TBGRAImageManipulation.rotateRight; TempBitmap := fImageBitmap.RotateCW; fImageBitmap.Assign(TempBitmap); - // Get the resampled dimensions to scale image for draw in component - DestinationRect := getImageRect(fImageBitmap); + CreateResampledBitmap; - // 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; - - // Calculate scale from original size and destination size - with OriginalRect do - begin - Left := 0; - Right := fResampledBitmap.Width; - Top := 0; - Bottom := fResampledBitmap.Height; - end; for i:=0 to rCropAreas.Count-1 do begin curCropArea :=rCropAreas[i]; - curCropAreaRect :=curCropArea.ScaledArea; + curCropArea.CalculateScaledAreaFromArea; - // Resize crop area - if ((abs(curCropAreaRect.Right - curCropAreaRect.Left) > 0) and - (abs(curCropAreaRect.Bottom - curCropAreaRect.Top) > 0)) then - begin - // Calculate source rectangle in original scale - xRatio := fImageBitmap.Width / (OriginalRect.Right - OriginalRect.Left); - yRatio := fImageBitmap.Height / (OriginalRect.Bottom - OriginalRect.Top); - with SourceRect do + if curCropArea.isNullSize then begin - Left := Round(curCropAreaRect.Left * xRatio); - Right := Round(curCropAreaRect.Right * xRatio); - Top := Round(curCropAreaRect.Top * yRatio); - Bottom := Round(curCropAreaRect.Bottom * yRatio); + // A Null-size crop selection (delete it or assign max size?) + //CalcMaxSelection(curCropArea); end; - - // Calculate destination rectangle in new scale - xRatio := fImageBitmap.Width / (DestinationRect.Right - DestinationRect.Left); - yRatio := fImageBitmap.Height / (DestinationRect.Bottom - DestinationRect.Top); - curCropArea.ScaledArea :=Rect(Round(SourceRect.Left / xRatio), - Round(SourceRect.Top / yRatio), - Round(SourceRect.Right / xRatio), - Round(SourceRect.Bottom / yRatio)); - end - else - begin - // Calculates maximum crop selection - CalcMaxSelection(curCropArea); - end; - end; finally // Force Render Struct @@ -3194,6 +3157,11 @@ procedure TBGRAImageManipulation.setAspectRatio(const Value: string); end; end; +procedure TBGRAImageManipulation.setEmptyImage(AValue: TBGRAEmptyImage); +begin + rEmptyImage.Assign(AValue); +end; + procedure TBGRAImageManipulation.setMinHeight(const Value: integer); begin if (Value <> fMinHeight) then diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm index 6ada9bd..4bfe8dd 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm @@ -1,7 +1,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo - Left = 236 + Left = 261 Height = 480 - Top = 116 + Top = 125 Width = 898 Caption = 'Demonstration of TBGRAImageManipulation' ClientHeight = 480 @@ -1457,6 +1457,11 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo AspectRatio = '3:4' MinHeight = 40 MinWidth = 30 + EmptyImage.Allow = True + EmptyImage.ResolutionUnit = ruPixelsPerCentimeter + EmptyImage.ResolutionWidth = 21 + EmptyImage.ResolutionHeight = 29.7000007629395 + EmptyImage.ShowBorder = True OnCropAreaAdded = AddedCrop OnCropAreaDeleted = DeletedCrop OnCropAreaChanged = ChangedCrop diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas index 02465ca..4524a31 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas @@ -193,19 +193,22 @@ procedure TFormBGRAImageManipulationDemo.btnOpenPictureClick(Sender: TObject); // Finally, associate the image into component BGRAImageManipulation.Bitmap := Bitmap; Bitmap.Free; - edUnit_Type.ItemIndex:=Integer(BGRAImageManipulation.Bitmap.ResolutionUnit); lbResolution.Caption:='Resolution : '+#13#10+' '+ FloatToStr(BGRAImageManipulation.Bitmap.ResolutionX)+' x '+ - FloatToStr(BGRAImageManipulation.Bitmap.ResolutionY)+' '+edUnit_Type.Items[edUnit_Type.ItemIndex]+#13#10+ + FloatToStr(BGRAImageManipulation.Bitmap.ResolutionY)+' '+edUnit_Type.Items[Integer(BGRAImageManipulation.Bitmap.ResolutionUnit)]+#13#10+ ' '+FloatToStr(BGRAImageManipulation.Bitmap.ResolutionWidth)+' x '+FloatToStr(BGRAImageManipulation.Bitmap.ResolutionHeight)+#13#10+ ' pixels '+IntToStr(BGRAImageManipulation.Bitmap.Width)+' x '+IntToStr(BGRAImageManipulation.Bitmap.Height); - edLeft.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionWidth; - edTop.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionHeight; - edWidth.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionWidth; - edHeight.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionHeight; - //BGRAImageManipulation.addCropArea(Rect(100,100,220,260)); + if (BGRAImageManipulation.SelectedCropArea=nil) + then begin + edUnit_Type.ItemIndex:=Integer(BGRAImageManipulation.Bitmap.ResolutionUnit); + edLeft.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionWidth; + edTop.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionHeight; + edWidth.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionWidth; + edHeight.MaxValue:=BGRAImageManipulation.Bitmap.ResolutionHeight; + end + else FillBoxUI(BGRAImageManipulation.SelectedCropArea); end; end; From 3bedc66dcb387292914d1dddad997fc2eaead167 Mon Sep 17 00:00:00 2001 From: Massimo Magnano Date: Thu, 7 Sep 2023 13:24:14 +0200 Subject: [PATCH 12/34] Rotate Empty Image and Image with Resolution Rotate Empty Image and Image with Resolution; Use Frame3d when Empty ShowBorder; Demo Added set to Empty. --- bgraimagemanipulation.pas | 40 ++++- .../unitbgraimagemanipulationdemo.lfm | 161 +++++++++++++++++- .../unitbgraimagemanipulationdemo.pas | 15 ++ 3 files changed, 205 insertions(+), 11 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index b891178..2fb53b1 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -176,6 +176,7 @@ TCropArea = class(TObject) procedure Render_Refresh; + { #note 2 -oMaxM : all the Code that use Resolution may be under if BGRABitmapVersion > 11050400 } procedure GetImageResolution(var resX, resY:Single; var resUnit:TResolutionUnit); procedure CalculateScaledAreaFromArea; procedure CalculateAreaFromScaledArea; @@ -2535,7 +2536,7 @@ procedure TBGRAImageManipulation.Resize; different transparency level for easy viewing of what will be cut. } procedure TBGRAImageManipulation.Render; var - WorkRect: TRect; + WorkRect, emptyRect: TRect; Mask: TBGRABitmap; BorderColor, SelectColor, FillColor: TBGRAPixel; curCropArea :TCropArea; @@ -2562,8 +2563,12 @@ procedure TBGRAImageManipulation.Render; FillColor := BGRA(0, 0, 0, 128); Mask := TBGRABitmap.Create(WorkRect.Right - WorkRect.Left, WorkRect.Bottom - WorkRect.Top, FillColor); - if Self.Empty and rEmptyImage.ShowBorder - then Mask.Rectangle(0,0,fResampledBitmap.Width-1, fResampledBitmap.Height-1, BorderColor, BGRAPixelTransparent); + 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; { #todo 1 -oMaxM : Test Z Order Draw correctly } for i:=0 to rCropAreas.Count-1 do @@ -2835,16 +2840,24 @@ procedure TBGRAImageManipulation.rotateLeft; begin try - // Prevent empty image - if fImageBitmap.Empty then - exit; + // Prevent empty image if not Allowed + if Self.Empty and not(rEmptyImage.Allow) + then exit; // Rotate bitmap TempBitmap := fImageBitmap.RotateCCW; + + { #todo 5 -oMaxM : Maybe done in TBGRACustomBitmap.RotateCCW } + {$if BGRABitmapVersion > 11050400} + TempBitmap.ResolutionUnit:=fImageBitmap.ResolutionUnit; + TempBitmap.ResolutionX:=fImageBitmap.ResolutionX; + TempBitmap.ResolutionY:=fImageBitmap.ResolutionY; + {$endif} 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]; @@ -2872,16 +2885,25 @@ procedure TBGRAImageManipulation.rotateRight; begin try - // Prevent empty image - if fImageBitmap.Empty then - exit; + // Prevent empty image if not Allowed + if Self.Empty and not(rEmptyImage.Allow) + then exit; // Rotate bitmap TempBitmap := fImageBitmap.RotateCW; + + { #todo 5 -oMaxM : Maybe done in TBGRACustomBitmap.RotateCCW } + {$if BGRABitmapVersion > 11050400} + TempBitmap.ResolutionUnit:=fImageBitmap.ResolutionUnit; + TempBitmap.ResolutionX:=fImageBitmap.ResolutionX; + TempBitmap.ResolutionY:=fImageBitmap.ResolutionY; + {$endif} + 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]; diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm index 4bfe8dd..ea85be6 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm @@ -123,7 +123,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Left = 10 Height = 40 Top = 8 - Width = 200 + Width = 134 StateClicked.Background.Gradient1.StartColor = 8404992 StateClicked.Background.Gradient1.EndColor = 4194304 StateClicked.Background.Gradient1.GradientType = gtRadial @@ -1440,12 +1440,169 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo object SpeedButton1: TSpeedButton Left = 208 Height = 19 - Top = 48 + Top = 56 Width = 32 AutoSize = True Caption = ':Tests' OnClick = SpeedButton1Click end + object btnEmptyImage: TBCButton + Left = 152 + Height = 40 + Hint = 'Get aspect ratio from image' + Top = 8 + Width = 88 + StateClicked.Background.Gradient1.StartColor = 8404992 + StateClicked.Background.Gradient1.EndColor = 4194304 + StateClicked.Background.Gradient1.GradientType = gtRadial + StateClicked.Background.Gradient1.Point1XPercent = 50 + StateClicked.Background.Gradient1.Point1YPercent = 100 + StateClicked.Background.Gradient1.Point2XPercent = 0 + StateClicked.Background.Gradient1.Point2YPercent = 0 + StateClicked.Background.Gradient2.StartColor = clWhite + StateClicked.Background.Gradient2.EndColor = clBlack + StateClicked.Background.Gradient2.GradientType = gtLinear + StateClicked.Background.Gradient2.Point1XPercent = 0 + StateClicked.Background.Gradient2.Point1YPercent = 0 + StateClicked.Background.Gradient2.Point2XPercent = 0 + StateClicked.Background.Gradient2.Point2YPercent = 100 + StateClicked.Background.Gradient1EndPercent = 100 + StateClicked.Background.Style = bbsGradient + StateClicked.Border.Style = bboNone + StateClicked.FontEx.Color = 16770790 + StateClicked.FontEx.FontQuality = fqSystemClearType + StateClicked.FontEx.Shadow = True + StateClicked.FontEx.ShadowRadius = 2 + StateClicked.FontEx.ShadowOffsetX = 1 + StateClicked.FontEx.ShadowOffsetY = 1 + StateClicked.FontEx.Style = [fsBold] + StateHover.Background.Gradient1.StartColor = 16744448 + StateHover.Background.Gradient1.EndColor = 8404992 + StateHover.Background.Gradient1.GradientType = gtRadial + StateHover.Background.Gradient1.Point1XPercent = 50 + StateHover.Background.Gradient1.Point1YPercent = 100 + StateHover.Background.Gradient1.Point2XPercent = 0 + StateHover.Background.Gradient1.Point2YPercent = 0 + StateHover.Background.Gradient2.StartColor = clWhite + StateHover.Background.Gradient2.EndColor = clBlack + StateHover.Background.Gradient2.GradientType = gtLinear + StateHover.Background.Gradient2.Point1XPercent = 0 + StateHover.Background.Gradient2.Point1YPercent = 0 + StateHover.Background.Gradient2.Point2XPercent = 0 + StateHover.Background.Gradient2.Point2YPercent = 100 + StateHover.Background.Gradient1EndPercent = 100 + StateHover.Background.Style = bbsGradient + StateHover.Border.Style = bboNone + StateHover.FontEx.Color = clWhite + StateHover.FontEx.FontQuality = fqSystemClearType + StateHover.FontEx.Shadow = True + StateHover.FontEx.ShadowRadius = 2 + StateHover.FontEx.ShadowOffsetX = 1 + StateHover.FontEx.ShadowOffsetY = 1 + StateHover.FontEx.Style = [fsBold] + StateNormal.Background.Gradient1.StartColor = 4194304 + StateNormal.Background.Gradient1.EndColor = 8405056 + StateNormal.Background.Gradient1.GradientType = gtLinear + StateNormal.Background.Gradient1.Point1XPercent = 0 + StateNormal.Background.Gradient1.Point1YPercent = 0 + StateNormal.Background.Gradient1.Point2XPercent = 0 + StateNormal.Background.Gradient1.Point2YPercent = 100 + StateNormal.Background.Gradient2.StartColor = 8405056 + StateNormal.Background.Gradient2.EndColor = 4194304 + StateNormal.Background.Gradient2.GradientType = gtRadial + StateNormal.Background.Gradient2.Point1XPercent = 50 + StateNormal.Background.Gradient2.Point1YPercent = 100 + StateNormal.Background.Gradient2.Point2XPercent = 0 + StateNormal.Background.Gradient2.Point2YPercent = 0 + StateNormal.Background.Gradient1EndPercent = 60 + StateNormal.Background.Style = bbsGradient + StateNormal.Border.Style = bboNone + StateNormal.FontEx.Color = 16770790 + StateNormal.FontEx.FontQuality = fqSystemClearType + StateNormal.FontEx.Shadow = True + StateNormal.FontEx.ShadowRadius = 2 + StateNormal.FontEx.ShadowOffsetX = 1 + StateNormal.FontEx.ShadowOffsetY = 1 + StateNormal.FontEx.Style = [fsBold] + Caption = 'Empty' + Color = clNone + DropDownWidth = 16 + DropDownArrowSize = 8 + GlobalOpacity = 255 + Glyph.Data = { + C6070000424DC607000000000000360000002800000016000000160000000100 + 2000000000009007000000000000000000000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0064646466D4D4 + D4DAD6D6D6DCD8D8D8DCD8D8D8DCD8D8D8DCD8D8D8DCD8D8D8DCD9D9D9DCD9D9 + D9DCD4D3D1DCABA9A4BD0F0F0F11FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0087878779F7F7F7FFF8F8F8FFFAFA + FAFFFAFAFAFFFBFBFBFFFCFCFCFFFDFDFDFFFEFEFEFFFFFFFFFFF7F7F7FFDCDB + DAFFBBBAB5E625252515FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF0087878779F7F7F7FFF8F8F8FFFAFAFAFFFAFAFAFFFBFB + FBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFFF9F9F9FFE2E2E2FFD8D8D7FFBDBD + B9F23D463D1DFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF0087878779F7F7F6FFF7F7F7FFF9F9F9FFFAFAFAFFFBFBFBFFFCFCFCFFFDFD + FDFFFDFDFDFFFEFEFEFFFBFBFBFFEEEEEEFFCCCBCBFFF9FAFAFFBEBFBBF53030 + 301AFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0087878779F6F6 + F6FFF7F7F7FFF9F9F9FFFAFAFAFFFBFBFBFFFCFCFCFFFCFCFCFFFDFDFDFFFEFE + FEFFFEFEFEFFF7F7F7FFDCDCDCFFE1E0E0FFF3F4F4FFBFBFBAF01A1A1A14FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0087878779F6F6F5FFF7F7F6FFF8F8 + F8FFFAFAFAFFFBFBFBFFFBFBFBFFFCFCFCFFFDFDFDFFFDFDFDFFFEFEFEFFFCFC + FCFFF6F6F6FFEAEAEAFFD4D4D4FFD9D8D7FFBEBEB7DC00000011FFFFFF00FFFF + FF00FFFFFF00FFFFFF0087878779F5F5F5FFF6F6F6FFF8F8F8FFF9F9F9FFFAFA + FAFFFBFBFBFFFCFCFCFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFFFDFDFDFFFAFA + FAFFF6F6F6FFF2F2F2FFF4F3F3FF7C7A7873FFFFFF00FFFFFF00FFFFFF00FFFF + FF0087878779F4F4F4FFF5F5F5FFF7F7F7FFF9F9F9FFFAFAFAFFFBFBFBFFFBFB + FBFFFCFCFCFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFDFD + FDFFFDFDFDFF87878779FFFFFF00FFFFFF00FFFFFF00FFFFFF0087878779F4F4 + F3FFF4F4F4FFF6F6F6FFF8F8F8FFF9F9F9FFFAFAFAFFFBFBFBFFFBFBFBFFFCFC + FCFFFCFCFCFFFDFDFDFFFDFDFDFFFDFDFDFFF9F9F9FFF6F6F6FFFEFEFEFF8787 + 8779FFFFFF00FFFFFF00FFFFFF00FFFFFF0087878779F3F3F2FFF3F3F3FFF5F5 + F5FFF7F7F6FFF8F8F8FFFAFAFAFFFAFAFAFFFBFBFBFFFBFBFBFFFCFCFCFFF0F0 + F0FFDEDEDEFFBDBDBDFFB7B7B7FFBEBEBEFFDEDEDEFF8585857AFFFFFF00FFFF + FF00FFFFFF00FFFFFF0087878779F2F2F1FFF2F2F2FFF4F4F4FFF5F5F5FFF7F7 + F7FFF8F8F8FFFAFAFAFFFAFAFAFFFBFBFBFFE9E9E9FF9494AEFF2D2DA8FF0A0A + B7FF0707BCFF0D0DBCFF2E2EA9FF343456B200000015FFFFFF00FFFFFF00FFFF + FF0087878779F1F1F0FFF1F1F1FFF3F3F2FFF4F4F4FFF6F6F5FFF7F7F7FFF8F8 + F8FFF9F9F9FFE7E7E7FF7575AEFF0606BBFF0004B3FF0010B0FF0023B1FF0024 + B4FF000FB8FF0101BCFD000068840000000AFFFFFF00FFFFFF0087878779F0F0 + F0FFF0F0EFFFF2F2F1FFF3F3F2FFF4F4F4FFF6F6F5FFF7F7F7FFF8F8F8FFACAC + C3FF0404B4FF0009ABFF3054C0FF0052C5FF006BD3FF0074D6FF2F7FD6FF0014 + B0FF0000B0F800004D4FFFFFFF00FFFFFF0087878779EFEFEFFFEFEFEEFFF0F0 + F0FFF2F2F1FFF3F3F2FFF4F4F4FFF5F5F5FFF2F2F2FF3434A7FF0005B1FF304B + BCFFFDFDFEFF7CAEDEFF008BDDFF7CC3EAFFFDFEFEFF2A6AC6FF000BB2FF0000 + 93C6FFFFFF00FFFFFF0087878779EEEEEDFFEEEEEDFFEFEFEEFFF0F0F0FFF1F1 + F1FFF3F3F2FFF4F4F3FFE3E3E2FF0E0EA5FF000DBCFF0038BDFF7CA2D6FFFFFF + FFFFB9D5EAFFFFFFFFFF78A9D9FF0052CCFF001AC0FF00009DF2FFFFFF00FFFF + FF0087878779EDEDECFFECECEBFFEEEEEDFFEFEFEEFFF0F0EFFFF1F1F0FFF2F2 + F2FFE2E2E1FF08089CFF0011C0FF0044CBFF004EB8FFB9CEE7FFFFFFFFFFB8C7 + DFFF004AB5FF003FCDFF0010BBFF00009BF5FFFFFF00FFFFFF0087878779EBEB + EAFFEBEBEAFFECECEBFFEDEDECFFEEEEEEFFEFEFEFFFF0F0F0FFEFEFEFFF0E0E + 9AFF010CB6FF0D3BC3FF8499CDFFFFFFFFFFBED1EDFFFFFFFFFF808FC7FF0C23 + B8FF0102B3FF000095ECFFFFFF00FFFFFF0087878779E9E9E8FFE9E8E7FFEAEA + E9FFECECEBFFEDEDECFFEEEEEDFFEFEFEEFFF0F0EFFF4949B0FF1013ADFF626F + CAFFFDFDFEFF97A6DBFF4461CCFF99A3DEFFFDFDFEFF5A5AC0FF0F0FAFFF0000 + 8DB4FFFFFF00FFFFFF0087878779E7E7E6FFE7E6E5FFE8E8E7FFEAEAE9FFEBEB + EAFFECECEBFFEDEDECFFEEEEEDFFC7C7E1FF080895FF494AC6FF8E8FE1FF7779 + DAFF797CDBFF7575D7FF8D8DDFFF4747C3FF000097F90000971FFFFFFF00FFFF + FF0064646466CECECEDAD1CFCFDCD1D1D1DCD2D1D1DCD2D2D2DCD2D2D2DCD2D2 + D2DCD3D3D2DCD3D3D3DC8A8ABDE71212A0FE6262CFFF9A9AE1FFA7A7E5FF9999 + E3FF6060D0FF0D0D9BF800009B4CFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF000000B0170000A0A10404A1E42A2AC0ED0303BDD90000A4970000 + A923FFFFFF00FFFFFF00 + } + OnClick = btnEmptyImageClick + ParentColor = False + Rounding.RoundX = 12 + Rounding.RoundY = 12 + RoundingDropDown.RoundX = 1 + RoundingDropDown.RoundY = 1 + TextApplyGlobalOpacity = False + MemoryUsage = bmuHigh + end end object BGRAImageManipulation: TBGRAImageManipulation Left = 170 diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas index 4524a31..d6b6bda 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas @@ -76,6 +76,7 @@ TFormBGRAImageManipulationDemo = class(TForm) btApplyAspectRatio: TSpeedButton; btBox_Add: TBGRASpeedButton; btBox_Del: TBGRASpeedButton; + btnEmptyImage: TBCButton; btnLoadCropList: TBCButton; btnSaveCropList: TBCButton; btnSavePictureAll: TBCButton; @@ -117,6 +118,7 @@ TFormBGRAImageManipulationDemo = class(TForm) btZBack: TSpeedButton; btZDown: TSpeedButton; btZUp: TSpeedButton; + procedure btnEmptyImageClick(Sender: TObject); procedure btnGetAspectRatioFromImageClick(Sender: TObject); procedure btnLoadCropListClick(Sender: TObject); procedure btnOpenPictureClick(Sender: TObject); @@ -232,6 +234,19 @@ procedure TFormBGRAImageManipulationDemo.btnGetAspectRatioFromImageClick( end; end; +procedure TFormBGRAImageManipulationDemo.btnEmptyImageClick(Sender: TObject); +var + emptyImg :TBGRABitmap; + +begin + try + emptyImg :=TBGRABitmap.Create(0, 0); + BGRAImageManipulation.Bitmap :=emptyImg; + finally + emptyImg.Free; + end; +end; + procedure TFormBGRAImageManipulationDemo.btnLoadCropListClick(Sender: TObject); begin try From f4a4826ddaeb1dba95b57e6a117ac3fad6392eb2 Mon Sep 17 00:00:00 2001 From: Massimo Magnano Date: Fri, 8 Sep 2023 12:54:54 +0200 Subject: [PATCH 13/34] Resolution Copy (getBitmap, Rotate) moved to bgrabitmap --- bgraimagemanipulation.pas | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index 2fb53b1..7bda41a 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -1152,11 +1152,6 @@ function TCropArea.getResampledBitmap: TBGRABitmap; // Create bitmap to put image on final scale Result := TBGRABitmap.Create(rScaledArea.Width, rScaledArea.Height); - {$if BGRABitmapVersion > 11050400} - Result.ResolutionUnit:=CropBitmap.ResolutionUnit; - Result.ResolutionX:=CropBitmap.ResolutionX; - Result.ResolutionY:=CropBitmap.ResolutionY; - {$endif} // Resize the cropped image to final scale ResampledBitmap := CropBitmap.Resample(rScaledArea.Width, rScaledArea.Height, rmFineResample); @@ -1179,11 +1174,6 @@ function TCropArea.getBitmap: TBGRABitmap; try // Get the cropped image on selected region in original scale Result :=fOwner.fImageBitmap.GetPart(GetPixelArea(rArea)); - {$if BGRABitmapVersion > 11050400} - Result.ResolutionUnit:=fOwner.fImageBitmap.ResolutionUnit; - Result.ResolutionX:=fOwner.fImageBitmap.ResolutionX; - Result.ResolutionY:=fOwner.fImageBitmap.ResolutionY; - {$endif} except if (Result<>nil) then FreeAndNil(Result); @@ -2846,13 +2836,6 @@ procedure TBGRAImageManipulation.rotateLeft; // Rotate bitmap TempBitmap := fImageBitmap.RotateCCW; - - { #todo 5 -oMaxM : Maybe done in TBGRACustomBitmap.RotateCCW } - {$if BGRABitmapVersion > 11050400} - TempBitmap.ResolutionUnit:=fImageBitmap.ResolutionUnit; - TempBitmap.ResolutionX:=fImageBitmap.ResolutionX; - TempBitmap.ResolutionY:=fImageBitmap.ResolutionY; - {$endif} fImageBitmap.Assign(TempBitmap); CreateResampledBitmap; @@ -2891,14 +2874,6 @@ procedure TBGRAImageManipulation.rotateRight; // Rotate bitmap TempBitmap := fImageBitmap.RotateCW; - - { #todo 5 -oMaxM : Maybe done in TBGRACustomBitmap.RotateCCW } - {$if BGRABitmapVersion > 11050400} - TempBitmap.ResolutionUnit:=fImageBitmap.ResolutionUnit; - TempBitmap.ResolutionX:=fImageBitmap.ResolutionX; - TempBitmap.ResolutionY:=fImageBitmap.ResolutionY; - {$endif} - fImageBitmap.Assign(TempBitmap); CreateResampledBitmap; From 791edf8b894ffcc8d711fd76e92aba5fb6c290ba Mon Sep 17 00:00:00 2001 From: Massimo Magnano Date: Mon, 11 Sep 2023 11:52:42 +0200 Subject: [PATCH 14/34] XML Use Laz2_XMLCfg in fpc;bgrabitmap minversion=11.5.5 --- bgracontrols.lpk | 2 +- bgraimagemanipulation.pas | 139 ++++++++++-------- .../unitbgraimagemanipulationdemo.lfm | 2 + .../unitbgraimagemanipulationdemo.pas | 18 ++- 4 files changed, 99 insertions(+), 62 deletions(-) diff --git a/bgracontrols.lpk b/bgracontrols.lpk index 76183b1..d056b07 100644 --- a/bgracontrols.lpk +++ b/bgracontrols.lpk @@ -361,7 +361,7 @@ - + diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index 7bda41a..be1195c 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -69,6 +69,7 @@ - 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 ============================================================================ } @@ -81,13 +82,19 @@ interface +{$IFDEF FPC} + {$DEFINE USE_Laz2_XMLCfg} +{$ENDIF} + uses - Classes, Contnrs, SysUtils, {$IFDEF FPC}LCLIntf, LResources, FPImage, {$ENDIF} + Classes, Contnrs, SysUtils, + {$IFDEF FPC}LCLIntf, LResources, FPImage, {$ENDIF} Forms, Controls, Graphics, Dialogs, - {$IFNDEF FPC}Windows, Messages, BGRAGraphics, GraphType, {$ENDIF} - XMLConf, BCBaseCtrls, BGRABitmap, BGRABitmapTypes, BGRAGradientScanner; + {$IFNDEF FPC}Windows, Messages, BGRAGraphics, GraphType,{$ENDIF} + {$IFDEF USE_Laz2_XMLCfg}Laz2_XMLCfg,{$ELSE}XMLConf,{$ENDIF} + BCBaseCtrls, BGRABitmap, BGRABitmapTypes, BGRAGradientScanner; - {$IFNDEF FPC} +{$IFNDEF FPC} const crSizeNW = TCursor(-23); crSizeN = TCursor(-24); @@ -104,7 +111,7 @@ interface crHSplit = TCursor(-14); crVSplit = TCursor(-15); crMultiDrag = TCursor(-16); - {$ENDIF} +{$ENDIF} type TCoord = packed record @@ -1308,50 +1315,47 @@ function TCropAreaList.add(aCropArea: TCropArea): integer; procedure TCropAreaList.Load(const XMLConf: TXMLConfig); var i, newCount, newSelected: integer; - curItemPath: String; + curItemPath, curPath: String; newCropArea: TCropArea; newArea: TRectF; newAreaUnit:TResolutionUnit; begin try - XMLConf.OpenKey(fOwner.Name+'.'+Self.Name); - newCount := XMLConf.GetValue('Count', -1); + curPath :=fOwner.Name+'.'+Self.Name+'/'; + newCount := XMLConf.GetValue(curPath+'Count', -1); if (newCount=-1) then raise Exception.Create('XML Path not Found'); Clear; Loading :=True; - newSelected := XMLConf.GetValue('Selected', 0); + newSelected := XMLConf.GetValue(curPath+'Selected', 0); for i :=0 to newCount-1 do begin - curItemPath :='Item' + IntToStr(i); + curItemPath :=curPath+'Item' + IntToStr(i)+'/'; newArea :=RectF(0,0,0,0); - XMLConf.OpenKey(curItemPath); - XMLConf.OpenKey('Area'); - newArea.Left :=StrToFloat(XMLConf.GetValue('Left', '0')); - newArea.Top :=StrToFloat(XMLConf.GetValue('Top', '0')); - newArea.Width :=StrToFloat(XMLConf.GetValue('Width', IntToStr(fOwner.MinWidth))); - newArea.Height :=StrToFloat(XMLConf.GetValue('Height', IntToStr(fOwner.MinHeight))); - XMLConf.CloseKey; - newAreaUnit :=TResolutionUnit(XMLConf.GetValue('AreaUnit', 0)); - newCropArea :=TCropArea.Create(Self.fOwner, newArea, newAreaUnit); - newCropArea.Loading:=True; - newCropArea.Name :=XMLConf.GetValue('Name', 'Name '+IntToStr(i)); - newCropArea.KeepAspectRatio :=BoolParent(XMLConf.GetValue('KeepAspectRatio', Integer(bParent))); - newCropArea.AspectRatio :=XMLConf.GetValue('AspectRatio', '3:4'); - newCropArea.Rotate :=StrToFloat(XMLConf.GetValue('Rotate', '0')); - - if assigned(fOwner.rOnCropAreaLoad) - then newCropArea.UserData :=fOwner.rOnCropAreaLoad(fOwner, newCropArea, XMLConf, - fOwner.Name+'.'+Self.Name+'/'+curItemPath); - newCropArea.Loading:=False; - XMLConf.CloseKey; + + //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')); + + if assigned(fOwner.rOnCropAreaLoad) + then newCropArea.UserData :=fOwner.rOnCropAreaLoad(fOwner, newCropArea, XMLConf, curItemPath); + newCropArea.Loading:=False; add(newCropArea); end; - XmlConf.CloseKey; if (newSelected Date: Tue, 12 Sep 2023 12:59:42 +0200 Subject: [PATCH 15/34] divide by zero in getImageRect on Component Loading divide by zero in getImageRect on Component Loading (using rLoading because csLoading in ComponentState does not work?) --- bgraimagemanipulation.pas | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index be1195c..1a3413c 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -70,6 +70,7 @@ -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 ============================================================================ } @@ -295,7 +296,6 @@ TBGRAEmptyImage = class(TPersistent) TBGRAImageManipulation = class(TBGRAGraphicCtrl) private { Private declarations } - fAnchorSize: byte; fAnchorSelected: TDirection; fBorderSize: byte; @@ -338,6 +338,7 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) 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; @@ -2325,6 +2326,8 @@ procedure TBGRAImageManipulation.Loaded; CreateResampledBitmap; end; + rLoading:=False; + // Force Render Struct RepaintBackground; Render; @@ -2341,6 +2344,9 @@ constructor TBGRAImageManipulation.Create(AOwner: TComponent); begin inherited Create(AOwner); + //MaxM: csLoading in ComponentState does not work? + rLoading :=True; + // Set default component values inherited Width := 320; inherited Height := 240; @@ -2391,10 +2397,6 @@ constructor TBGRAImageManipulation.Create(AOwner: TComponent); rNewCropArea :=Nil; rSelectedCropArea :=Nil; - (* // Force Render Struct - RepaintBackground; - Render; *) - fMouseCaught := False; end; @@ -2512,6 +2514,9 @@ procedure TBGRAImageManipulation.Resize; 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)), From 553953176d7a5bd9508afb35838f688e0491f07f Mon Sep 17 00:00:00 2001 From: Massimo Magnano Date: Thu, 14 Sep 2023 12:05:12 +0200 Subject: [PATCH 16/34] EmptyImage size to ClientRect when Width/Height=0; Mouse Events when Image is Empty EmptyImage size to ClientRect when Width/Height=0; Mouse Events when Image is Empty --- bgraimagemanipulation.pas | 87 +++++++++++-------- .../unitbgraimagemanipulationdemo.lfm | 1 - .../unitbgraimagemanipulationdemo.pas | 5 -- 3 files changed, 50 insertions(+), 43 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index 1a3413c..73557b5 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -68,9 +68,10 @@ -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; + - 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 ============================================================================ } @@ -349,6 +350,7 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) 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; @@ -1479,21 +1481,41 @@ procedure TCropAreaList.SaveToFile(const FileName: string); { TBGRAEmptyImage } function TBGRAEmptyImage.getHeight: Integer; +var + wRect: TRect; + begin - Case rResolutionUnit of - ruNone : Result :=Trunc(rResolutionHeight); - ruPixelsPerInch : Result :=Round(fOwner.PixelsPerInch*rResolutionHeight); - ruPixelsPerCentimeter : Result :=Round((fOwner.PixelsPerInch/2.54)*rResolutionHeight); - end; + 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 - Case rResolutionUnit of - ruNone : Result :=Trunc(rResolutionWidth); - ruPixelsPerInch : Result :=Round(fOwner.PixelsPerInch*rResolutionWidth); - ruPixelsPerCentimeter : Result :=Round((fOwner.PixelsPerInch/2.54)*rResolutionWidth); - end; + 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; constructor TBGRAEmptyImage.Create(AOwner: TBGRAImageManipulation); @@ -2134,14 +2156,7 @@ function TBGRAImageManipulation.getImageRect(Picture: TBGRABitmap): TRect; { Get work area rectangle } function TBGRAImageManipulation.getWorkRect: TRect; -var - // Number of units to remove from left, right, top, and bottom to get the - // work rectangle - Delta: integer; begin - // Start with the border size - Delta := fBorderSize; - // Get the coordinates of the control if (fVirtualScreen <> nil) then Result := Rect(0, 0, fVirtualScreen.Width, fVirtualScreen.Height) @@ -2149,7 +2164,7 @@ function TBGRAImageManipulation.getWorkRect: TRect; Result := GetClientRect; // Remove the non-work areas from our work rectangle - InflateRect(Result, -Delta, -Delta); + InflateRect(Result, -fBorderSize, -fBorderSize); end; { Check if mouse is over any anchor } @@ -2285,6 +2300,15 @@ function TBGRAImageManipulation.isOverAnchor(APoint :TPoint; var AnchorSelected 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; @@ -2318,11 +2342,7 @@ procedure TBGRAImageManipulation.Loaded; if Self.Empty and rEmptyImage.Allow then begin - fImageBitmap.Free; - fImageBitmap :=TBGRABitmap.Create(rEmptyImage.Width, rEmptyImage.Height); - fImageBitmap.ResolutionUnit :=ruPixelsPerInch; - fImageBitmap.ResolutionX :=Self.PixelsPerInch; - fImageBitmap.ResolutionY :=fImageBitmap.ResolutionX; + CreateEmptyImage; CreateResampledBitmap; end; @@ -2523,6 +2543,9 @@ procedure TBGRAImageManipulation.Resize; 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 @@ -2805,21 +2828,15 @@ procedure TBGRAImageManipulation.setBitmap(const Value: TBGRABitmap); if (Value <> fImageBitmap) then begin try - // Clear actual image - fImageBitmap.Free; - if Value.Empty or (Value.Width = 0) or (Value.Height = 0) then begin if EmptyImage.Allow - then begin - fImageBitmap :=TBGRABitmap.Create(EmptyImage.Width, EmptyImage.Height); - fImageBitmap.ResolutionUnit :=ruPixelsPerInch; - fImageBitmap.ResolutionX :=Self.PixelsPerInch; - fImageBitmap.ResolutionY :=fImageBitmap.ResolutionX; - end + then CreateEmptyImage else exit; end else begin + // Clear actual image + fImageBitmap.Free; fImageBitmap :=TBGRABitmap.Create(Value.Width, Value.Height); fImageBitmap.Assign(Value); // Associate the new bitmap @@ -3482,10 +3499,6 @@ procedure TBGRAImageManipulation.MouseMove(Shift: TShiftState; X, Y: integer); // Find the working area of the component WorkRect := GetWorkRect; - // If image empty - if (fImageBitmap.Empty) then //MaxM: Maybe deleted? - exit; - // If the mouse was originally clicked on the control if fMouseCaught then begin diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm index 896f03c..50422a5 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm @@ -8,7 +8,6 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo ClientWidth = 898 OnCloseQuery = FormCloseQuery OnCreate = FormCreate - OnShow = FormShow ShowHint = True LCLVersion = '3.99.0.0' object Background: TBCPanel diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas index 579b677..a1a27ac 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas @@ -143,7 +143,6 @@ TFormBGRAImageManipulationDemo = class(TForm) procedure edUnit_TypeChange(Sender: TObject); procedure edWidthEditingDone(Sender: TObject); procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); - procedure FormShow(Sender: TObject); procedure KeepAspectRatioClick(Sender: TObject); procedure btBox_AddClick(Sender: TObject); @@ -504,10 +503,6 @@ procedure TFormBGRAImageManipulationDemo.FormCloseQuery(Sender: TObject; closing :=True; end; -procedure TFormBGRAImageManipulationDemo.FormShow(Sender: TObject); -begin -end; - procedure TFormBGRAImageManipulationDemo.KeepAspectRatioClick(Sender: TObject); begin BGRAImageManipulation.KeepAspectRatio := KeepAspectRatio.Checked; From 3490c3188631452432df7b474f54b743d094fb5a Mon Sep 17 00:00:00 2001 From: Massimo Magnano Date: Thu, 14 Sep 2023 13:43:33 +0200 Subject: [PATCH 17/34] corrected Render at Design Time --- bgraimagemanipulation.pas | 2 -- 1 file changed, 2 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index 73557b5..9bdc292 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -2338,8 +2338,6 @@ procedure TBGRAImageManipulation.Loaded; begin inherited Loaded; - if (csDesigning in ComponentState) then exit; - if Self.Empty and rEmptyImage.Allow then begin CreateEmptyImage; From cb3174d358fded1909dface262ae9d9feada1683 Mon Sep 17 00:00:00 2001 From: Massimo Magnano Date: Fri, 15 Sep 2023 13:37:00 +0200 Subject: [PATCH 18/34] CropArea Rotate and Flip --- bgraimagemanipulation.pas | 182 +++++++++- .../unitbgraimagemanipulationdemo.lfm | 328 ++++++++++++++++-- .../unitbgraimagemanipulationdemo.pas | 92 +++++ 3 files changed, 575 insertions(+), 27 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index 9bdc292..fd50753 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -72,6 +72,7 @@ - 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 ============================================================================ } @@ -185,14 +186,14 @@ TCropArea = class(TObject) procedure Render_Refresh; - { #note 2 -oMaxM : all the Code that use Resolution may be under if BGRABitmapVersion > 11050400 } procedure GetImageResolution(var resX, resY:Single; var resUnit:TResolutionUnit); procedure CalculateScaledAreaFromArea; procedure CalculateAreaFromScaledArea; function GetPixelArea(const AValue: TRectF):TRect; - property ScaledArea :TRect read rScaledArea write setScaledArea; + procedure CheckOutOfBounds(var AArea:TRect); + property ScaledArea :TRect read rScaledArea write setScaledArea; public Rotate :double; UserData :Integer; @@ -213,6 +214,14 @@ TCropArea = class(TObject) procedure BringForward; procedure BringBackward; + //Rotate/Flip + procedure RotateLeft; + procedure RotateRight; + procedure FlipHLeft; + procedure FlipHRight; + procedure FlipVUp; + procedure FlipVDown; + property Area:TRectF read rArea write setArea; property AreaUnit:TResolutionUnit read rAreaUnit write setAreaUnit; property Top:Single read getTop write setTop; @@ -254,6 +263,14 @@ TCropAreaList = class(TObjectList) procedure SaveToStream(Stream: TStream); procedure SaveToFile(const FileName: 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; @@ -837,6 +854,41 @@ function TCropArea.GetPixelArea(const AValue: TRectF): TRect; end; end; +procedure TCropArea.CheckOutOfBounds(var AArea:TRect); +var + tmpValue: Integer; + +begin + //Out of Bounds check + if (AArea.Left<0) + then begin + tmpValue :=-AArea.Left; + AArea.Left :=0; + AArea.Right:=AArea.Right+tmpValue; + end; + + if (AArea.Top<0) + then begin + tmpValue :=-AArea.Top; + AArea.Top :=0; + AArea.Bottom:=AArea.Bottom+tmpValue; + 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 + 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 + end; +end; + procedure TCropArea.CopyAspectFromParent; begin rAspectX :=fOwner.fAspectX; @@ -1260,6 +1312,84 @@ procedure TCropArea.BringBackward; 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; + CheckOutOfBounds(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; + CheckOutOfBounds(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; + CheckOutOfBounds(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; + CheckOutOfBounds(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; + CheckOutOfBounds(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; + CheckOutOfBounds(newArea); + ScaledArea :=newArea; +end; + { TCropAreaList } procedure TCropAreaList.setLoading(AValue: Boolean); @@ -1478,6 +1608,54 @@ procedure TCropAreaList.SaveToFile(const FileName: string); 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; diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm index 50422a5..f09a90d 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm @@ -1,10 +1,10 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Left = 261 - Height = 480 + Height = 513 Top = 125 Width = 898 Caption = 'Demonstration of TBGRAImageManipulation' - ClientHeight = 480 + ClientHeight = 513 ClientWidth = 898 OnCloseQuery = FormCloseQuery OnCreate = FormCreate @@ -12,7 +12,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo LCLVersion = '3.99.0.0' object Background: TBCPanel Left = 650 - Height = 480 + Height = 513 Top = 0 Width = 248 Align = alRight @@ -1605,7 +1605,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo end object BGRAImageManipulation: TBGRAImageManipulation Left = 170 - Height = 480 + Height = 513 Top = 0 Width = 480 Align = alClient @@ -1627,7 +1627,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo end object BCPanelCropAreas: TBCPanel Left = 0 - Height = 480 + Height = 513 Top = 0 Width = 170 Align = alLeft @@ -1714,7 +1714,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo object BCPanelCropAreaLoad: TBCPanel Left = 1 Height = 106 - Top = 373 + Top = 406 Width = 168 Align = alBottom Background.Color = clBtnFace @@ -2198,7 +2198,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo end object BCPanelCropArea: TBCPanel Left = 0 - Height = 320 + Height = 360 Top = 48 Width = 170 Background.Color = clBtnFace @@ -2459,17 +2459,17 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo TabOrder = 1 end object edAspectPersonal: TEdit - Left = 47 + Left = 45 Height = 23 - Top = 298 + Top = 330 Width = 87 AutoSize = False TabOrder = 2 end object rgAspect: TRadioGroup - Left = 22 - Height = 76 - Top = 216 + Left = 20 + Height = 68 + Top = 256 Width = 137 AutoFill = True Caption = 'Aspect Ratio' @@ -2480,7 +2480,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo ChildSizing.ShrinkVertical = crsScaleChilds ChildSizing.Layout = cclLeftToRightThenTopToBottom ChildSizing.ControlsPerLine = 1 - ClientHeight = 56 + ClientHeight = 48 ClientWidth = 133 Items.Strings = ( 'Parent' @@ -2491,9 +2491,9 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo TabOrder = 3 end object btApplyAspectRatio: TSpeedButton - Left = 135 + Left = 133 Height = 22 - Top = 299 + Top = 331 Width = 23 Glyph.Data = { C6070000424DC607000000000000360000002800000016000000160000000100 @@ -2623,17 +2623,17 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Value = 50 end object Label1: TLabel - Left = 8 + Left = 7 Height = 15 - Top = 176 + Top = 224 Width = 46 Caption = 'Z Order :' end object btZFront: TSpeedButton - Left = 58 + Left = 57 Height = 22 Hint = 'To Front' - Top = 176 + Top = 224 Width = 23 Glyph.Data = { 36040000424D3604000000000000360000002800000010000000100000000100 @@ -2674,10 +2674,10 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo OnClick = btZFrontClick end object btZBack: TSpeedButton - Left = 82 + Left = 81 Height = 22 Hint = 'To Back' - Top = 176 + Top = 224 Width = 23 Glyph.Data = { 36040000424D3604000000000000360000002800000010000000100000000100 @@ -2718,10 +2718,10 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo OnClick = btZBackClick end object btZDown: TSpeedButton - Left = 135 + Left = 134 Height = 22 Hint = 'Down' - Top = 176 + Top = 224 Width = 23 Glyph.Data = { 36040000424D3604000000000000360000002800000010000000100000000100 @@ -2762,10 +2762,10 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo OnClick = btZDownClick end object btZUp: TSpeedButton - Left = 111 + Left = 110 Height = 22 Hint = 'Up' - Top = 176 + Top = 224 Width = 23 Glyph.Data = { 36040000424D3604000000000000360000002800000010000000100000000100 @@ -2805,6 +2805,284 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo } OnClick = btZUpClick end + object Label2: TLabel + Left = 14 + Height = 15 + Top = 176 + Width = 40 + Caption = 'Rotate :' + end + object btCRotateLeft: TSpeedButton + Left = 58 + Height = 22 + Hint = 'Rotate Left' + Top = 176 + Width = 23 + Glyph.Data = { + 36040000424D3604000000000000360000002800000010000000100000000100 + 2000000000000004000000000000000000000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF0000820015068E039300000001FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000480 + 003A1E9A1AF22CA529FF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000480026F1F9D + 1EFB07AD07FE18AB16FF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00008000010C8209A71B9719FC01A4 + 01FF00BA00FF15B413FF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00007100140F7D0DD4118210FB008F00FF00A5 + 00FF00BB00FF11B00FFF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF000561003610700EED096809FB007500FF008A00FF009D + 00FF00AE00FF0DA40CFF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00035E00550E5C0DF8034703FE015B01FF026F02FF007F00FF0090 + 00FF009D00FF0A9609FF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00005900521F671FF9397639FE337E33FF2F872FFF2D922DFF2799 + 27FF239E23FF1C931CFF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF0000520032196618EB468746FC479447FF439943FF3E9D + 3EFF3A9F3AFF248D24FF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00004400120F5C0FCB4F934FFC5BA45BFF57A5 + 57FF52A552FF2D892DFF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0000800001075307994F8E4FFC70B3 + 70FF6BB16BFF3A853AFF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00004E00604381 + 43F983C083FE458545FF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000048 + 0032327032ED4A844AFF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF0000420012004E008700000001FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00 + } + OnClick = btCRotateLeftClick + end + object btCRotateRight: TSpeedButton + Left = 82 + Height = 22 + Hint = 'Rotate Right' + Top = 176 + Width = 23 + Glyph.Data = { + 36040000424D3604000000000000360000002800000010000000100000000100 + 2000000000000004000000000000000000000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF0006890351078A0366FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00159611BE2EA92CFF118F0CAC00800001FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF0013920FBB0BBC0BFF17B116FB159112D800770015FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF000F8D0CB809D509FF00C500FF0CB00CFC169214F00575 + 0038FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF000E880BB508D008FF00C400FF00B000FF059B05FE027E + 00F90272006BFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF000C8209B106BB06FF00B400FF00A600FF009400FF007F + 00FF127A11FB086F06A200800001FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00077C06AF04A604FF00A100FF009700FF018801FF0378 + 03FF006300FF0A5C0AFB086608CC00000001FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00057404AC2AA52AFF25A325FF2AA02AFF2E992EFF3190 + 31FF368836FF327A32FC0D630CCB00000001FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00036C02A93EA43EFF3CA23CFF40A240FF459E45FF499A + 49FF327F32FB0559059900800001FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00006200A655A855FF55A955FF59A959FF5CA75CFE2D78 + 2DF900500060FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00005700A66DB36DFF6DB46DFF6BAF6BFD236B23EB0049 + 0031FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00004E00A685C185FF70AC70FD195F19CD00440012FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00004E00A67BAD7BFE0D570D9B00800001FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00004F004A0852085CFFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00 + } + OnClick = btCRotateRightClick + end + object Label3: TLabel + Left = 28 + Height = 15 + Top = 200 + Width = 25 + Caption = 'Flip :' + end + object btCFlipVUp: TSpeedButton + Left = 58 + Height = 22 + Hint = 'Flip Vertical Up' + Top = 200 + Width = 23 + Glyph.Data = { + 36040000424D3604000000000000360000002800000010000000100000000100 + 2000000000000004000000000000000000000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000000 + 0001005000090050000900500009005000090050000900500009005000090050 + 00090050000900500009005000090050000900000001FFFFFF00FFFFFF00004D + 00854A844AFF458545FF3A853AFF2D892DFF258D25FF1C931CFF0A9609FF0DA4 + 0CFF11B00FFF15B413FF18AB16FF29A427FF068E039BFFFFFF00FFFFFF000042 + 0012327032ED83C083FE6BB16BFF52A552FF3A9F3AFF259F25FF009D00FF00AE + 00FF00BB00FF00BA00FF05AC05FE1A9616F300870019FFFFFF00FFFFFF00FFFF + FF0000480032418041F970B370FF57A557FF3E9D3EFF2A9A2AFF009000FF009D + 00FF00A500FF00A400FF038E00FB04830044FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00004E00614D8C4DFC5BA45BFF439943FF2D932DFF028002FF008A + 00FF008F00FF179515FC067F0277FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00055205984E924EFC479447FF2F872FFF067106FF0075 + 00FF0E810DFB0C8109AEFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00008000010D5A0DCB468946FC347F34FF055D05FF0767 + 07FB0D7D0BD800800001FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF0000420012166215EB387638FE044804FE0E71 + 0CF0006E0018FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00005100341E631DF91B661AFA0465 + 003EFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0000580053337F3064FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00 + } + OnClick = btCFlipVUpClick + end + object btCFlipVDown: TSpeedButton + Left = 82 + Height = 22 + Hint = 'Flip Vertical Down' + Top = 200 + Width = 23 + Glyph.Data = { + 36040000424D3604000000000000360000002800000010000000100000000100 + 2000000000000004000000000000000000000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000058005303610058FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00005100341E6A1DF90C620BFA0465 + 003EFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF0000420012166715EB388238FE045D04FE0E76 + 0CF0006E0018FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00008000010D5D0DCB469246FC348C34FF057005FF077A + 07FB0D7F0BD800800001FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00055205984E974EFC479C47FF2F942FFF068206FF008A + 00FF0E920DFB0C8109AEFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00004E00614D904DFC5BA95BFF43A043FF2D9D2DFF029102FF009D + 00FF00A500FF17A015FC067F0277FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF0000480032418141F970B670FF57A957FF3EA33EFF2AA22AFF009D00FF00AE + 00FF00BA00FF00BA00FF1BA118FB04830044FFFFFF00FFFFFF00FFFFFF000042 + 0012327032ED83C183FE6BB26BFF52A752FF3AA23AFF25A425FF00A300FF00B8 + 00FF00CC00FF00D000FF05B805FE1A9816F300870019FFFFFF00FFFFFF00004D + 00854A844AFF458545FF3A853AFF2D882DFF228B22FF178F17FF019000FF019E + 00FF02A900FF02AB00FF18AA16FF28A325FF038D009AFFFFFF00FFFFFF000000 + 0001005000090050000900500009005000090050000900500009005000090050 + 00090050000900500009005000090050000900000001FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00 + } + OnClick = btCFlipVDownClick + end + object btCFlipHLeft: TSpeedButton + Left = 111 + Height = 22 + Hint = 'Flip Horizzontal Left' + Top = 200 + Width = 23 + Glyph.Data = { + 36040000424D3604000000000000360000002800000010000000100000000100 + 2000000000000004000000000000000000000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF0000820015068E039300000001FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000480 + 003A1E9A1AF22CA529FF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000480026F1F9D + 1EFB07AD07FE18AB16FF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00008000010C8209A71B9719FC01A4 + 01FF00BA00FF15B413FF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00007100140F7D0DD4118210FB008F00FF00A5 + 00FF00BB00FF11B00FFF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF000561003610700EED096809FB007500FF008A00FF009D + 00FF00AE00FF0DA40CFF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00035E00550E5C0DF8034703FE015B01FF026F02FF007F00FF0090 + 00FF009D00FF0A9609FF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00005900521F671FF9397639FE337E33FF2F872FFF2D922DFF2799 + 27FF239E23FF1C931CFF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF0000520032196618EB468746FC479447FF439943FF3E9D + 3EFF3A9F3AFF248D24FF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00004400120F5C0FCB4F934FFC5BA45BFF57A5 + 57FF52A552FF2D892DFF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0000800001075307994F8E4FFC70B3 + 70FF6BB16BFF3A853AFF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00004E00604381 + 43F983C083FE458545FF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000048 + 0032327032ED4A844AFF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF0000420012004E008700000001FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00 + } + OnClick = btCFlipHLeftClick + end + object btCFlipHRight: TSpeedButton + Left = 134 + Height = 22 + Hint = 'Flip Horizzontal Right' + Top = 200 + Width = 23 + Glyph.Data = { + 36040000424D3604000000000000360000002800000010000000100000000100 + 2000000000000004000000000000000000000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF0006890351078A0366FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00159611BE2EA92CFF118F0CAC00800001FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF0013920FBB0BBC0BFF17B116FB159112D800770015FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF000F8D0CB809D509FF00C500FF0CB00CFC169214F00575 + 0038FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF000E880BB508D008FF00C400FF00B000FF059B05FE027E + 00F90272006BFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF000C8209B106BB06FF00B400FF00A600FF009400FF007F + 00FF127A11FB086F06A200800001FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00077C06AF04A604FF00A100FF009700FF018801FF0378 + 03FF006300FF0A5C0AFB086608CC00000001FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00057404AC2AA52AFF25A325FF2AA02AFF2E992EFF3190 + 31FF368836FF327A32FC0D630CCB00000001FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00036C02A93EA43EFF3CA23CFF40A240FF459E45FF499A + 49FF327F32FB0559059900800001FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00006200A655A855FF55A955FF59A959FF5CA75CFE2D78 + 2DF900500060FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00005700A66DB36DFF6DB46DFF6BAF6BFD236B23EB0049 + 0031FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00004E00A685C185FF70AC70FD195F19CD00440012FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00004E00A67BAD7BFE0D570D9B00800001FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00004F004A0852085CFFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00 + } + OnClick = btCFlipHRightClick + end end end object OpenPictureDialog: TOpenPictureDialog diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas index a1a27ac..f98928f 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas @@ -76,6 +76,10 @@ TFormBGRAImageManipulationDemo = class(TForm) btApplyAspectRatio: TSpeedButton; btBox_Add: TBGRASpeedButton; btBox_Del: TBGRASpeedButton; + btCFlipHLeft: TSpeedButton; + btCFlipHRight: TSpeedButton; + btCFlipVUp: TSpeedButton; + btCFlipVDown: TSpeedButton; btnEmptyImage: TBCButton; btnLoadCropList: TBCButton; btnSaveCropList: TBCButton; @@ -88,6 +92,8 @@ TFormBGRAImageManipulationDemo = class(TForm) btnSetAspectRatio: TBCButton; btnRotateLeft: TBCButton; btnRotateRight: TBCButton; + btCRotateRight: TSpeedButton; + btCRotateLeft: TSpeedButton; cbBoxList: TComboBox; chkFullSize: TCheckBox; cbSaveFormat: TComboBox; @@ -101,6 +107,8 @@ TFormBGRAImageManipulationDemo = class(TForm) edWidth: TFloatSpinEdit; KeepAspectRatio: TCheckBox; Label1: TLabel; + Label2: TLabel; + Label3: TLabel; lbResolution: TLabel; lbAspectRatio: TLabel; lbOptions: TLabel; @@ -118,6 +126,12 @@ TFormBGRAImageManipulationDemo = class(TForm) btZBack: TSpeedButton; btZDown: TSpeedButton; btZUp: TSpeedButton; + procedure btCFlipHLeftClick(Sender: TObject); + procedure btCFlipHRightClick(Sender: TObject); + procedure btCFlipVDownClick(Sender: TObject); + procedure btCFlipVUpClick(Sender: TObject); + procedure btCRotateLeftClick(Sender: TObject); + procedure btCRotateRightClick(Sender: TObject); procedure btnEmptyImageClick(Sender: TObject); procedure btnGetAspectRatioFromImageClick(Sender: TObject); procedure btnLoadCropListClick(Sender: TObject); @@ -250,6 +264,84 @@ procedure TFormBGRAImageManipulationDemo.btnEmptyImageClick(Sender: TObject); end; end; +procedure TFormBGRAImageManipulationDemo.btCRotateLeftClick(Sender: TObject); +var + CropArea :TCropArea; + +begin + if (cbBoxList.ItemIndex>-1) then + begin + CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); + if CropArea<>nil + then CropArea.RotateLeft; + end; +end; + +procedure TFormBGRAImageManipulationDemo.btCFlipVDownClick(Sender: TObject); +var + CropArea :TCropArea; + +begin + if (cbBoxList.ItemIndex>-1) then + begin + CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); + if CropArea<>nil + then CropArea.FlipVDown; + end; +end; + +procedure TFormBGRAImageManipulationDemo.btCFlipHLeftClick(Sender: TObject); +var + CropArea :TCropArea; + +begin + if (cbBoxList.ItemIndex>-1) then + begin + CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); + if CropArea<>nil + then CropArea.FlipHLeft; + end; +end; + +procedure TFormBGRAImageManipulationDemo.btCFlipHRightClick(Sender: TObject); +var + CropArea :TCropArea; + +begin + if (cbBoxList.ItemIndex>-1) then + begin + CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); + if CropArea<>nil + then CropArea.FlipHRight; + end; +end; + +procedure TFormBGRAImageManipulationDemo.btCFlipVUpClick(Sender: TObject); +var + CropArea :TCropArea; + +begin + if (cbBoxList.ItemIndex>-1) then + begin + CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); + if CropArea<>nil + then CropArea.FlipVUp; + end; +end; + +procedure TFormBGRAImageManipulationDemo.btCRotateRightClick(Sender: TObject); +var + CropArea :TCropArea; + +begin + if (cbBoxList.ItemIndex>-1) then + begin + CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); + if CropArea<>nil + then CropArea.RotateRight; + end; +end; + procedure TFormBGRAImageManipulationDemo.btnLoadCropListClick(Sender: TObject); begin try From 51d6034f578a5506bc4b4676018fdc5d77134257 Mon Sep 17 00:00:00 2001 From: Massimo Magnano Date: Wed, 20 Sep 2023 17:29:13 +0200 Subject: [PATCH 19/34] ImageManipulation Demo use only OnChange; GetCurrentCropArea; corrected Enabled --- .../unitbgraimagemanipulationdemo.lfm | 5 +- .../unitbgraimagemanipulationdemo.pas | 259 ++++++------------ 2 files changed, 91 insertions(+), 173 deletions(-) diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm index f09a90d..d53bdd3 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm @@ -2222,6 +2222,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo BevelOuter = bvNone BevelWidth = 1 Border.Style = bboNone + Enabled = False FontEx.Color = clDefault FontEx.FontQuality = fqSystemClearType FontEx.Shadow = False @@ -2572,7 +2573,6 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Font.Name = 'Arial' MaxValue = 100 OnChange = edLeftChange - OnEditingDone = edLeftEditingDone ParentFont = False TabOrder = 4 Value = 50 @@ -2587,7 +2587,6 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Font.Name = 'Arial' MaxValue = 100 OnChange = edTopChange - OnEditingDone = edTopEditingDone ParentFont = False TabOrder = 5 Value = 50 @@ -2602,7 +2601,6 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Font.Name = 'Arial' MaxValue = 100 OnChange = edWidthChange - OnEditingDone = edWidthEditingDone ParentFont = False TabOrder = 6 Value = 50 @@ -2617,7 +2615,6 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Font.Name = 'Arial' MaxValue = 100 OnChange = edHeightChange - OnEditingDone = edHeightEditingDone ParentFont = False TabOrder = 7 Value = 50 diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas index f98928f..500e9f9 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas @@ -150,12 +150,8 @@ TFormBGRAImageManipulationDemo = class(TForm) const Path: String): Integer; procedure CropAreaSave(AOwner: TBGRAImageManipulation; CropArea: TCropArea; const XMLConf: TXMLConfig; const Path: String); - procedure edHeightEditingDone(Sender: TObject); - procedure edLeftEditingDone(Sender: TObject); procedure edNameChange(Sender: TObject); - procedure edTopEditingDone(Sender: TObject); procedure edUnit_TypeChange(Sender: TObject); - procedure edWidthEditingDone(Sender: TObject); procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); procedure KeepAspectRatioClick(Sender: TObject); @@ -178,8 +174,10 @@ TFormBGRAImageManipulationDemo = class(TForm) private { private declarations } lastNewBoxNum :Word; - changingAspect, closing:Boolean; + changingAspect, closing, + inFillBoxUI :Boolean; + function GetCurrentCropArea: TCropArea; procedure FillBoxUI(ABox :TCropArea); procedure SaveCallBack(Bitmap :TBGRABitmap; CropArea: TCropArea); procedure UpdateBoxList; @@ -269,12 +267,9 @@ procedure TFormBGRAImageManipulationDemo.btCRotateLeftClick(Sender: TObject); CropArea :TCropArea; begin - if (cbBoxList.ItemIndex>-1) then - begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - if CropArea<>nil - then CropArea.RotateLeft; - end; + CropArea :=GetCurrentCropArea; + if CropArea<>nil + then CropArea.RotateLeft; end; procedure TFormBGRAImageManipulationDemo.btCFlipVDownClick(Sender: TObject); @@ -282,12 +277,9 @@ procedure TFormBGRAImageManipulationDemo.btCFlipVDownClick(Sender: TObject); CropArea :TCropArea; begin - if (cbBoxList.ItemIndex>-1) then - begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - if CropArea<>nil - then CropArea.FlipVDown; - end; + CropArea :=GetCurrentCropArea; + if CropArea<>nil + then CropArea.FlipVDown; end; procedure TFormBGRAImageManipulationDemo.btCFlipHLeftClick(Sender: TObject); @@ -295,12 +287,9 @@ procedure TFormBGRAImageManipulationDemo.btCFlipHLeftClick(Sender: TObject); CropArea :TCropArea; begin - if (cbBoxList.ItemIndex>-1) then - begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - if CropArea<>nil - then CropArea.FlipHLeft; - end; + CropArea :=GetCurrentCropArea; + if CropArea<>nil + then CropArea.FlipHLeft; end; procedure TFormBGRAImageManipulationDemo.btCFlipHRightClick(Sender: TObject); @@ -308,12 +297,9 @@ procedure TFormBGRAImageManipulationDemo.btCFlipHRightClick(Sender: TObject); CropArea :TCropArea; begin - if (cbBoxList.ItemIndex>-1) then - begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - if CropArea<>nil - then CropArea.FlipHRight; - end; + CropArea :=GetCurrentCropArea; + if CropArea<>nil + then CropArea.FlipHRight; end; procedure TFormBGRAImageManipulationDemo.btCFlipVUpClick(Sender: TObject); @@ -321,12 +307,9 @@ procedure TFormBGRAImageManipulationDemo.btCFlipVUpClick(Sender: TObject); CropArea :TCropArea; begin - if (cbBoxList.ItemIndex>-1) then - begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - if CropArea<>nil - then CropArea.FlipVUp; - end; + CropArea :=GetCurrentCropArea; + if CropArea<>nil + then CropArea.FlipVUp; end; procedure TFormBGRAImageManipulationDemo.btCRotateRightClick(Sender: TObject); @@ -334,12 +317,9 @@ procedure TFormBGRAImageManipulationDemo.btCRotateRightClick(Sender: TObject); CropArea :TCropArea; begin - if (cbBoxList.ItemIndex>-1) then - begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - if CropArea<>nil - then CropArea.RotateRight; - end; + CropArea :=GetCurrentCropArea; + if CropArea<>nil + then CropArea.RotateRight; end; procedure TFormBGRAImageManipulationDemo.btnLoadCropListClick(Sender: TObject); @@ -441,15 +421,12 @@ procedure TFormBGRAImageManipulationDemo.btZBackClick(Sender: TObject); CropArea :TCropArea; begin - if (cbBoxList.ItemIndex>-1) then - begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - if CropArea<>nil - then begin - CropArea.BringToBack; - UpdateBoxList; - end; - end; + CropArea :=GetCurrentCropArea; + if CropArea<>nil + then begin + CropArea.BringToBack; + UpdateBoxList; + end; end; procedure TFormBGRAImageManipulationDemo.btZDownClick(Sender: TObject); @@ -457,15 +434,12 @@ procedure TFormBGRAImageManipulationDemo.btZDownClick(Sender: TObject); CropArea :TCropArea; begin - if (cbBoxList.ItemIndex>-1) then - begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - if CropArea<>nil - then begin - CropArea.BringBackward; - UpdateBoxList; - end; - end; + CropArea :=GetCurrentCropArea; + if CropArea<>nil + then begin + CropArea.BringBackward; + UpdateBoxList; + end; end; procedure TFormBGRAImageManipulationDemo.btZFrontClick(Sender: TObject); @@ -473,15 +447,12 @@ procedure TFormBGRAImageManipulationDemo.btZFrontClick(Sender: TObject); CropArea :TCropArea; begin - if (cbBoxList.ItemIndex>-1) then - begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - if CropArea<>nil - then begin - CropArea.BringToFront; - UpdateBoxList; - end; - end; + CropArea :=GetCurrentCropArea; + if CropArea<>nil + then begin + CropArea.BringToFront; + UpdateBoxList; + end; end; procedure TFormBGRAImageManipulationDemo.btZUpClick(Sender: TObject); @@ -489,15 +460,12 @@ procedure TFormBGRAImageManipulationDemo.btZUpClick(Sender: TObject); CropArea :TCropArea; begin - if (cbBoxList.ItemIndex>-1) then - begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - if CropArea<>nil - then begin - CropArea.BringForward; - UpdateBoxList; - end; - end; + CropArea :=GetCurrentCropArea; + if CropArea<>nil + then begin + CropArea.BringForward; + UpdateBoxList; + end; end; function TFormBGRAImageManipulationDemo.CropAreaLoad(AOwner: TBGRAImageManipulation; CropArea: TCropArea; @@ -512,52 +480,14 @@ procedure TFormBGRAImageManipulationDemo.CropAreaSave(AOwner: TBGRAImageManipula // end; -procedure TFormBGRAImageManipulationDemo.edHeightEditingDone(Sender: TObject); -var - CropArea :TCropArea; - -begin - if (cbBoxList.ItemIndex>-1) then - begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - if CropArea<>nil - then CropArea.Height :=edHeight.Value; - end; -end; - -procedure TFormBGRAImageManipulationDemo.edLeftEditingDone(Sender: TObject); -var - CropArea :TCropArea; - -begin - if (cbBoxList.ItemIndex>-1) then - begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - if CropArea<>nil - then CropArea.Left :=edLeft.Value; - end; -end; - procedure TFormBGRAImageManipulationDemo.edNameChange(Sender: TObject); var CropArea :TCropArea; begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - CropArea.Name :=edName.Text; -end; - -procedure TFormBGRAImageManipulationDemo.edTopEditingDone(Sender: TObject); -var - CropArea :TCropArea; - -begin - if (cbBoxList.ItemIndex>-1) then - begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - if CropArea<>nil - then CropArea.Top :=edTop.Value; - end; + CropArea :=GetCurrentCropArea; + if CropArea<>nil + then CropArea.Name :=edName.Text; end; procedure TFormBGRAImageManipulationDemo.edUnit_TypeChange(Sender: TObject); @@ -565,27 +495,11 @@ procedure TFormBGRAImageManipulationDemo.edUnit_TypeChange(Sender: TObject); CropArea :TCropArea; begin - if (cbBoxList.ItemIndex>-1) then - begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - if CropArea<>nil then - begin - CropArea.AreaUnit:=TResolutionUnit(edUnit_Type.ItemIndex); - FillBoxUI(CropArea); - end; - end; -end; - -procedure TFormBGRAImageManipulationDemo.edWidthEditingDone(Sender: TObject); -var - CropArea :TCropArea; - -begin - if (cbBoxList.ItemIndex>-1) then + CropArea :=GetCurrentCropArea; + if CropArea<>nil then begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - if CropArea<>nil - then CropArea.Width :=edWidth.Value; + CropArea.AreaUnit:=TResolutionUnit(edUnit_Type.ItemIndex); + FillBoxUI(CropArea); end; end; @@ -621,13 +535,13 @@ procedure TFormBGRAImageManipulationDemo.btBox_DelClick(Sender: TObject); begin curIndex :=cbBoxList.ItemIndex; - CropArea :=TCropArea(cbBoxList.Items.Objects[curIndex]); - BGRAImageManipulation.delCropArea(CropArea); - cbBoxList.Items.Delete(curIndex); -(* if (BGRAImageManipulation.SelectedCropArea <> nil) - then cbBoxList.ItemIndex:=BGRAImageManipulation.SelectedCropArea.Index - else cbBoxList.ItemIndex:=-1; - FillBoxUI(BGRAImageManipulation.SelectedCropArea);*) + if (curIndex>-1) then + begin + CropArea :=TCropArea(cbBoxList.Items.Objects[curIndex]); + BGRAImageManipulation.delCropArea(CropArea); + cbBoxList.ItemIndex:=cbBoxList.Items.IndexOfObject(BGRAImageManipulation.SelectedCropArea); + end; + FillBoxUI(BGRAImageManipulation.SelectedCropArea); end; procedure TFormBGRAImageManipulationDemo.cbBoxListChange(Sender: TObject); @@ -640,12 +554,11 @@ procedure TFormBGRAImageManipulationDemo.edHeightChange(Sender: TObject; AByUser CropArea :TCropArea; begin - if AByUser and (cbBoxList.ItemIndex>-1) then - begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - if (CropArea<>nil) - then CropArea.Height:=edHeight.Value; - end; + if inFillBoxUI then exit; + + CropArea :=GetCurrentCropArea; + if (CropArea<>nil) + then CropArea.Height:=edHeight.Value; end; procedure TFormBGRAImageManipulationDemo.edLeftChange(Sender: TObject; AByUser: boolean); @@ -653,12 +566,11 @@ procedure TFormBGRAImageManipulationDemo.edLeftChange(Sender: TObject; AByUser: CropArea :TCropArea; begin - if AByUser and (cbBoxList.ItemIndex>-1) then - begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - if (CropArea<>nil) - then CropArea.Left :=edLeft.Value; - end; + if inFillBoxUI then exit; + + CropArea :=GetCurrentCropArea; + if (CropArea<>nil) + then CropArea.Left :=edLeft.Value; end; procedure TFormBGRAImageManipulationDemo.edTopChange(Sender: TObject; AByUser: boolean); @@ -666,12 +578,11 @@ procedure TFormBGRAImageManipulationDemo.edTopChange(Sender: TObject; AByUser: b CropArea :TCropArea; begin - if AByUser and (cbBoxList.ItemIndex>-1) then - begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - if (CropArea<>nil) - then CropArea.Top :=edTop.Value; - end; + if inFillBoxUI then exit; + + CropArea :=GetCurrentCropArea; + if (CropArea<>nil) + then CropArea.Top :=edTop.Value; end; procedure TFormBGRAImageManipulationDemo.edWidthChange(Sender: TObject; AByUser: boolean); @@ -679,12 +590,11 @@ procedure TFormBGRAImageManipulationDemo.edWidthChange(Sender: TObject; AByUser: CropArea :TCropArea; begin - if AByUser and (cbBoxList.ItemIndex>-1) then - begin - CropArea :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); - if (CropArea<>nil) - then CropArea.Width:=edWidth.Value; - end; + if inFillBoxUI then exit; + + CropArea :=GetCurrentCropArea; + if (CropArea<>nil) + then CropArea.Width:=edWidth.Value; end; procedure TFormBGRAImageManipulationDemo.FormCreate(Sender: TObject); @@ -695,6 +605,7 @@ procedure TFormBGRAImageManipulationDemo.FormCreate(Sender: TObject); begin closing :=False; changingAspect :=False; + inFillBoxUI :=False; lastNewBoxNum :=0; TStringList(cbBoxList.Items).OwnsObjects:=False; j:=0; @@ -764,6 +675,7 @@ procedure TFormBGRAImageManipulationDemo.DeletedCrop(AOwner: TBGRAImageManipulat delIndex :=cbBoxList.Items.IndexOfObject(CropArea); if (delIndex<>-1) then cbBoxList.Items.Delete(delIndex); + BCPanelCropArea.Enabled:=(cbBoxList.Items.Count>0); end; except end; @@ -799,10 +711,18 @@ procedure TFormBGRAImageManipulationDemo.SpeedButton1Click(Sender: TObject); BGRAImageManipulation.tests; end; +function TFormBGRAImageManipulationDemo.GetCurrentCropArea: TCropArea; +begin + if (cbBoxList.ItemIndex<0) + then Result :=nil + else Result :=TCropArea(cbBoxList.Items.Objects[cbBoxList.ItemIndex]); +end; + procedure TFormBGRAImageManipulationDemo.FillBoxUI(ABox: TCropArea); begin if (ABox<>nil) then begin + inFillBoxUI :=True; BCPanelCropArea.Enabled :=True; edName.Text :=ABox.Name; edUnit_Type.ItemIndex :=Integer(ABox.AreaUnit); @@ -839,6 +759,7 @@ procedure TFormBGRAImageManipulationDemo.FillBoxUI(ABox: TCropArea); end; edAspectPersonal.Text:=ABox.AspectRatio; changingAspect:=False; + inFillBoxUI :=False; end else BCPanelCropArea.Enabled :=False; end; From e659cbabe37830cd7c125d7e8497f024d7a2fe2b Mon Sep 17 00:00:00 2001 From: Johann ELSASS Date: Thu, 21 Sep 2023 09:37:20 +0200 Subject: [PATCH 20/34] partial invalidate of virtual screen --- bgravirtualscreen.pas | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/bgravirtualscreen.pas b/bgravirtualscreen.pas index 480b796..50c2e20 100644 --- a/bgravirtualscreen.pas +++ b/bgravirtualscreen.pas @@ -61,11 +61,13 @@ TCustomBGRAVirtualScreen = class(TBGRACustomPanel) public { Public declarations } constructor Create(TheOwner: TComponent); override; + function BitmapRectToClient(ARect: TRect): TRect; procedure RedrawBitmap; overload; procedure RedrawBitmap(ARect: TRect); overload; procedure RedrawBitmap(ARectArray: array of TRect); overload; procedure DiscardBitmap; overload; procedure DiscardBitmap(ARect: TRect); overload; + procedure InvalidateBitmap(ARect: TRect); destructor Destroy; override; public property OnRedraw: TBGRARedrawEvent Read FOnRedraw Write FOnRedraw; @@ -379,6 +381,15 @@ constructor TCustomBGRAVirtualScreen.Create(TheOwner: TComponent); Color := clWhite; end; +function TCustomBGRAVirtualScreen.BitmapRectToClient(ARect: TRect): TRect; +var + scale: Double; +begin + scale := BitmapScale; + result := rect(floor(ARect.Left/scale), floor(ARect.Top/scale), + ceil(ARect.Right/scale), ceil(ARect.Bottom/scale)); +end; + procedure TCustomBGRAVirtualScreen.RedrawBitmap; begin RedrawBitmapContent; @@ -389,7 +400,6 @@ procedure TCustomBGRAVirtualScreen.RedrawBitmap; procedure TCustomBGRAVirtualScreen.RedrawBitmap(ARect: TRect); var All, displayRect: TRect; - scale: Double; begin if Assigned(FBGRA) then begin @@ -413,9 +423,7 @@ procedure TCustomBGRAVirtualScreen.RedrawBitmap(ARect: TRect); FBGRA.ClipRect := ARect; RedrawBitmapContent; FBGRA.NoClip; - scale := BitmapScale; - displayRect := rect(round(ARect.Left/scale), round(ARect.Top/scale), - round(ARect.Right/scale), round(ARect.Bottom/scale)); + displayRect := BitmapRectToClient(ARect); {$IFDEF LINUX} FBGRA.DrawPart(ARect, Canvas, displayRect, True); {$ELSE} @@ -533,7 +541,6 @@ procedure TCustomBGRAVirtualScreen.DiscardBitmap; procedure TCustomBGRAVirtualScreen.DiscardBitmap(ARect: TRect); var - scale: Double; displayRect: TRect; begin ARect.Intersect(rect(0,0,FBGRA.Width,FBGRA.Height)); @@ -544,13 +551,19 @@ procedure TCustomBGRAVirtualScreen.DiscardBitmap(ARect: TRect); FDiscardedRect := ARect else FDiscardedRect.Union(ARect); - scale := BitmapScale; - displayRect := rect(round(ARect.Left/scale), round(ARect.Top/scale), - round(ARect.Right/scale), round(ARect.Bottom/scale)); + displayRect := BitmapRectToClient(ARect); InvalidateRect(self.Handle, @displayRect, false); end; end; +procedure TCustomBGRAVirtualScreen.InvalidateBitmap(ARect: TRect); +var + displayRect: TRect; +begin + displayRect := BitmapRectToClient(ARect); + InvalidateRect(self.Handle, @displayRect, false); +end; + destructor TCustomBGRAVirtualScreen.Destroy; begin FBGRA.Free; From 5b6522ab4050e17b0ca2b0a8b52cb9e1d93ea812 Mon Sep 17 00:00:00 2001 From: Massimo Magnano Date: Thu, 21 Sep 2023 12:36:00 +0200 Subject: [PATCH 21/34] Crop SetLeft/Top/Width/Height use Area property; CheckOutOfBounds as function Crop SetLeft/Top/Width/Height use Area property; CheckScaledOutOfBounds as function; new CheckAreaOutOfBounds function (unused) that maybe revisited --- bgraimagemanipulation.pas | 153 +++++++++++++++++++++----------------- 1 file changed, 85 insertions(+), 68 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index fd50753..e3460bf 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -191,7 +191,8 @@ TCropArea = class(TObject) procedure CalculateAreaFromScaledArea; function GetPixelArea(const AValue: TRectF):TRect; - procedure CheckOutOfBounds(var AArea:TRect); + function CheckScaledOutOfBounds(var AArea:TRect):Boolean; + function CheckAreaOutOfBounds(var AArea:TRectF):Boolean; property ScaledArea :TRect read rScaledArea write setScaledArea; public @@ -563,21 +564,16 @@ function TCropArea.getTop: Single; procedure TCropArea.setTop(AValue: Single); var - tempHeight :Single; + tempArea:TRectF; begin if AValue=rArea.Top then Exit; - tempHeight :=rArea.Height; - rArea.Top:=AValue; - rArea.Height:=tempHeight; - - CalculateScaledAreaFromArea; - - Render_Refresh; - - if assigned(fOwner.rOnCropAreaChanged) - then fOwner.rOnCropAreaChanged(fOwner, Self); + tempArea :=rArea; + tempArea.Top:=AValue; + tempArea.Height:=rArea.Height; + //CheckAreaOutOfBounds(tempArea); + Area :=tempArea; end; function TCropArea.getLeft: Single; @@ -587,21 +583,24 @@ function TCropArea.getLeft: Single; procedure TCropArea.setLeft(AValue: Single); var - tempWidth :Single; + tempArea:TRectF; + tempSArea:TRect; begin if AValue=rArea.Left then Exit; - tempWidth :=rArea.Width; - rArea.Left:=AValue; - rArea.Width:=tempWidth; - - CalculateScaledAreaFromArea; - - Render_Refresh; + 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); + if assigned(fOwner.rOnCropAreaChanged) + then fOwner.rOnCropAreaChanged(fOwner, Self); + end; *) end; function TCropArea.getHeight: Single; @@ -611,29 +610,15 @@ function TCropArea.getHeight: Single; procedure TCropArea.setHeight(AValue: Single); var - curKeepAspectRatio :Boolean; - curRatio :TRatio; + tempArea:TRectF; begin if AValue=rArea.Height then Exit; - curKeepAspectRatio :=getRealAspectRatio(curRatio); - - if curKeepAspectRatio - then begin - //The Height is Changed recalculate the Width - rArea.Width :=abs(AValue) * (curRatio.Horizontal / curRatio.Vertical); - { #todo -oMaxM : should you check the maximum Width? } - end; - - rArea.Height:=AValue; - - CalculateScaledAreaFromArea; - - Render_Refresh; - - if assigned(fOwner.rOnCropAreaChanged) - then fOwner.rOnCropAreaChanged(fOwner, Self); + tempArea :=rArea; + tempArea.Height:=AValue; + //CheckAreaOutOfBounds(tempArea); + Area :=tempArea; end; function TCropArea.getWidth: Single; @@ -643,29 +628,15 @@ function TCropArea.getWidth: Single; procedure TCropArea.setWidth(AValue: Single); var - curKeepAspectRatio :Boolean; - curRatio :TRatio; + tempArea:TRectF; begin if AValue=rArea.Width then Exit; - curKeepAspectRatio :=getRealAspectRatio(curRatio); - - if curKeepAspectRatio - then begin - //The Width is Changed recalculate the Height - rArea.Height :=abs(AValue) * (curRatio.Vertical / curRatio.Horizontal); - { #todo -oMaxM : should you check the maximum Height? } - end; - - rArea.Width:=AValue; - - CalculateScaledAreaFromArea; - - Render_Refresh; - - if assigned(fOwner.rOnCropAreaChanged) - then fOwner.rOnCropAreaChanged(fOwner, Self); + tempArea :=rArea; + tempArea.Width:=AValue; + //CheckAreaOutOfBounds(tempArea); + Area :=tempArea; end; function TCropArea.getMaxHeight: Single; @@ -854,17 +825,19 @@ function TCropArea.GetPixelArea(const AValue: TRectF): TRect; end; end; -procedure TCropArea.CheckOutOfBounds(var AArea:TRect); +function TCropArea.CheckScaledOutOfBounds(var AArea: TRect): Boolean; var tmpValue: Integer; begin - //Out of Bounds check + 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) @@ -872,6 +845,7 @@ procedure TCropArea.CheckOutOfBounds(var AArea:TRect); tmpValue :=-AArea.Top; AArea.Top :=0; AArea.Bottom:=AArea.Bottom+tmpValue; + Result :=True; end; if (AArea.Right>fOwner.fResampledBitmap.Width) @@ -879,6 +853,7 @@ procedure TCropArea.CheckOutOfBounds(var AArea:TRect); 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) @@ -886,6 +861,48 @@ procedure TCropArea.CheckOutOfBounds(var AArea:TRect); 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; @@ -1321,7 +1338,7 @@ procedure TCropArea.RotateLeft; newArea.Bottom:=rScaledArea.Bottom; newArea.Left:=newArea.Right-rScaledArea.Height; newArea.Top:=newArea.Bottom-rScaledArea.Width; - CheckOutOfBounds(newArea); + CheckScaledOutOfBounds(newArea); ScaledArea :=newArea; end; @@ -1334,7 +1351,7 @@ procedure TCropArea.RotateRight; newArea.Bottom:=rScaledArea.Bottom; newArea.Right:=newArea.Left+rScaledArea.Height; newArea.Top:=newArea.Bottom-rScaledArea.Width; - CheckOutOfBounds(newArea); + CheckScaledOutOfBounds(newArea); ScaledArea :=newArea; end; @@ -1347,7 +1364,7 @@ procedure TCropArea.FlipHLeft; newArea.Bottom:=rScaledArea.Bottom; newArea.Right :=rScaledArea.Left; newArea.Left:=newArea.Right-rScaledArea.Width; - CheckOutOfBounds(newArea); + CheckScaledOutOfBounds(newArea); ScaledArea :=newArea; end; @@ -1360,7 +1377,7 @@ procedure TCropArea.FlipHRight; newArea.Bottom:=rScaledArea.Bottom; newArea.Left :=rScaledArea.Right; newArea.Right:=newArea.Left+rScaledArea.Width; - CheckOutOfBounds(newArea); + CheckScaledOutOfBounds(newArea); ScaledArea :=newArea; end; @@ -1373,7 +1390,7 @@ procedure TCropArea.FlipVUp; newArea.Right:=rScaledArea.Right; newArea.Bottom :=rScaledArea.Top; newArea.Top:=newArea.Bottom-rScaledArea.Height; - CheckOutOfBounds(newArea); + CheckScaledOutOfBounds(newArea); ScaledArea :=newArea; end; @@ -1386,7 +1403,7 @@ procedure TCropArea.FlipVDown; newArea.Right:=rScaledArea.Right; newArea.Top :=rScaledArea.Bottom; newArea.Bottom:=newArea.Top+rScaledArea.Height; - CheckOutOfBounds(newArea); + CheckScaledOutOfBounds(newArea); ScaledArea :=newArea; end; From 2373d83a48d87cb31e9fd90d62e540cb3bdca2e1 Mon Sep 17 00:00:00 2001 From: Massimo Magnano Date: Fri, 22 Sep 2023 12:47:14 +0200 Subject: [PATCH 22/34] CropArea Duplicate and SetSize; Duplicate on Demo --- bgraimagemanipulation.pas | 44 +++- .../unitbgraimagemanipulationdemo.lfm | 235 ++++++++++++------ .../unitbgraimagemanipulationdemo.pas | 82 +++++- 3 files changed, 275 insertions(+), 86 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index e3460bf..c35a35f 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -73,6 +73,7 @@ - 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 ============================================================================ } @@ -206,7 +207,9 @@ TCropArea = class(TObject) constructor Create(AOwner: TBGRAImageManipulation; AArea: TRectF; AAreaUnit: TResolutionUnit = ruNone; //Pixels ARotate: double = 0; - AUserData: Integer = 0); + AUserData: Integer = 0); overload; + constructor Create(AOwner: TBGRAImageManipulation; + DuplicateFrom: TCropArea; InsertInList:Boolean); overload; destructor Destroy; override; //ZOrder @@ -223,6 +226,8 @@ TCropArea = class(TObject) 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; @@ -1264,7 +1269,7 @@ constructor TCropArea.Create(AOwner: TBGRAImageManipulation; AArea: TRectF; begin inherited Create; if (AOwner = Nil) - then raise Exception.Create('Owner TBGRAImageManipulation is Nil'); + then raise Exception.Create('TCropArea Owner is Nil'); OwnerList :=nil; fOwner :=AOwner; rAreaUnit :=AAreaUnit; @@ -1278,6 +1283,26 @@ constructor TCropArea.Create(AOwner: TBGRAImageManipulation; AArea: TRectF; 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.Rotate, 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; @@ -1407,6 +1432,21 @@ procedure TCropArea.FlipVDown; 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); diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm index d53bdd3..77adb91 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm @@ -1,17 +1,17 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo - Left = 261 + Left = 262 Height = 513 Top = 125 - Width = 898 + Width = 926 Caption = 'Demonstration of TBGRAImageManipulation' ClientHeight = 513 - ClientWidth = 898 + ClientWidth = 926 OnCloseQuery = FormCloseQuery OnCreate = FormCreate ShowHint = True LCLVersion = '3.99.0.0' object Background: TBCPanel - Left = 650 + Left = 678 Height = 513 Top = 0 Width = 248 @@ -1604,7 +1604,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo end end object BGRAImageManipulation: TBGRAImageManipulation - Left = 170 + Left = 198 Height = 513 Top = 0 Width = 480 @@ -1629,7 +1629,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Left = 0 Height = 513 Top = 0 - Width = 170 + Width = 198 Align = alLeft Background.Color = clSilver Background.ColorOpacity = 35 @@ -1687,7 +1687,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo ParentShowHint = False end object btBox_Del: TBGRASpeedButton - Left = 147 + Left = 149 Height = 22 Hint = 'Remove this Box' Top = 21 @@ -1715,7 +1715,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Left = 1 Height = 106 Top = 406 - Width = 168 + Width = 196 Align = alBottom Background.Color = clBtnFace Background.Gradient1.StartColor = clWhite @@ -2200,7 +2200,7 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Left = 0 Height = 360 Top = 48 - Width = 170 + Width = 186 Background.Color = clBtnFace Background.Gradient1.StartColor = clWhite Background.Gradient1.EndColor = clBlack @@ -2817,39 +2817,39 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo Width = 23 Glyph.Data = { 36040000424D3604000000000000360000002800000010000000100000000100 - 2000000000000004000000000000000000000000000000000000FFFFFF00FFFF - FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF0000820015068E039300000001FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000480 - 003A1E9A1AF22CA529FF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000480026F1F9D - 1EFB07AD07FE18AB16FF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00008000010C8209A71B9719FC01A4 - 01FF00BA00FF15B413FF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00FFFFFF00007100140F7D0DD4118210FB008F00FF00A5 - 00FF00BB00FF11B00FFF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF000561003610700EED096809FB007500FF008A00FF009D - 00FF00AE00FF0DA40CFF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00035E00550E5C0DF8034703FE015B01FF026F02FF007F00FF0090 - 00FF009D00FF0A9609FF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00005900521F671FF9397639FE337E33FF2F872FFF2D922DFF2799 - 27FF239E23FF1C931CFF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF0000520032196618EB468746FC479447FF439943FF3E9D - 3EFF3A9F3AFF248D24FF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00FFFFFF00004400120F5C0FCB4F934FFC5BA45BFF57A5 - 57FF52A552FF2D892DFF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0000800001075307994F8E4FFC70B3 - 70FF6BB16BFF3A853AFF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00004E00604381 - 43F983C083FE458545FF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000048 - 0032327032ED4A844AFF00500009FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF0000420012004E008700000001FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00 + 2000000000000004000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000002ECE + 560114BE27021DC4360200000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000000000002AC84F0123C5 + 43031CC1340A1DC3360E20B73A0F068C0C020000000000000000000000000000 + 0000000000000000000000000000000000000000000025C6460223C441041EC2 + 380817BF2C1610BB1E1C0ABA13221CA531250000000000000000000000000000 + 00000000000010BB1E0116BE29020000000024C443011FB73B051FBC380925C8 + 421011BC21260BB9152F04B608370CAD1343066F0B6000620012000000000000 + 00000000000009B812010EBB1B0821B73E0524AC4301006000080064003B026B + 03680F921C6505B60A4500B4005000B4005A0A8A1095007C006F000000000000 + 00000000000010B7200115BA290523AA43040000000000000000000000000081 + 0015018601DB0CAE176B00B4006C00B40079099D11A4008C00CC007C0068007F + 00CC008300DC008600EA008A00F8008C00FF008C00FF008C00ED008C002D0000 + 0000008D019F06970BB801B4018D03B5039C0AAF0FB0008C00F9008C01F01DB2 + 32F91FB633FD25B836FD24BA36FE20BC32FE21BD39FA008C00EA008C002A0000 + 0000008D019F08990DCB0BB50BB216B916C419B41DD7008C00F9018D01F422BD + 27FF24BD26FF22BC23FF11B812FF0FAF17FF018D03F4008C004E00000000008C + 001A018C01F40DAD12DD1DBB1EEC24BD26F81BA620FD018C01CF018D01F436C3 + 3EFF33C438FF2EC133FF05B505FF10A119FD018C02F4018C01B8018C02AD018B + 02F60A970EFD17BB18FF33C438FF33C438FF169919FE008C0093018D02F446CA + 50FF43CB4DFF43CB4DFF37C740FF12B914FF0DB514FF11A418FE099F10FE0AB0 + 0FFF20BE23FF43CB4CFF43CB4DFF3FC248FF028D02F5008C0033018D02F457D1 + 65FF58D266FF56D466FF55D465FF54D364FF54D25EFF34C83CFF31C63AFF49CF + 55FF56D466FF55D465FF58D667FF109814F8008C008300000000018D02F44CC6 + 5CFF028F04F90F9A14FB58D069FF67DF7FFF67DF7FFF67DF7FFF67DF7FFF67DF + 7FFF67DF7FFF5ED772FF119C17F8018D01C6008C000500000000028E03DB028E + 02F4008C004E038E0566039005F72BAE35FF4EC65DFF6EDB85FF6EDB86FF48C4 + 58FF29AE32FF039004F5018D0176008C00020000000000000000008C0019008C + 001B0000000000000000008C001D028E037B0B950FC109920AF0069108EE0491 + 07BD018D0180008C002500000000000000000000000000000000 } OnClick = btCRotateLeftClick end @@ -2864,36 +2864,36 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo 2000000000000004000000000000000000000000000000000000FFFFFF00FFFF FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF0006890351078A0366FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00159611BE2EA92CFF118F0CAC00800001FFFFFF00FFFF - FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF0013920FBB0BBC0BFF17B116FB159112D800770015FFFF - FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF000F8D0CB809D509FF00C500FF0CB00CFC169214F00575 - 0038FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF000E880BB508D008FF00C400FF00B000FF059B05FE027E - 00F90272006BFFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF000C8209B106BB06FF00B400FF00A600FF009400FF007F - 00FF127A11FB086F06A200800001FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00077C06AF04A604FF00A100FF009700FF018801FF0378 - 03FF006300FF0A5C0AFB086608CC00000001FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00057404AC2AA52AFF25A325FF2AA02AFF2E992EFF3190 - 31FF368836FF327A32FC0D630CCB00000001FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00036C02A93EA43EFF3CA23CFF40A240FF459E45FF499A - 49FF327F32FB0559059900800001FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00006200A655A855FF55A955FF59A959FF5CA75CFE2D78 - 2DF900500060FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00005700A66DB36DFF6DB46DFF6BAF6BFD236B23EB0049 - 0031FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00004E00A685C185FF70AC70FD195F19CD00440012FFFF - FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00004E00A67BAD7BFE0D570D9B00800001FFFFFF00FFFF - FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00004F004A0852085CFFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF - FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00 + FF00FFFFFF0000BF00001DC4360214BE27022ECE5601FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00068C0C0220B73A0F1DC3360E1CC1340A23C543032AC84F01FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000099 + 00001CA531250ABA132210BB1E1C17BF2C161EC2380823C4410425C64602FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0000620012066F + 0B600CAD134304B608370BB9152F11BC212625C842101FBC38091FB73B0524C4 + 4301FFFFFF0016BE290210BB1E01FFFFFF00FFFFFF00FFFFFF00007C006F0A8A + 109500B4005A00B4005005B60A450F921C65026B03680064003B0060000824AC + 430121B73E050EBB1B0809B81201FFFFFF00FFFFFF00FFFFFF00008C00CC099D + 11A400B4007900B4006C0CAE176B018601DB00810015FFFFFF00FFFFFF00FFFF + FF0023AA430415BA290510B72001FFFFFF00FFFFFF00FFFFFF00008C00F90AAF + 0FB003B5039C01B4018D06970BB8008D019FFFFFFF00008C002D008C00ED008C + 00FF008C00FF008A00F8008600EA008300DC007F00CC007C0068008C00F919B4 + 1DD716B916C40BB50BB208990DCB008D019FFFFFFF00008C002A008C00EA21BD + 39FA20BC32FE24BA36FE25B836FD1FB633FD1DB232F9008C01F0018C01CF1BA6 + 20FD24BD26F81DBB1EEC0DAD12DD018C01F4008C001AFFFFFF00008C004E018D + 03F40FAF17FF11B812FF22BC23FF24BD26FF22BD27FF018D01F4008C00931699 + 19FE33C438FF33C438FF17BB18FF0A970EFD018B02F6018C02AD018C01B8018C + 02F410A119FD05B505FF2EC133FF33C438FF36C33EFF018D01F4008C0033028D + 02F53FC248FF43CB4DFF43CB4CFF20BE23FF0AB00FFF099F10FE11A418FE0DB5 + 14FF12B914FF37C740FF43CB4DFF43CB4DFF46CA50FF018D02F4FFFFFF00008C + 0083109814F858D667FF55D465FF56D466FF49CF55FF31C63AFF34C83CFF54D2 + 5EFF54D364FF55D465FF56D466FF58D266FF57D165FF018D02F4FFFFFF00008C + 0005018D01C6119C17F85ED772FF67DF7FFF67DF7FFF67DF7FFF67DF7FFF67DF + 7FFF67DF7FFF58D069FF0F9A14FB028F04F94CC65CFF018D02F4FFFFFF00FFFF + FF00008C0002018D0176039004F529AE32FF48C458FF6EDB86FF6EDB85FF4EC6 + 5DFF2BAE35FF039005F7038E0566008C004E028E02F4028E03DBFFFFFF00FFFF + FF00FFFFFF00FFFFFF00008C0025018D0180049107BD069108EE09920AF00B95 + 0FC1028E037B008C001DFFFFFF00FFFFFF00008C001B008C0019 } OnClick = btCRotateRightClick end @@ -3080,6 +3080,97 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo } OnClick = btCFlipHRightClick end + object btCropDuplicateOp: TSpeedButton + Left = 161 + Height = 22 + Hint = 'Duplicate when Rotate/Flip' + Top = 192 + Width = 23 + AllowAllUp = True + Flat = True + Glyph.Data = { + 36040000424D3604000000000000360000002800000010000000100000000100 + 2000000000000004000000000000000000000000000000000000000000000000 + 0000000000000000000000000012000000570000005B0000005B0000005B0000 + 005B0000005A0000002800000002000000000000000000000000000000000000 + 0000000000000000000000000046FBFBFBFEFFFFFFFFFFFFFFFFFFFFFFFFFDFC + FAFFEBEAE6FF797A79CE00000047000000040000000000000000000000000000 + 0000000000000000000000000048FFFFFFFFF9F9F9FFFBFBFBFFFCFCFCFFFEFE + FEFFEAEBEAFFF3F3F3FF727373D00000004E0000000200000000000000140000 + 005D00000062000000620000008EFFFFFFFFF8F8F8FFFAFAFAFFFCFCFCFFFDFD + FDFFE7E8E7FFFEFEFEFFF3F3F3FF6C6D6CCB00000034000000000000004BFBFB + FBFEFFFFFFFFFFFFFFFFB7B7B7FFFFFFFFFFF7F7F7FFFAFAFAFFFBFBFBFFFDFD + FDFFE4E4E4FFFEFEFEFFFEFEFEFFF3F4F4FF6F6F6FB9000000140000004DFFFF + FFFFFAFAFAFFFCFCFCFFB5B5B5FFFFFFFFFFF6F6F5FFF9F9F9FFFBFBFBFFFCFC + FCFFF8F8F8FFE4E4E4FFE9EAEAFFF3F3F3FFF4F3F1FE000000460000004DFFFF + FFFFFAFAFAFFFBFBFBFFB5B5B5FFFFFFFFFFF4F4F4FFF7F7F7FFFAFAFAFFFBFB + FBFFFCFCFCFFFCFCFCFFFDFDFDFFFDFDFDFFFFFFFFFF0000004A0000004DFFFF + FFFFF9F9F9FFFBFBFBFFB5B5B5FFFFFFFFFFF2F2F2FFF5F5F5FFF8F8F8FFFAFA + FAFFFBFBFBFFFBFBFBFFFCFCFCFFFCFCFCFFFFFFFFFF0000004A0000004DFFFF + FFFFF7F7F7FFFAFAFAFFB4B4B4FFFFFFFFFFF0F0F0FFF3F3F3FFF6F6F5FFF8F8 + F8FFF9F9F9FFFAFAFAFFFAFAFAFFFAFAFAFFFFFFFFFF0000004A0000004DFFFF + FFFFF5F5F5FFF8F8F8FFB3B3B3FFFFFFFFFFEEEEEEFFF1F1F0FFF3F3F3FFF5F5 + F5FFF6F6F6FFF8F8F7FFF8F8F8FFF8F8F8FFFFFFFFFF0000004A0000004DFFFF + FFFFF3F3F3FFF5F5F5FFB2B2B1FFFFFFFFFFECECEBFFEEEEEEFFF0F0F0FFF2F2 + F2FFF4F4F3FFF5F5F4FFF5F5F5FFF5F5F5FFFFFFFFFF0000004A0000004DFFFF + FFFFF0F0F0FFF3F3F2FFB1B1B1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000480000004DFFFF + FFFFEEEEEDFFF0F0EFFFE2E2E2FFB0B0B0FFB0B0B0FFB1B1B0FFB1B1B1FFB1B1 + B1FFB8B8B8FF00000080000000470000004700000046000000100000004DFFFF + FFFFEBEBEAFFEDEDECFFEEEEEEFFF0F0EFFFF1F1F0FFF2F2F1FFF2F2F2FFF2F2 + F2FFFFFFFFFF0000004E000000000000000000000000000000000000004CFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFF0000004D00000000000000000000000000000000000000120000 + 004B0000004C0000004C0000004C0000004C0000004C0000004C0000004C0000 + 004C0000004B0000001200000000000000000000000000000000 + } + GroupIndex = 1 + end + end + object btCropDuplicate: TSpeedButton + Left = 172 + Height = 22 + Hint = 'Duplicate this Area' + Top = 20 + Width = 23 + Flat = True + Glyph.Data = { + 36040000424D3604000000000000360000002800000010000000100000000100 + 2000000000000004000000000000000000000000000000000000000000000000 + 0000000000000000000000000012000000570000005B0000005B0000005B0000 + 005B0000005A0000002800000002000000000000000000000000000000000000 + 0000000000000000000000000046FBFBFBFEFFFFFFFFFFFFFFFFFFFFFFFFFDFC + FAFFEBEAE6FF797A79CE00000047000000040000000000000000000000000000 + 0000000000000000000000000048FFFFFFFFF9F9F9FFFBFBFBFFFCFCFCFFFEFE + FEFFEAEBEAFFF3F3F3FF727373D00000004E0000000200000000000000140000 + 005D00000062000000620000008EFFFFFFFFF8F8F8FFFAFAFAFFFCFCFCFFFDFD + FDFFE7E8E7FFFEFEFEFFF3F3F3FF6C6D6CCB00000034000000000000004BFBFB + FBFEFFFFFFFFFFFFFFFFB7B7B7FFFFFFFFFFF7F7F7FFFAFAFAFFFBFBFBFFFDFD + FDFFE4E4E4FFFEFEFEFFFEFEFEFFF3F4F4FF6F6F6FB9000000140000004DFFFF + FFFFFAFAFAFFFCFCFCFFB5B5B5FFFFFFFFFFF6F6F5FFF9F9F9FFFBFBFBFFFCFC + FCFFF8F8F8FFE4E4E4FFE9EAEAFFF3F3F3FFF4F3F1FE000000460000004DFFFF + FFFFFAFAFAFFFBFBFBFFB5B5B5FFFFFFFFFFF4F4F4FFF7F7F7FFFAFAFAFFFBFB + FBFFFCFCFCFFFCFCFCFFFDFDFDFFFDFDFDFFFFFFFFFF0000004A0000004DFFFF + FFFFF9F9F9FFFBFBFBFFB5B5B5FFFFFFFFFFF2F2F2FFF5F5F5FFF8F8F8FFFAFA + FAFFFBFBFBFFFBFBFBFFFCFCFCFFFCFCFCFFFFFFFFFF0000004A0000004DFFFF + FFFFF7F7F7FFFAFAFAFFB4B4B4FFFFFFFFFFF0F0F0FFF3F3F3FFF6F6F5FFF8F8 + F8FFF9F9F9FFFAFAFAFFFAFAFAFFFAFAFAFFFFFFFFFF0000004A0000004DFFFF + FFFFF5F5F5FFF8F8F8FFB3B3B3FFFFFFFFFFEEEEEEFFF1F1F0FFF3F3F3FFF5F5 + F5FFF6F6F6FFF8F8F7FFF8F8F8FFF8F8F8FFFFFFFFFF0000004A0000004DFFFF + FFFFF3F3F3FFF5F5F5FFB2B2B1FFFFFFFFFFECECEBFFEEEEEEFFF0F0F0FFF2F2 + F2FFF4F4F3FFF5F5F4FFF5F5F5FFF5F5F5FFFFFFFFFF0000004A0000004DFFFF + FFFFF0F0F0FFF3F3F2FFB1B1B1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000480000004DFFFF + FFFFEEEEEDFFF0F0EFFFE2E2E2FFB0B0B0FFB0B0B0FFB1B1B0FFB1B1B1FFB1B1 + B1FFB8B8B8FF00000080000000470000004700000046000000100000004DFFFF + FFFFEBEBEAFFEDEDECFFEEEEEEFFF0F0EFFFF1F1F0FFF2F2F1FFF2F2F2FFF2F2 + F2FFFFFFFFFF0000004E000000000000000000000000000000000000004CFFFF + FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + FFFFFFFFFFFF0000004D00000000000000000000000000000000000000120000 + 004B0000004C0000004C0000004C0000004C0000004C0000004C0000004C0000 + 004C0000004B0000001200000000000000000000000000000000 + } + OnClick = btCropDuplicateClick end end object OpenPictureDialog: TOpenPictureDialog diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas index 500e9f9..a9c8f96 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas @@ -80,6 +80,7 @@ TFormBGRAImageManipulationDemo = class(TForm) btCFlipHRight: TSpeedButton; btCFlipVUp: TSpeedButton; btCFlipVDown: TSpeedButton; + btCropDuplicate: TSpeedButton; btnEmptyImage: TBCButton; btnLoadCropList: TBCButton; btnSaveCropList: TBCButton; @@ -126,10 +127,12 @@ TFormBGRAImageManipulationDemo = class(TForm) btZBack: TSpeedButton; btZDown: TSpeedButton; btZUp: TSpeedButton; + btCropDuplicateOp: TSpeedButton; procedure btCFlipHLeftClick(Sender: TObject); procedure btCFlipHRightClick(Sender: TObject); procedure btCFlipVDownClick(Sender: TObject); procedure btCFlipVUpClick(Sender: TObject); + procedure btCropDuplicateClick(Sender: TObject); procedure btCRotateLeftClick(Sender: TObject); procedure btCRotateRightClick(Sender: TObject); procedure btnEmptyImageClick(Sender: TObject); @@ -268,8 +271,15 @@ procedure TFormBGRAImageManipulationDemo.btCRotateLeftClick(Sender: TObject); begin CropArea :=GetCurrentCropArea; - if CropArea<>nil - then CropArea.RotateLeft; + if CropArea<>nil then + begin + if btCropDuplicateOp.Down then + begin + CropArea :=TCropArea.Create(BGRAImageManipulation, CropArea, True); + BGRAImageManipulation.SelectedCropArea :=CropArea; + end; + CropArea.RotateLeft; + end; end; procedure TFormBGRAImageManipulationDemo.btCFlipVDownClick(Sender: TObject); @@ -278,8 +288,15 @@ procedure TFormBGRAImageManipulationDemo.btCFlipVDownClick(Sender: TObject); begin CropArea :=GetCurrentCropArea; - if CropArea<>nil - then CropArea.FlipVDown; + if CropArea<>nil then + begin + if btCropDuplicateOp.Down then + begin + CropArea :=TCropArea.Create(BGRAImageManipulation, CropArea, True); + BGRAImageManipulation.SelectedCropArea :=CropArea; + end; + CropArea.FlipVDown; + end; end; procedure TFormBGRAImageManipulationDemo.btCFlipHLeftClick(Sender: TObject); @@ -288,8 +305,15 @@ procedure TFormBGRAImageManipulationDemo.btCFlipHLeftClick(Sender: TObject); begin CropArea :=GetCurrentCropArea; - if CropArea<>nil - then CropArea.FlipHLeft; + if CropArea<>nil then + begin + if btCropDuplicateOp.Down then + begin + CropArea :=TCropArea.Create(BGRAImageManipulation, CropArea, True); + BGRAImageManipulation.SelectedCropArea :=CropArea; + end; + CropArea.FlipHLeft; + end; end; procedure TFormBGRAImageManipulationDemo.btCFlipHRightClick(Sender: TObject); @@ -298,8 +322,15 @@ procedure TFormBGRAImageManipulationDemo.btCFlipHRightClick(Sender: TObject); begin CropArea :=GetCurrentCropArea; - if CropArea<>nil - then CropArea.FlipHRight; + if CropArea<>nil then + begin + if btCropDuplicateOp.Down then + begin + CropArea :=TCropArea.Create(BGRAImageManipulation, CropArea, True); + BGRAImageManipulation.SelectedCropArea :=CropArea; + end; + CropArea.FlipHRight; + end; end; procedure TFormBGRAImageManipulationDemo.btCFlipVUpClick(Sender: TObject); @@ -308,8 +339,28 @@ procedure TFormBGRAImageManipulationDemo.btCFlipVUpClick(Sender: TObject); begin CropArea :=GetCurrentCropArea; - if CropArea<>nil - then CropArea.FlipVUp; + if CropArea<>nil then + begin + if btCropDuplicateOp.Down then + begin + CropArea :=TCropArea.Create(BGRAImageManipulation, CropArea, True); + BGRAImageManipulation.SelectedCropArea :=CropArea; + end; + CropArea.FlipVUp; + end; +end; + +procedure TFormBGRAImageManipulationDemo.btCropDuplicateClick(Sender: TObject); +var + newCropArea :TCropArea; + +begin + if BGRAImageManipulation.SelectedCropArea<>nil then + begin + newCropArea :=TCropArea.Create(BGRAImageManipulation, BGRAImageManipulation.SelectedCropArea, True); + BGRAImageManipulation.SelectedCropArea :=newCropArea; + newCropArea.BorderColor :=VGALime; + end; end; procedure TFormBGRAImageManipulationDemo.btCRotateRightClick(Sender: TObject); @@ -318,8 +369,15 @@ procedure TFormBGRAImageManipulationDemo.btCRotateRightClick(Sender: TObject); begin CropArea :=GetCurrentCropArea; - if CropArea<>nil - then CropArea.RotateRight; + if CropArea<>nil then + begin + if btCropDuplicateOp.Down then + begin + CropArea :=TCropArea.Create(BGRAImageManipulation, CropArea, True); + BGRAImageManipulation.SelectedCropArea :=CropArea; + end; + CropArea.RotateRight; + end; end; procedure TFormBGRAImageManipulationDemo.btnLoadCropListClick(Sender: TObject); From 8a512bd0c982df59159cf263bedc97b30b5ebddd Mon Sep 17 00:00:00 2001 From: Leandro Diaz Date: Sun, 24 Sep 2023 10:48:56 -0300 Subject: [PATCH 23/34] Added checklistbox demo --- test/test_checklistbox/project1.ico | Bin 0 -> 133345 bytes test/test_checklistbox/project1.lpi | 80 +++++++++++++++++++ test/test_checklistbox/project1.lpr | 25 ++++++ test/test_checklistbox/unit1.lfm | 30 +++++++ test/test_checklistbox/unit1.pas | 117 ++++++++++++++++++++++++++++ 5 files changed, 252 insertions(+) create mode 100644 test/test_checklistbox/project1.ico create mode 100644 test/test_checklistbox/project1.lpi create mode 100644 test/test_checklistbox/project1.lpr create mode 100644 test/test_checklistbox/unit1.lfm create mode 100644 test/test_checklistbox/unit1.pas diff --git a/test/test_checklistbox/project1.ico b/test/test_checklistbox/project1.ico new file mode 100644 index 0000000000000000000000000000000000000000..10c5fc1a3d8d9ff264229f4ff1cf99ebc83f167f GIT binary patch literal 133345 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
  • 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 literal 0 HcmV?d00001 diff --git a/test/test_checklistbox/project1.lpi b/test/test_checklistbox/project1.lpi new file mode 100644 index 0000000..e4edf54 --- /dev/null +++ b/test/test_checklistbox/project1.lpi @@ -0,0 +1,80 @@ + + + + + + + + + <Scaled Value="True"/> + <ResourceType Value="res"/> + <UseXPManifest Value="True"/> + <XPManifest> + <DpiAware Value="True"/> + </XPManifest> + <Icon Value="0"/> + </General> + <BuildModes> + <Item Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + <UseFileFilters Value="True"/> + </PublishOptions> + <RunParams> + <FormatVersion Value="2"/> + </RunParams> + <RequiredPackages> + <Item> + <PackageName Value="bgracontrols"/> + </Item> + <Item> + <PackageName Value="LCL"/> + </Item> + </RequiredPackages> + <Units> + <Unit> + <Filename Value="project1.lpr"/> + <IsPartOfProject Value="True"/> + </Unit> + <Unit> + <Filename Value="unit1.pas"/> + <IsPartOfProject Value="True"/> + <ComponentName Value="Form1"/> + <ResourceBaseClass Value="Form"/> + <UnitName Value="Unit1"/> + </Unit> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <PathDelim Value="\"/> + <Target> + <Filename Value="project1"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Linking> + <Options> + <Win32> + <GraphicApplication Value="True"/> + </Win32> + </Options> + </Linking> + </CompilerOptions> + <Debugging> + <Exceptions> + <Item> + <Name Value="EAbort"/> + </Item> + <Item> + <Name Value="ECodetoolError"/> + </Item> + <Item> + <Name Value="EFOpenError"/> + </Item> + </Exceptions> + </Debugging> +</CONFIG> diff --git a/test/test_checklistbox/project1.lpr b/test/test_checklistbox/project1.lpr new file mode 100644 index 0000000..ac2bb7a --- /dev/null +++ b/test/test_checklistbox/project1.lpr @@ -0,0 +1,25 @@ +program project1; + +{$mode objfpc}{$H+} + +uses + {$IFDEF UNIX} + cthreads, + {$ENDIF} + {$IFDEF HASAMIGA} + athreads, + {$ENDIF} + Interfaces, // this includes the LCL widgetset + Forms, Unit1 + { you can add units after this }; + +{$R *.res} + +begin + RequireDerivedFormResource:=True; + Application.Scaled:=True; + Application.Initialize; + Application.CreateForm(TForm1, Form1); + Application.Run; +end. + diff --git a/test/test_checklistbox/unit1.lfm b/test/test_checklistbox/unit1.lfm new file mode 100644 index 0000000..f3813e3 --- /dev/null +++ b/test/test_checklistbox/unit1.lfm @@ -0,0 +1,30 @@ +object Form1: TForm1 + Left = 285 + Height = 240 + Top = 31 + Width = 320 + Caption = 'Form1' + ClientHeight = 240 + ClientWidth = 320 + OnCreate = FormCreate + LCLVersion = '2.2.6.0' + object CheckListBox1: TCheckListBox + Left = 48 + Height = 174 + Top = 32 + Width = 198 + ItemHeight = 32 + OnDrawItem = CheckListBox1DrawItem + Style = lbOwnerDrawFixed + TabOrder = 0 + end + object BGRAThemeCheckBox1: TBGRAThemeCheckBox + Left = 48 + Height = 19 + Top = 8 + Width = 165 + Caption = 'BGRAThemeCheckBox1' + Checked = False + TabOrder = 1 + end +end diff --git a/test/test_checklistbox/unit1.pas b/test/test_checklistbox/unit1.pas new file mode 100644 index 0000000..c2e5f8f --- /dev/null +++ b/test/test_checklistbox/unit1.pas @@ -0,0 +1,117 @@ +unit Unit1; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, Forms, Controls, Graphics, Dialogs, CheckLst, Types, + StdCtrls, BGRAThemeCheckBox, BGRABitmap, BGRABitmapTypes, BGRATheme; + +type + + { TForm1 } + + TForm1 = class(TForm) + BGRAThemeCheckBox1: TBGRAThemeCheckBox; + CheckListBox1: TCheckListBox; + procedure CheckListBox1DrawItem(Control: TWinControl; Index: Integer; + ARect: TRect; State: TOwnerDrawState); + procedure FormCreate(Sender: TObject); + private + procedure DrawCheckBox(aCaption: string; State: TBGRAThemeButtonState; + aFocused: boolean; Checked: boolean; ARect: TRect; + ASurface: TBGRAThemeSurface); + + public + + end; + +var + Form1: TForm1; + +implementation + +{$R *.lfm} + +{ TForm1 } + +procedure TForm1.CheckListBox1DrawItem(Control: TWinControl; Index: Integer; + ARect: TRect; State: TOwnerDrawState); +var + surface: TBGRAThemeSurface; + parentForm: TCustomForm; + lclDPI: Integer; +begin + parentForm := GetParentForm(Control, False); + if Assigned(parentForm) then + lclDPI := parentForm.PixelsPerInch + else lclDPI := Screen.PixelsPerInch; + surface := TBGRAThemeSurface.Create(ARect, TCheckListBox(Control).Canvas, 1, lclDPI); + try + DrawCheckBox(TCheckListBox(Control).Items[Index], btbsNormal, False, TCheckListBox(Control).Checked[Index], ARect, surface); + finally + surface.Free; + end; +end; + +procedure TForm1.FormCreate(Sender: TObject); +begin + CheckListBox1.AddItem('Red', nil); + CheckListBox1.AddItem('Green', nil); + CheckListBox1.AddItem('Blue', nil); + CheckListBox1.AddItem('Alpha', nil); +end; + +procedure TForm1.DrawCheckBox(aCaption: string; State: TBGRAThemeButtonState; + aFocused: boolean; Checked: boolean; ARect: TRect; ASurface: TBGRAThemeSurface + ); +var + Style: TTextStyle; + aColor: TBGRAPixel; + aleft, atop, aright, abottom: integer; +begin + with ASurface do + begin + DestCanvas.Font.Color := clBlack; + case State of + btbsHover: aColor := BGRA(0, 120, 215); + btbsActive: aColor := BGRA(0, 84, 153); + btbsDisabled: + begin + DestCanvas.Font.Color := clGray; + aColor := BGRA(204, 204, 204); + end; + else {btbsNormal} + aColor := BGRABlack; + end; + + Bitmap.Fill(BGRAWhite); + BitmapRect := ARect; + Bitmap.Rectangle(0, 0, Bitmap.Height, Bitmap.Height, aColor, BGRAWhite); + aleft := 0; + aright := Bitmap.Height; + atop := 0; + abottom := Bitmap.Height; + if Checked then + Bitmap.DrawPolyLineAntialias(Bitmap.ComputeBezierSpline( + [BezierCurve(pointF(aleft + 2, atop + 3), PointF((aleft + aright - 1) / 2, abottom - 3)), + BezierCurve(PointF((aleft + aright - 1) / 2, abottom - 3), PointF( + (aleft + aright - 1) / 2, (atop * 2 + abottom - 1) / 3), PointF(aright - 2, atop - 2))]), + Color, 1.5); + DrawBitmap; + + if aCaption <> '' then + begin + fillchar(Style, sizeof(Style), 0); + Style.Alignment := taLeftJustify; + Style.Layout := tlCenter; + Style.Wordbreak := True; + DestCanvas.TextRect(ARect, + ARect.Height, 0, aCaption, Style); + end; + end; +end; + +end. + From a90c00154fb3239d8a77a162c8fa4c8a5ebf3eb0 Mon Sep 17 00:00:00 2001 From: Johann ELSASS <circular@operamail.com> Date: Sun, 24 Sep 2023 16:06:54 +0200 Subject: [PATCH 24/34] scale factor, margin --- test/test_checklistbox/unit1.pas | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/test/test_checklistbox/unit1.pas b/test/test_checklistbox/unit1.pas index c2e5f8f..76cfb22 100644 --- a/test/test_checklistbox/unit1.pas +++ b/test/test_checklistbox/unit1.pas @@ -47,7 +47,7 @@ procedure TForm1.CheckListBox1DrawItem(Control: TWinControl; Index: Integer; if Assigned(parentForm) then lclDPI := parentForm.PixelsPerInch else lclDPI := Screen.PixelsPerInch; - surface := TBGRAThemeSurface.Create(ARect, TCheckListBox(Control).Canvas, 1, lclDPI); + surface := TBGRAThemeSurface.Create(ARect, TCheckListBox(Control).Canvas, Control.GetCanvasScaleFactor, lclDPI); try DrawCheckBox(TCheckListBox(Control).Items[Index], btbsNormal, False, TCheckListBox(Control).Checked[Index], ARect, surface); finally @@ -70,6 +70,7 @@ procedure TForm1.DrawCheckBox(aCaption: string; State: TBGRAThemeButtonState; Style: TTextStyle; aColor: TBGRAPixel; aleft, atop, aright, abottom: integer; + penWidth: single; begin with ASurface do begin @@ -88,17 +89,24 @@ procedure TForm1.DrawCheckBox(aCaption: string; State: TBGRAThemeButtonState; Bitmap.Fill(BGRAWhite); BitmapRect := ARect; - Bitmap.Rectangle(0, 0, Bitmap.Height, Bitmap.Height, aColor, BGRAWhite); - aleft := 0; - aright := Bitmap.Height; - atop := 0; - abottom := Bitmap.Height; + penWidth := ASurface.ScaleForBitmap(10) / 10; + aleft := round(penWidth); + aright := Bitmap.Height-round(penWidth); + atop := round(penWidth); + abottom := Bitmap.Height-round(penWidth); + Bitmap.RectangleAntialias(aleft-0.5+penWidth/2, atop-0.5+penWidth/2, + aright-0.5-penWidth/2, abottom-0.5-penWidth/2, + aColor, penWidth); + aleft := round(penWidth*2); + aright := Bitmap.Height-round(penWidth*2); + atop := round(penWidth*2); + abottom := Bitmap.Height-round(penWidth*2); if Checked then Bitmap.DrawPolyLineAntialias(Bitmap.ComputeBezierSpline( [BezierCurve(pointF(aleft + 2, atop + 3), PointF((aleft + aright - 1) / 2, abottom - 3)), BezierCurve(PointF((aleft + aright - 1) / 2, abottom - 3), PointF( - (aleft + aright - 1) / 2, (atop * 2 + abottom - 1) / 3), PointF(aright - 2, atop - 2))]), - Color, 1.5); + (aleft + aright - 1) / 2, (atop * 2 + abottom - 1) / 3), PointF(aright - 2, atop))]), + Color, penWidth*1.5); DrawBitmap; if aCaption <> '' then From 1131543a4e8c5dda1b590d8c24400d4071b50c39 Mon Sep 17 00:00:00 2001 From: Massimo Magnano <maxm.dev@gmail.com> Date: Mon, 25 Sep 2023 13:23:30 +0200 Subject: [PATCH 25/34] NewCropAreaDefault property (to Cm); ResolutionUnitConvert function; SetEmptyImageSizeToCropAreas --- bgraimagemanipulation.pas | 163 +++++++++++++++--- .../unitbgraimagemanipulationdemo.pas | 4 +- 2 files changed, 143 insertions(+), 24 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index c35a35f..25e8650 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -74,6 +74,7 @@ - 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 ============================================================================ } @@ -197,7 +198,7 @@ TCropArea = class(TObject) property ScaledArea :TRect read rScaledArea write setScaledArea; public - Rotate :double; + Rotate :Single; UserData :Integer; BorderColor :TBGRAPixel; @@ -206,7 +207,6 @@ TCropArea = class(TObject) constructor Create(AOwner: TBGRAImageManipulation; AArea: TRectF; AAreaUnit: TResolutionUnit = ruNone; //Pixels - ARotate: double = 0; AUserData: Integer = 0); overload; constructor Create(AOwner: TBGRAImageManipulation; DuplicateFrom: TCropArea; InsertInList:Boolean); overload; @@ -296,19 +296,39 @@ TBGRAEmptyImage = class(TPersistent) function getHeight: Integer; function getWidth: Integer; + 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 rResolutionUnit default ruPixelsPerInch; + property ResolutionUnit: TResolutionUnit read rResolutionUnit write rResolutionUnit 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 (AOwner: TBGRAImageManipulation; CropArea: TCropArea) of object; @@ -335,8 +355,8 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) fStartArea: TRect; fRatio: TRatio; fSizeLimits: TSizeLimits; - fImageBitmap, fResampledBitmap, fBackground, fVirtualScreen: TBGRABitmap; + rNewCropAreaDefault: TBGRANewCropAreaDefault; function getAnchorSize: byte; function getPixelsPerInch: Integer; @@ -402,13 +422,15 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) //Crop Areas Manipulation functions function addCropArea(AArea : TRectF; AAreaUnit: TResolutionUnit = ruNone; - ARotate :double = 0; AUserData: Integer = 0) :TCropArea; - function addScaledCropArea(AArea : TRect; ARotate :double = 0; AUserData: Integer = 0) :TCropArea; + AUserData: Integer = 0) :TCropArea; + function addScaledCropArea(AArea : TRect; AUserData: Integer = 0) :TCropArea; procedure delCropArea(ACropArea :TCropArea); procedure clearCropAreas; procedure getAllResampledBitmaps(ACallBack :TgetAllBitmapsCallback); procedure getAllBitmaps(ACallBack :TgetAllBitmapsCallback); + procedure SetEmptyImageSizeToCropAreas(ReduceLarger: Boolean=False); + property SelectedCropArea :TCropArea read rSelectedCropArea write setSelectedCropArea; property CropAreas :TCropAreaList read rCropAreas; property PixelsPerInch: Integer read getPixelsPerInch; @@ -427,6 +449,7 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) 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; @@ -439,6 +462,10 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) property OnSelectedCropAreaChanged:TCropAreaEvent read rOnSelectedCropAreaChanged write rOnSelectedCropAreaChanged; end; + +function RoundUp(AValue:Single):Integer; +function ResolutionUnitConvert(const AValue:Single; fromRes, toRes:TResolutionUnit; predefInchRes:Integer=96):Single; + {$IFDEF FPC}procedure Register;{$ENDIF} implementation @@ -517,6 +544,41 @@ procedure CheckAspectRatio(const Value :String; var AspectRatioText :String; var 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; + { TCropArea } procedure TCropArea.Render_Refresh; @@ -1265,7 +1327,7 @@ function TCropArea.getBitmap: TBGRABitmap; end; constructor TCropArea.Create(AOwner: TBGRAImageManipulation; AArea: TRectF; - AAreaUnit: TResolutionUnit; ARotate: double; AUserData: Integer); + AAreaUnit: TResolutionUnit; AUserData: Integer); begin inherited Create; if (AOwner = Nil) @@ -1274,7 +1336,6 @@ constructor TCropArea.Create(AOwner: TBGRAImageManipulation; AArea: TRectF; fOwner :=AOwner; rAreaUnit :=AAreaUnit; Area := AArea; - Rotate := ARotate; UserData :=AUserData; rAspectX :=3; rAspectY :=4; @@ -1289,7 +1350,7 @@ constructor TCropArea.Create(AOwner: TBGRAImageManipulation; if (DuplicateFrom = Nil) then raise Exception.Create('TCropArea DuplicateFrom is Nil'); - Create(AOwner, DuplicateFrom.Area, DuplicateFrom.AreaUnit, DuplicateFrom.Rotate, DuplicateFrom.UserData); + Create(AOwner, DuplicateFrom.Area, DuplicateFrom.AreaUnit, DuplicateFrom.UserData); OwnerList :=nil; rAspectX :=DuplicateFrom.rAspectX; @@ -1762,6 +1823,17 @@ constructor TBGRAEmptyImage.Create(AOwner: TBGRAImageManipulation); rResolutionUnit:=ruPixelsPerInch; end; +{ TBGRANewCropAreaDefault } + +constructor TBGRANewCropAreaDefault.Create(AOwner: TBGRAImageManipulation); +begin + inherited Create; + fOwner :=AOwner; + rKeepAspectRatio:=bFalse; + rAspectRatio:='3:4'; + rResolutionUnit:=ruPixelsPerCentimeter; +end; + { TBGRAImageManipulation } { ============================================================================ } @@ -2556,14 +2628,11 @@ procedure TBGRAImageManipulation.CreateResampledBitmap; // 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); + 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; @@ -2643,6 +2712,7 @@ constructor TBGRAImageManipulation.Create(AOwner: TComponent); fVirtualScreen := TBGRABitmap.Create(Width, Height); rEmptyImage :=TBGRAEmptyImage.Create(Self); + rNewCropAreaDefault :=TBGRANewCropAreaDefault.Create(Self); // Initialize crop area rCropAreas :=TCropAreaList.Create(Self); @@ -2660,6 +2730,7 @@ destructor TBGRAImageManipulation.Destroy; fBackground.Free; fVirtualScreen.Free; rEmptyImage.Free; + rNewCropAreaDefault.Free; rCropAreas.Free; inherited Destroy; @@ -2840,7 +2911,6 @@ procedure TBGRAImageManipulation.Render; //Mask.Rectangle(emptyRect, BorderColor, BGRAPixelTransparent); //wich one? end; - { #todo 1 -oMaxM : Test Z Order Draw correctly } for i:=0 to rCropAreas.Count-1 do begin curCropArea :=rCropAreas[i]; @@ -3180,13 +3250,13 @@ procedure TBGRAImageManipulation.tests; end; function TBGRAImageManipulation.addCropArea(AArea: TRectF; AAreaUnit: TResolutionUnit; - ARotate: double; AUserData: Integer): TCropArea; + AUserData: Integer): TCropArea; var newCropArea :TCropArea; begin try - newCropArea :=TCropArea.Create(Self, AArea, AAreaUnit, ARotate, AUserData); + newCropArea :=TCropArea.Create(Self, AArea, AAreaUnit, AUserData); newCropArea.BorderColor :=BGRAWhite; rCropAreas.add(newCropArea); @@ -3207,9 +3277,11 @@ function TBGRAImageManipulation.addCropArea(AArea: TRectF; AAreaUnit: TResolutio Invalidate; end; -function TBGRAImageManipulation.addScaledCropArea(AArea: TRect; ARotate: double; AUserData: Integer): TCropArea; +function TBGRAImageManipulation.addScaledCropArea(AArea: TRect; AUserData: Integer): TCropArea; begin - Result :=Self.addCropArea(RectF(0,0,0,0), ruNone, ARotate, AUserData); + Result :=Self.addCropArea(RectF(0,0,0,0), rNewCropAreaDefault.rResolutionUnit, AUserData); + Result.rAspectRatio:=rNewCropAreaDefault.rAspectRatio; + Result.KeepAspectRatio:=rNewCropAreaDefault.rKeepAspectRatio; Result.ScaledArea :=AArea; if (fMouseCaught) @@ -3289,6 +3361,53 @@ procedure TBGRAImageManipulation.getAllBitmaps(ACallBack: TgetAllBitmapsCallback 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.setBorderSize(const Value: byte); const diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas index a9c8f96..50eda6e 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas @@ -719,7 +719,7 @@ procedure TFormBGRAImageManipulationDemo.AddedCrop(AOwner: TBGRAImageManipulatio cbBoxList.AddItem(CropArea.Name, CropArea); cbBoxList.ItemIndex:=cbBoxList.Items.IndexOfObject(CropArea); - CropArea.AreaUnit:=BGRAImageManipulation.Bitmap.ResolutionUnit; + //CropArea.AreaUnit:=BGRAImageManipulation.Bitmap.ResolutionUnit; FillBoxUI(CropArea); end; @@ -766,7 +766,7 @@ procedure TFormBGRAImageManipulationDemo.SelectedChangedCrop(AOwner: TBGRAImageM procedure TFormBGRAImageManipulationDemo.SpeedButton1Click(Sender: TObject); begin - BGRAImageManipulation.tests; + BGRAImageManipulation.SetEmptyImageSizeToCropAreas(False); end; function TFormBGRAImageManipulationDemo.GetCurrentCropArea: TCropArea; From 19b206449dd856e59e421bcf54c12d9b048b6709 Mon Sep 17 00:00:00 2001 From: Massimo Magnano <maxm.dev@gmail.com> Date: Wed, 27 Sep 2023 17:56:27 +0200 Subject: [PATCH 26/34] Annoying conversion from inch to Cm for Pixels (ResolutionUnitConvert); GetPixelArea corrected --- bgraimagemanipulation.pas | 64 ++++++++++++++------------------------- 1 file changed, 22 insertions(+), 42 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index 25e8650..986ec07 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -464,7 +464,8 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) function RoundUp(AValue:Single):Integer; -function ResolutionUnitConvert(const AValue:Single; fromRes, toRes:TResolutionUnit; predefInchRes:Integer=96):Single; +function ResolutionUnitConvert(const AValue:Single; fromRes, toRes:TResolutionUnit; predefInchRes:Integer=96):Single; overload; +procedure ResolutionUnitConvert(var resX, resY:Single; fromRes, toRes:TResolutionUnit); overload; {$IFDEF FPC}procedure Register;{$ENDIF} @@ -579,6 +580,23 @@ function ResolutionUnitConvert(const AValue:Single; fromRes, toRes:TResolutionUn else Result:=AValue; end; +procedure ResolutionUnitConvert(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; @@ -779,20 +797,7 @@ procedure TCropArea.CalculateScaledAreaFromArea; if (rAreaUnit<>ruNone) then begin GetImageResolution(resX, resY, resUnit); - - //Do Conversion from/to inch/cm - if (rAreaUnit <> resUnit) then - begin - if (rAreaUnit=ruPixelsPerInch) - then begin //Bitmap is in Cm, i'm in Inch - resX :=resX*2.54; - resY :=resY*2.54; - end - else begin //Bitmap is in Inch, i'm in Cm - resX :=resX/2.54; - resY :=resY/2.54; - end; - end; + ResolutionUnitConvert(resX, resY, resUnit, rAreaUnit); end; //MaxM: Use Trunc for Top/Left and Round for Right/Bottom so we @@ -828,20 +833,7 @@ procedure TCropArea.CalculateAreaFromScaledArea; if (rAreaUnit<>ruNone) then begin GetImageResolution(resX, resY, resUnit); - - //Do Conversion from/to inch/cm - if (rAreaUnit <> resUnit) then - begin - if (rAreaUnit=ruPixelsPerInch) - then begin - resX :=resX*2.54; - resY :=resY*2.54; - end - else begin - resX :=resX/2.54; - resY :=resY/2.54; - end - end; + ResolutionUnitConvert(resX, resY, resUnit, rAreaUnit); end; rArea.Left := (rScaledArea.Left / resX) / xRatio; @@ -871,19 +863,7 @@ function TCropArea.GetPixelArea(const AValue: TRectF): TRect; end else GetImageResolution(resX, resY, resUnit); - //Do Conversion from/to inch/cm - if (rAreaUnit <> resUnit) then - begin - if (rAreaUnit=ruPixelsPerInch) - then begin //Bitmap is in Cm - resX :=resX/2.54; - resY :=resY/2.54; - end - else begin //Bitmap is in Inch - resX :=resX*2.54; - resY :=resY*2.54; - end - end; + ResolutionUnitConvert(resX, resY, resUnit, rAreaUnit); Result.Left := Trunc(AValue.Left * resX); Result.Top := Trunc(AValue.Top * resY); From 40c78dfa70c9af507ec10eb4d08afd6f9d6df009 Mon Sep 17 00:00:00 2001 From: Massimo Magnano <maxm.dev@gmail.com> Date: Fri, 29 Sep 2023 13:49:12 +0200 Subject: [PATCH 27/34] CropAreas Load/Save XMLPath parameter; Load/Save UserData --- bgraimagemanipulation.pas | 71 ++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index 986ec07..dfa0b57 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -207,7 +207,7 @@ TCropArea = class(TObject) constructor Create(AOwner: TBGRAImageManipulation; AArea: TRectF; AAreaUnit: TResolutionUnit = ruNone; //Pixels - AUserData: Integer = 0); overload; + AUserData: Integer = -1); overload; constructor Create(AOwner: TBGRAImageManipulation; DuplicateFrom: TCropArea; InsertInList:Boolean); overload; destructor Destroy; override; @@ -262,12 +262,12 @@ TCropAreaList = class(TObjectList) constructor Create(AOwner: TBGRAImageManipulation); function add(aCropArea: TCropArea): integer; - procedure Load(const XMLConf: TXMLConfig); - procedure Save(const XMLConf: TXMLConfig); - procedure LoadFromStream(Stream: TStream); - procedure LoadFromFile(const FileName: string); - procedure SaveToStream(Stream: TStream); - procedure SaveToFile(const FileName: string); + 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; @@ -1543,7 +1543,7 @@ function TCropAreaList.add(aCropArea: TCropArea): integer; Result := inherited Add(aCropArea); end; -procedure TCropAreaList.Load(const XMLConf: TXMLConfig); +procedure TCropAreaList.Load(const XMLConf: TXMLConfig; XMLPath: String); var i, newCount, newSelected: integer; curItemPath, curPath: String; @@ -1553,7 +1553,10 @@ procedure TCropAreaList.Load(const XMLConf: TXMLConfig); begin try - curPath :=fOwner.Name+'.'+Self.Name+'/'; + 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'); @@ -1580,6 +1583,7 @@ procedure TCropAreaList.Load(const XMLConf: TXMLConfig); 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); @@ -1600,37 +1604,44 @@ procedure TCropAreaList.Load(const XMLConf: TXMLConfig); end; end; -procedure TCropAreaList.Save(const XMLConf: TXMLConfig); +procedure TCropAreaList.Save(const XMLConf: TXMLConfig; XMLPath: String); var i: integer; curItemPath, curPath: String; + curCropArea: TCropArea; begin - curPath :=fOwner.Name+'.'+Self.Name+'/'; + 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)+'/'; - XMLConf.SetValue(curItemPath+'Name', Items[i].Name); - XMLConf.SetValue(curItemPath+'KeepAspectRatio', Integer(Items[i].KeepAspectRatio)); - XMLConf.SetValue(curItemPath+'AspectRatio', Items[i].AspectRatio); - XMLConf.SetValue(curItemPath+'Rotate', FloatToStr(Items[i].Rotate)); - XMLConf.SetValue(curItemPath+'AreaUnit', Integer(Items[i].AreaUnit)); + 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(Items[i].Area.Left)); - XMLConf.SetValue(curItemPath+'Area/Top', FloatToStr(Items[i].Area.Top)); - XMLConf.SetValue(curItemPath+'Area/Width', FloatToStr(Items[i].Area.Width)); - XMLConf.SetValue(curItemPath+'Area/Height', FloatToStr(Items[i].Area.Height)); + 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, Items[i], XMLConf, curItemPath); + then fOwner.rOnCropAreaSave(fOwner, curCropArea, XMLConf, curItemPath); end; end; -procedure TCropAreaList.LoadFromStream(Stream: TStream); +procedure TCropAreaList.LoadFromStream(Stream: TStream; XMLPath: String); var FXMLConf: TXMLConfig; @@ -1643,13 +1654,13 @@ procedure TCropAreaList.LoadFromStream(Stream: TStream); FXMLConf.ReadOnly:=True; FXMLConf.LoadFromStream(Stream); {$ENDIF} - Load(FXMLConf); + Load(FXMLConf, XMLPath); finally FXMLConf.Free; end; end; -procedure TCropAreaList.LoadFromFile(const FileName: string); +procedure TCropAreaList.LoadFromFile(const FileName: String; XMLPath: String); var FXMLConf: TXMLConfig; @@ -1662,20 +1673,20 @@ procedure TCropAreaList.LoadFromFile(const FileName: string); FXMLConf.ReadOnly:=True; FXMLConf.LoadFromFile(FileName); {$ENDIF} - Load(FXMLConf); + Load(FXMLConf, XMLPath); finally FXMLConf.Free; end; end; -procedure TCropAreaList.SaveToStream(Stream: TStream); +procedure TCropAreaList.SaveToStream(Stream: TStream; XMLPath: String); var FXMLConf: TXMLConfig; begin try FXMLConf := TXMLConfig.Create(nil); - Save(FXMLConf); + Save(FXMLConf, XMLPath); {$IFDEF USE_Laz2_XMLCfg} FXMLConf.WriteToStream(Stream); {$ELSE} @@ -1686,7 +1697,7 @@ procedure TCropAreaList.SaveToStream(Stream: TStream); end; end; -procedure TCropAreaList.SaveToFile(const FileName: string); +procedure TCropAreaList.SaveToFile(const FileName: String; XMLPath: String); var FXMLConf: TXMLConfig; @@ -1694,11 +1705,11 @@ procedure TCropAreaList.SaveToFile(const FileName: string); try {$IFDEF USE_Laz2_XMLCfg} FXMLConf := TXMLConfig.Create(FileName); - Save(FXMLConf); + Save(FXMLConf, XMLPath); FXMLConf.Flush; {$ELSE} FXMLConf := TXMLConfig.Create(nil); - Save(FXMLConf); + Save(FXMLConf, XMLPath); FXMLConf.SaveToFile(FileName); {$ENDIF} finally From 5c7e3a466d4e58965dc05bebf3a321fcfe84507d Mon Sep 17 00:00:00 2001 From: Massimo Magnano <maxm.dev@gmail.com> Date: Mon, 2 Oct 2023 12:43:22 +0200 Subject: [PATCH 28/34] ContextMenu Event; changed AOwner to Sender in Events; CropArea UserData default=-1 --- bgraimagemanipulation.pas | 38 ++++++++++++++++--- .../unitbgraimagemanipulationdemo.lfm | 4 +- .../unitbgraimagemanipulationdemo.pas | 32 ++++------------ 3 files changed, 41 insertions(+), 33 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index dfa0b57..8421100 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -331,12 +331,16 @@ TBGRANewCropAreaDefault = class(TPersistent) { TBGRAImageManipulation } - TCropAreaEvent = procedure (AOwner: TBGRAImageManipulation; CropArea: TCropArea) of object; - TCropAreaLoadEvent = function (AOwner: TBGRAImageManipulation; CropArea: TCropArea; + 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 (AOwner: TBGRAImageManipulation; CropArea: TCropArea; + 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 } @@ -357,6 +361,7 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) fSizeLimits: TSizeLimits; fImageBitmap, fResampledBitmap, fBackground, fVirtualScreen: TBGRABitmap; rNewCropAreaDefault: TBGRANewCropAreaDefault; + rOnContextPopup: TBGRAIMContextPopupEvent; function getAnchorSize: byte; function getPixelsPerInch: Integer; @@ -405,6 +410,7 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) 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 } @@ -422,8 +428,8 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) //Crop Areas Manipulation functions function addCropArea(AArea : TRectF; AAreaUnit: TResolutionUnit = ruNone; - AUserData: Integer = 0) :TCropArea; - function addScaledCropArea(AArea : TRect; AUserData: Integer = 0) :TCropArea; + AUserData: Integer = -1) :TCropArea; + function addScaledCropArea(AArea : TRect; AUserData: Integer = -1) :TCropArea; procedure delCropArea(ACropArea :TCropArea); procedure clearCropAreas; procedure getAllResampledBitmaps(ACallBack :TgetAllBitmapsCallback); @@ -460,6 +466,12 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) //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; @@ -1811,7 +1823,7 @@ constructor TBGRAEmptyImage.Create(AOwner: TBGRAImageManipulation); fOwner :=AOwner; rAllow :=False; rShowBorder :=False; - rResolutionUnit:=ruPixelsPerInch; + rResolutionUnit:=ruPixelsPerCentimeter; end; { TBGRANewCropAreaDefault } @@ -3969,6 +3981,20 @@ procedure TBGRAImageManipulation.MouseUp(Button: TMouseButton; 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 ]================================================== } diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm index 77adb91..b17d534 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.lfm @@ -1614,15 +1614,13 @@ object FormBGRAImageManipulationDemo: TFormBGRAImageManipulationDemo MinHeight = 40 MinWidth = 30 EmptyImage.Allow = True - EmptyImage.ResolutionUnit = ruPixelsPerCentimeter EmptyImage.ResolutionWidth = 21 EmptyImage.ResolutionHeight = 29.7000007629395 EmptyImage.ShowBorder = True + NewCropAreaDefault.AspectRatio = '3:4' OnCropAreaAdded = AddedCrop OnCropAreaDeleted = DeletedCrop OnCropAreaChanged = ChangedCrop - OnCropAreaLoad = CropAreaLoad - OnCropAreaSave = CropAreaSave OnSelectedCropAreaChanged = SelectedChangedCrop end object BCPanelCropAreas: TBCPanel diff --git a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas index 50eda6e..a622c8a 100644 --- a/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas +++ b/test/test_bgraimagemanipulation/unitbgraimagemanipulationdemo.pas @@ -149,10 +149,6 @@ TFormBGRAImageManipulationDemo = class(TForm) procedure btZDownClick(Sender: TObject); procedure btZFrontClick(Sender: TObject); procedure btZUpClick(Sender: TObject); - function CropAreaLoad(AOwner: TBGRAImageManipulation; CropArea: TCropArea; const XMLConf: TXMLConfig; - const Path: String): Integer; - procedure CropAreaSave(AOwner: TBGRAImageManipulation; CropArea: TCropArea; const XMLConf: TXMLConfig; - const Path: String); procedure edNameChange(Sender: TObject); procedure edUnit_TypeChange(Sender: TObject); procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); @@ -169,10 +165,10 @@ TFormBGRAImageManipulationDemo = class(TForm) procedure rgAspectSelectionChanged(Sender: TObject); procedure btApplyAspectRatioClick(Sender: TObject); - procedure AddedCrop(AOwner: TBGRAImageManipulation; CropArea: TCropArea); - procedure DeletedCrop(AOwner: TBGRAImageManipulation; CropArea: TCropArea); - procedure ChangedCrop(AOwner: TBGRAImageManipulation; CropArea: TCropArea); - procedure SelectedChangedCrop(AOwner: TBGRAImageManipulation; CropArea: TCropArea); + procedure AddedCrop(Sender: TBGRAImageManipulation; CropArea: TCropArea); + procedure DeletedCrop(Sender: TBGRAImageManipulation; CropArea: TCropArea); + procedure ChangedCrop(Sender: TBGRAImageManipulation; CropArea: TCropArea); + procedure SelectedChangedCrop(Sender: TBGRAImageManipulation; CropArea: TCropArea); procedure SpeedButton1Click(Sender: TObject); private { private declarations } @@ -526,18 +522,6 @@ procedure TFormBGRAImageManipulationDemo.btZUpClick(Sender: TObject); end; end; -function TFormBGRAImageManipulationDemo.CropAreaLoad(AOwner: TBGRAImageManipulation; CropArea: TCropArea; - const XMLConf: TXMLConfig; const Path: String): Integer; -begin - // -end; - -procedure TFormBGRAImageManipulationDemo.CropAreaSave(AOwner: TBGRAImageManipulation; CropArea: TCropArea; - const XMLConf: TXMLConfig; const Path: String); -begin - // -end; - procedure TFormBGRAImageManipulationDemo.edNameChange(Sender: TObject); var CropArea :TCropArea; @@ -707,7 +691,7 @@ procedure TFormBGRAImageManipulationDemo.btApplyAspectRatioClick(Sender: TObject end; end; -procedure TFormBGRAImageManipulationDemo.AddedCrop(AOwner: TBGRAImageManipulation; CropArea: TCropArea); +procedure TFormBGRAImageManipulationDemo.AddedCrop(Sender: TBGRAImageManipulation; CropArea: TCropArea); var curIndex :Integer; @@ -723,7 +707,7 @@ procedure TFormBGRAImageManipulationDemo.AddedCrop(AOwner: TBGRAImageManipulatio FillBoxUI(CropArea); end; -procedure TFormBGRAImageManipulationDemo.DeletedCrop(AOwner: TBGRAImageManipulation; CropArea: TCropArea); +procedure TFormBGRAImageManipulationDemo.DeletedCrop(Sender: TBGRAImageManipulation; CropArea: TCropArea); var delIndex :Integer; begin @@ -740,7 +724,7 @@ procedure TFormBGRAImageManipulationDemo.DeletedCrop(AOwner: TBGRAImageManipulat //MessageDlg('Deleting Crop Area', 'Deleting '+CropArea.Name, mtInformation, [mbOk], 0); end; -procedure TFormBGRAImageManipulationDemo.ChangedCrop(AOwner: TBGRAImageManipulation; CropArea: TCropArea); +procedure TFormBGRAImageManipulationDemo.ChangedCrop(Sender: TBGRAImageManipulation; CropArea: TCropArea); begin if (cbBoxList.Items.Objects[cbBoxList.ItemIndex] = CropArea) then begin @@ -752,7 +736,7 @@ procedure TFormBGRAImageManipulationDemo.ChangedCrop(AOwner: TBGRAImageManipulat end; end; -procedure TFormBGRAImageManipulationDemo.SelectedChangedCrop(AOwner: TBGRAImageManipulation; CropArea: TCropArea); +procedure TFormBGRAImageManipulationDemo.SelectedChangedCrop(Sender: TBGRAImageManipulation; CropArea: TCropArea); var newIndex :Integer; begin From 486da7e044648c352d401fc6f790b06e9743dcd1 Mon Sep 17 00:00:00 2001 From: Massimo Magnano <maxm.dev@gmail.com> Date: Tue, 3 Oct 2023 13:34:48 +0200 Subject: [PATCH 29/34] EmptyImage ResolutionUnit and SetEmptyImageSize --- bgraimagemanipulation.pas | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index 8421100..b2aafd8 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -296,6 +296,7 @@ TBGRAEmptyImage = class(TPersistent) function getHeight: Integer; function getWidth: Integer; + procedure SetResolutionUnit(AValue: TResolutionUnit); public property Width:Integer read getWidth; @@ -305,7 +306,7 @@ TBGRAEmptyImage = class(TPersistent) published property Allow: Boolean read rAllow write rAllow default False; - property ResolutionUnit: TResolutionUnit read rResolutionUnit write rResolutionUnit default ruPixelsPerCentimeter; + 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; @@ -436,6 +437,8 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) procedure getAllBitmaps(ACallBack :TgetAllBitmapsCallback); 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; @@ -1817,6 +1820,26 @@ function TBGRAEmptyImage.getWidth: Integer; end; end; +procedure TBGRAEmptyImage.SetResolutionUnit(AValue: TResolutionUnit); +begin + if (AValue<>rResolutionUnit) then + begin + Case AValue of + ruPixelsPerInch : if (rResolutionUnit=ruPixelsPerCentimeter) then //Old Resolution is in Cm + begin + rResolutionWidth :=rResolutionWidth/2.54; + rResolutionHeight :=rResolutionHeight/2.54; + end; + ruPixelsPerCentimeter: if (rResolutionUnit=ruPixelsPerInch) then //Old Resolution is in Inch + begin + rResolutionWidth :=rResolutionWidth*2.54; + rResolutionHeight :=rResolutionHeight*2.54; + end; + end; + rResolutionUnit :=AValue; + end; +end; + constructor TBGRAEmptyImage.Create(AOwner: TBGRAImageManipulation); begin inherited Create; @@ -3412,6 +3435,20 @@ procedure TBGRAImageManipulation.SetEmptyImageSizeToCropAreas(ReduceLarger: Bool 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; From 03677b1d686996abeb44be00e2f33cfb4fec0e38 Mon Sep 17 00:00:00 2001 From: Massimo Magnano <maxm.dev@gmail.com> Date: Wed, 4 Oct 2023 17:09:36 +0200 Subject: [PATCH 30/34] renamed overload of ResolutionUnitConvert, use in EmptyImage.SetResolutionUnit renamed overload of ResolutionUnitConvert to PixelXResolutionUnitConvert avoid confusion; use ResolutionUnitConvert in EmptyImage.SetResolutionUnit --- bgraimagemanipulation.pas | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index b2aafd8..3361f0d 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -479,8 +479,8 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) function RoundUp(AValue:Single):Integer; -function ResolutionUnitConvert(const AValue:Single; fromRes, toRes:TResolutionUnit; predefInchRes:Integer=96):Single; overload; -procedure ResolutionUnitConvert(var resX, resY:Single; fromRes, toRes:TResolutionUnit); overload; +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} @@ -595,7 +595,7 @@ function ResolutionUnitConvert(const AValue:Single; fromRes, toRes:TResolutionUn else Result:=AValue; end; -procedure ResolutionUnitConvert(var resX, resY: Single; fromRes, toRes: TResolutionUnit); +procedure PixelXResolutionUnitConvert(var resX, resY: Single; fromRes, toRes: TResolutionUnit); begin //Do Conversion from/to PixelXInch/PixelXCm if (toRes <> fromRes) then @@ -812,7 +812,7 @@ procedure TCropArea.CalculateScaledAreaFromArea; if (rAreaUnit<>ruNone) then begin GetImageResolution(resX, resY, resUnit); - ResolutionUnitConvert(resX, resY, resUnit, rAreaUnit); + PixelXResolutionUnitConvert(resX, resY, resUnit, rAreaUnit); end; //MaxM: Use Trunc for Top/Left and Round for Right/Bottom so we @@ -848,7 +848,7 @@ procedure TCropArea.CalculateAreaFromScaledArea; if (rAreaUnit<>ruNone) then begin GetImageResolution(resX, resY, resUnit); - ResolutionUnitConvert(resX, resY, resUnit, rAreaUnit); + PixelXResolutionUnitConvert(resX, resY, resUnit, rAreaUnit); end; rArea.Left := (rScaledArea.Left / resX) / xRatio; @@ -878,7 +878,7 @@ function TCropArea.GetPixelArea(const AValue: TRectF): TRect; end else GetImageResolution(resX, resY, resUnit); - ResolutionUnitConvert(resX, resY, resUnit, rAreaUnit); + PixelXResolutionUnitConvert(resX, resY, resUnit, rAreaUnit); Result.Left := Trunc(AValue.Left * resX); Result.Top := Trunc(AValue.Top * resY); @@ -1824,18 +1824,8 @@ procedure TBGRAEmptyImage.SetResolutionUnit(AValue: TResolutionUnit); begin if (AValue<>rResolutionUnit) then begin - Case AValue of - ruPixelsPerInch : if (rResolutionUnit=ruPixelsPerCentimeter) then //Old Resolution is in Cm - begin - rResolutionWidth :=rResolutionWidth/2.54; - rResolutionHeight :=rResolutionHeight/2.54; - end; - ruPixelsPerCentimeter: if (rResolutionUnit=ruPixelsPerInch) then //Old Resolution is in Inch - begin - rResolutionWidth :=rResolutionWidth*2.54; - rResolutionHeight :=rResolutionHeight*2.54; - end; - end; + rResolutionWidth :=ResolutionUnitConvert(rResolutionWidth, rResolutionUnit, AValue, fOwner.PixelsPerInch); + rResolutionHeight :=ResolutionUnitConvert(rResolutionHeight, rResolutionUnit, AValue, fOwner.PixelsPerInch); rResolutionUnit :=AValue; end; end; From eb5fad29623e575d2d4d3f725fcd3c643cf47246 Mon Sep 17 00:00:00 2001 From: Massimo Magnano <maxm.dev@gmail.com> Date: Fri, 6 Oct 2023 13:48:14 +0200 Subject: [PATCH 31/34] ImageManipulation getAllBitmapsCallback UserData --- bgraimagemanipulation.pas | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index 3361f0d..434ee26 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -281,7 +281,7 @@ TCropAreaList = class(TObjectList) property Name:String read rName write rName; end; - TgetAllBitmapsCallback = procedure (Bitmap :TBGRABitmap; CropArea: TCropArea) of object; + TgetAllBitmapsCallback = procedure (Bitmap :TBGRABitmap; CropArea: TCropArea; AUserData:Integer) of object; { TBGRAEmptyImage } @@ -433,8 +433,8 @@ TBGRAImageManipulation = class(TBGRAGraphicCtrl) function addScaledCropArea(AArea : TRect; AUserData: Integer = -1) :TCropArea; procedure delCropArea(ACropArea :TCropArea); procedure clearCropAreas; - procedure getAllResampledBitmaps(ACallBack :TgetAllBitmapsCallback); - procedure getAllBitmaps(ACallBack :TgetAllBitmapsCallback); + procedure getAllResampledBitmaps(ACallBack :TgetAllBitmapsCallback; AUserData:Integer=0); + procedure getAllBitmaps(ACallBack :TgetAllBitmapsCallback; AUserData:Integer=0); procedure SetEmptyImageSizeToCropAreas(ReduceLarger: Boolean=False); procedure SetEmptyImageSizeToNull; @@ -1574,7 +1574,7 @@ procedure TCropAreaList.Load(const XMLConf: TXMLConfig; XMLPath: String); newCount := XMLConf.GetValue(curPath+'Count', -1); if (newCount=-1) - then raise Exception.Create('XML Path not Found'); + then raise Exception.Create('XML Path not Found - '+curPath+'Count'); Clear; Loading :=True; @@ -3343,7 +3343,7 @@ procedure TBGRAImageManipulation.clearCropAreas; Invalidate; end; -procedure TBGRAImageManipulation.getAllResampledBitmaps(ACallBack: TgetAllBitmapsCallback); +procedure TBGRAImageManipulation.getAllResampledBitmaps(ACallBack: TgetAllBitmapsCallback; AUserData:Integer); var i :Integer; curBitmap :TBGRABitmap; @@ -3353,14 +3353,14 @@ procedure TBGRAImageManipulation.getAllResampledBitmaps(ACallBack: TgetAllBitmap for i:=0 to rCropAreas.Count-1 do try curBitmap :=rCropAreas[i].getResampledBitmap; - ACallBack(curBitmap, rCropAreas[i]); + ACallBack(curBitmap, rCropAreas[i], AUserData); finally if (curBitmap<>nil) then curBitmap.Free; end; end; -procedure TBGRAImageManipulation.getAllBitmaps(ACallBack: TgetAllBitmapsCallback); +procedure TBGRAImageManipulation.getAllBitmaps(ACallBack: TgetAllBitmapsCallback; AUserData:Integer); var i :Integer; curBitmap :TBGRABitmap; @@ -3370,7 +3370,7 @@ procedure TBGRAImageManipulation.getAllBitmaps(ACallBack: TgetAllBitmapsCallback for i:=0 to rCropAreas.Count-1 do try curBitmap :=rCropAreas[i].getBitmap; - ACallBack(curBitmap, rCropAreas[i]); + ACallBack(curBitmap, rCropAreas[i], AUserData); finally if (curBitmap<>nil) then curBitmap.Free; From 1d5a31a9722b290ea77d6977e4f171ad0e235a18 Mon Sep 17 00:00:00 2001 From: Massimo Magnano <maxm.dev@gmail.com> Date: Mon, 9 Oct 2023 13:50:26 +0200 Subject: [PATCH 32/34] CropArea Icons --- bgraimagemanipulation.pas | 52 +++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/bgraimagemanipulation.pas b/bgraimagemanipulation.pas index 434ee26..7621347 100644 --- a/bgraimagemanipulation.pas +++ b/bgraimagemanipulation.pas @@ -146,6 +146,7 @@ TCropAreaList = class; { TCropArea } BoolParent = (bFalse=0, bTrue=1, bParent=2); + TCropAreaIcons = set of (cIcoIndex, cIcoLockSize, cIcoLockMove); TCropArea = class(TObject) protected @@ -163,6 +164,7 @@ TCropArea = class(TObject) rName: String; rKeepAspectRatio: BoolParent; Loading :Boolean; + rIcons: TCropAreaIcons; procedure CopyAspectFromParent; procedure setAspectRatio(AValue: string); @@ -185,6 +187,7 @@ TCropArea = class(TObject) procedure setArea(AValue: TRectF); procedure setAreaUnit(AValue: TResolutionUnit); procedure setName(AValue: String); + procedure setIcons(AValue: TCropAreaIcons); procedure Render_Refresh; @@ -241,6 +244,7 @@ TCropArea = class(TObject) 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 } @@ -657,6 +661,13 @@ procedure TCropArea.setName(AValue: String); 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; @@ -2895,11 +2906,12 @@ procedure TBGRAImageManipulation.Render; var WorkRect, emptyRect: TRect; Mask: TBGRABitmap; - BorderColor, SelectColor, FillColor: TBGRAPixel; + BorderColor, SelectColor, + FillColor, IcoColor: TBGRAPixel; curCropArea :TCropArea; curCropAreaRect :TRect; i: Integer; - emptyImg: Boolean; + TextS:TTextStyle; begin // This procedure render main feature of engine @@ -2932,11 +2944,22 @@ procedure TBGRAImageManipulation.Render; curCropArea :=rCropAreas[i]; curCropAreaRect :=curCropArea.ScaledArea; + //Colors + SelectColor := BGRA(255, 255, 0, 255); + FillColor := BGRA(255, 255, 0, 128); + if (curCropArea = SelectedCropArea) - then BorderColor := BGRA(255, 0, 0, 255) - else if (curCropArea = rNewCropArea) - then BorderColor := BGRA(255, 0, 255, 255) - else BorderColor := curCropArea.BorderColor; + 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); @@ -2946,10 +2969,23 @@ procedure TBGRAImageManipulation.Render; 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; - SelectColor := BGRA(255, 255, 0, 255); - FillColor := BGRA(255, 255, 0, 128); // NW Mask.Rectangle(curCropAreaRect.Left-fAnchorSize, curCropAreaRect.Top-fAnchorSize, From c2b600fb3ac46c2b49405fb036b4e8e7281cb2ff Mon Sep 17 00:00:00 2001 From: Leandro Diaz <lainz@users.noreply.github.com> Date: Sat, 14 Oct 2023 06:28:17 -0300 Subject: [PATCH 33/34] Update bcpanel.pas --- bcpanel.pas | 1 + 1 file changed, 1 insertion(+) diff --git a/bcpanel.pas b/bcpanel.pas index 46cd7ea..9471628 100644 --- a/bcpanel.pas +++ b/bcpanel.pas @@ -122,6 +122,7 @@ TBCPanel = class(TCustomBCPanel) property Border; property BorderBCStyle; property Caption; + property Color; property Constraints; property DockSite; property DragCursor; From 7fdf2b97a38810c32350cb19b61017fd0405ea40 Mon Sep 17 00:00:00 2001 From: Leandro Diaz <lainz@users.noreply.github.com> Date: Sun, 15 Oct 2023 13:50:33 -0300 Subject: [PATCH 34/34] windows 11 light styles for panel, panel title, panel text, and demo --- styles/windows11-panel-light.bcpnl | 115 +++++++++++++++++++ styles/windows11-panel-text-light.bclbl | 89 ++++++++++++++ styles/windows11-panel-title-light.bclbl | 89 ++++++++++++++ test/test_windows_theme/umain.lfm | 123 ++++++++++++++++++++ test/test_windows_theme/umain.pas | 42 +++++++ test/test_windows_theme/windowstheme.ico | Bin 0 -> 133345 bytes test/test_windows_theme/windowstheme.lpi | 140 +++++++++++++++++++++++ test/test_windows_theme/windowstheme.lpr | 25 ++++ 8 files changed, 623 insertions(+) create mode 100644 styles/windows11-panel-light.bcpnl create mode 100644 styles/windows11-panel-text-light.bclbl create mode 100644 styles/windows11-panel-title-light.bclbl create mode 100644 test/test_windows_theme/umain.lfm create mode 100644 test/test_windows_theme/umain.pas create mode 100644 test/test_windows_theme/windowstheme.ico create mode 100644 test/test_windows_theme/windowstheme.lpi create mode 100644 test/test_windows_theme/windowstheme.lpr diff --git a/styles/windows11-panel-light.bcpnl b/styles/windows11-panel-light.bcpnl new file mode 100644 index 0000000..049d2eb --- /dev/null +++ b/styles/windows11-panel-light.bcpnl @@ -0,0 +1,115 @@ +[HEADER] +Author=Me +Description= +ControlClass=TBCPanel + +[PROPERTIES] +Align = alNone +AnchorSideBottom.Side = asrTop +AnchorSideLeft.Side = asrTop +AnchorSideRight.Side = asrTop +AnchorSideTop.Side = asrTop +Anchors = akTop,akLeft,akRight +AutoSize = 0 +Background.Color = 16514043 +Background.ColorOpacity = 255 +Background.Gradient1.ColorCorrection = 1 +Background.Gradient1.DrawMode = dmSet +Background.Gradient1.EndColor = 0 +Background.Gradient1.EndColorOpacity = 255 +Background.Gradient1.GradientType = gtLinear +Background.Gradient1.Point1XPercent = 0 +Background.Gradient1.Point1YPercent = 0 +Background.Gradient1.Point2XPercent = 0 +Background.Gradient1.Point2YPercent = 100 +Background.Gradient1.Sinus = 0 +Background.Gradient1.StartColor = 16777215 +Background.Gradient1.StartColorOpacity = 255 +Background.Gradient1EndPercent = 35 +Background.Gradient2.ColorCorrection = 1 +Background.Gradient2.DrawMode = dmSet +Background.Gradient2.EndColor = 0 +Background.Gradient2.EndColorOpacity = 255 +Background.Gradient2.GradientType = gtLinear +Background.Gradient2.Point1XPercent = 0 +Background.Gradient2.Point1YPercent = 0 +Background.Gradient2.Point2XPercent = 0 +Background.Gradient2.Point2YPercent = 100 +Background.Gradient2.Sinus = 0 +Background.Gradient2.StartColor = 16777215 +Background.Gradient2.StartColorOpacity = 255 +Background.Style = bbsColor +BevelInner = bvNone +BevelOuter = bvNone +BevelWidth = 1 +Border.Color = 15066597 +Border.ColorOpacity = 255 +Border.LightColor = 16777215 +Border.LightOpacity = 255 +Border.LightWidth = 0 +Border.Style = bboSolid +Border.Width = 1 +BorderBCStyle = bpsBorder +BorderSpacing.Around = 0 +BorderSpacing.Bottom = 0 +BorderSpacing.CellAlignHorizontal = ccaFill +BorderSpacing.CellAlignVertical = ccaFill +BorderSpacing.InnerBorder = 0 +BorderSpacing.Left = 0 +BorderSpacing.Right = 0 +BorderSpacing.Top = 0 +ChildSizing.ControlsPerLine = 0 +ChildSizing.EnlargeHorizontal = crsAnchorAligning +ChildSizing.EnlargeVertical = crsAnchorAligning +ChildSizing.HorizontalSpacing = 0 +ChildSizing.Layout = cclNone +ChildSizing.LeftRightSpacing = 0 +ChildSizing.ShrinkHorizontal = crsAnchorAligning +ChildSizing.ShrinkVertical = crsAnchorAligning +ChildSizing.TopBottomSpacing = 0 +ChildSizing.VerticalSpacing = 0 +Color = 15987699 +Constraints.MaxHeight = 0 +Constraints.MaxWidth = 0 +Constraints.MinHeight = 0 +Constraints.MinWidth = 0 +Cursor = 0 +DockSite = 0 +DragCursor = -12 +DragKind = dkDrag +DragMode = dmManual +Enabled = 1 +FontEx.Color = 536870912 +FontEx.DisabledColor = 536870911 +FontEx.EndEllipsis = 0 +FontEx.FontQuality = fqSystemClearType +FontEx.Height = 0 +FontEx.Name = default +FontEx.PaddingBottom = 0 +FontEx.PaddingLeft = 0 +FontEx.PaddingRight = 0 +FontEx.PaddingTop = 0 +FontEx.Shadow = 0 +FontEx.ShadowColor = 0 +FontEx.ShadowColorOpacity = 255 +FontEx.ShadowOffsetX = 5 +FontEx.ShadowOffsetY = 5 +FontEx.ShadowRadius = 5 +FontEx.SingleLine = 1 +FontEx.Style = +FontEx.TextAlignment = bcaCenter +FontEx.WordBreak = 0 +HelpContext = 0 +HelpKeyword = +HelpType = htContext +Hint = +ParentBackground = 0 +Rounding.RoundOptions = +Rounding.RoundX = 5 +Rounding.RoundY = 5 +ShowHint = 0 +TabOrder = 0 +TabStop = 0 +Tag = 0 +UseDockManager = 1 +Visible = 1 diff --git a/styles/windows11-panel-text-light.bclbl b/styles/windows11-panel-text-light.bclbl new file mode 100644 index 0000000..b4cea3f --- /dev/null +++ b/styles/windows11-panel-text-light.bclbl @@ -0,0 +1,89 @@ +[HEADER] +Author=Me +Description= +ControlClass=TBCLabel + +[PROPERTIES] +Align = alNone +AnchorSideBottom.Side = asrTop +AnchorSideLeft.Side = asrTop +AnchorSideRight.Side = asrTop +AnchorSideTop.Side = asrTop +Anchors = akTop,akLeft +AutoSize = 1 +Background.Color = 0 +Background.ColorOpacity = 255 +Background.Gradient1.ColorCorrection = 1 +Background.Gradient1.DrawMode = dmSet +Background.Gradient1.EndColor = 0 +Background.Gradient1.EndColorOpacity = 255 +Background.Gradient1.GradientType = gtLinear +Background.Gradient1.Point1XPercent = 0 +Background.Gradient1.Point1YPercent = 0 +Background.Gradient1.Point2XPercent = 0 +Background.Gradient1.Point2YPercent = 100 +Background.Gradient1.Sinus = 0 +Background.Gradient1.StartColor = 16777215 +Background.Gradient1.StartColorOpacity = 255 +Background.Gradient1EndPercent = 35 +Background.Gradient2.ColorCorrection = 1 +Background.Gradient2.DrawMode = dmSet +Background.Gradient2.EndColor = 0 +Background.Gradient2.EndColorOpacity = 255 +Background.Gradient2.GradientType = gtLinear +Background.Gradient2.Point1XPercent = 0 +Background.Gradient2.Point1YPercent = 0 +Background.Gradient2.Point2XPercent = 0 +Background.Gradient2.Point2YPercent = 100 +Background.Gradient2.Sinus = 0 +Background.Gradient2.StartColor = 16777215 +Background.Gradient2.StartColorOpacity = 255 +Background.Style = bbsClear +Border.Color = 0 +Border.ColorOpacity = 255 +Border.LightColor = 16777215 +Border.LightOpacity = 255 +Border.LightWidth = 0 +Border.Style = bboNone +Border.Width = 1 +BorderSpacing.Around = 0 +BorderSpacing.Bottom = 0 +BorderSpacing.CellAlignHorizontal = ccaFill +BorderSpacing.CellAlignVertical = ccaFill +BorderSpacing.InnerBorder = 0 +BorderSpacing.Left = 0 +BorderSpacing.Right = 0 +BorderSpacing.Top = 0 +Cursor = 0 +Enabled = 1 +FontEx.Color = 6250335 +FontEx.DisabledColor = 536870911 +FontEx.EndEllipsis = 0 +FontEx.FontQuality = fqSystemClearType +FontEx.Height = 14 +FontEx.Name = default +FontEx.PaddingBottom = 0 +FontEx.PaddingLeft = 0 +FontEx.PaddingRight = 0 +FontEx.PaddingTop = 0 +FontEx.Shadow = 0 +FontEx.ShadowColor = 0 +FontEx.ShadowColorOpacity = 255 +FontEx.ShadowOffsetX = 5 +FontEx.ShadowOffsetY = 5 +FontEx.ShadowRadius = 5 +FontEx.SingleLine = 1 +FontEx.Style = +FontEx.TextAlignment = bcaCenter +FontEx.WordBreak = 0 +HelpContext = 0 +HelpKeyword = +HelpType = htContext +Hint = +InnerMargin = 0 +Rounding.RoundOptions = +Rounding.RoundX = 1 +Rounding.RoundY = 1 +ShowHint = 0 +Tag = 0 +Visible = 1 diff --git a/styles/windows11-panel-title-light.bclbl b/styles/windows11-panel-title-light.bclbl new file mode 100644 index 0000000..4034170 --- /dev/null +++ b/styles/windows11-panel-title-light.bclbl @@ -0,0 +1,89 @@ +[HEADER] +Author=Me +Description= +ControlClass=TBCLabel + +[PROPERTIES] +Align = alNone +AnchorSideBottom.Side = asrTop +AnchorSideLeft.Side = asrTop +AnchorSideRight.Side = asrTop +AnchorSideTop.Side = asrTop +Anchors = akTop,akLeft +AutoSize = 1 +Background.Color = 0 +Background.ColorOpacity = 255 +Background.Gradient1.ColorCorrection = 1 +Background.Gradient1.DrawMode = dmSet +Background.Gradient1.EndColor = 0 +Background.Gradient1.EndColorOpacity = 255 +Background.Gradient1.GradientType = gtLinear +Background.Gradient1.Point1XPercent = 0 +Background.Gradient1.Point1YPercent = 0 +Background.Gradient1.Point2XPercent = 0 +Background.Gradient1.Point2YPercent = 100 +Background.Gradient1.Sinus = 0 +Background.Gradient1.StartColor = 16777215 +Background.Gradient1.StartColorOpacity = 255 +Background.Gradient1EndPercent = 35 +Background.Gradient2.ColorCorrection = 1 +Background.Gradient2.DrawMode = dmSet +Background.Gradient2.EndColor = 0 +Background.Gradient2.EndColorOpacity = 255 +Background.Gradient2.GradientType = gtLinear +Background.Gradient2.Point1XPercent = 0 +Background.Gradient2.Point1YPercent = 0 +Background.Gradient2.Point2XPercent = 0 +Background.Gradient2.Point2YPercent = 100 +Background.Gradient2.Sinus = 0 +Background.Gradient2.StartColor = 16777215 +Background.Gradient2.StartColorOpacity = 255 +Background.Style = bbsClear +Border.Color = 0 +Border.ColorOpacity = 255 +Border.LightColor = 16777215 +Border.LightOpacity = 255 +Border.LightWidth = 0 +Border.Style = bboNone +Border.Width = 1 +BorderSpacing.Around = 0 +BorderSpacing.Bottom = 0 +BorderSpacing.CellAlignHorizontal = ccaFill +BorderSpacing.CellAlignVertical = ccaFill +BorderSpacing.InnerBorder = 0 +BorderSpacing.Left = 0 +BorderSpacing.Right = 0 +BorderSpacing.Top = 0 +Cursor = 0 +Enabled = 1 +FontEx.Color = 1776411 +FontEx.DisabledColor = 536870911 +FontEx.EndEllipsis = 0 +FontEx.FontQuality = fqSystemClearType +FontEx.Height = 20 +FontEx.Name = default +FontEx.PaddingBottom = 0 +FontEx.PaddingLeft = 0 +FontEx.PaddingRight = 0 +FontEx.PaddingTop = 0 +FontEx.Shadow = 0 +FontEx.ShadowColor = 0 +FontEx.ShadowColorOpacity = 255 +FontEx.ShadowOffsetX = 5 +FontEx.ShadowOffsetY = 5 +FontEx.ShadowRadius = 5 +FontEx.SingleLine = 1 +FontEx.Style = fsBold +FontEx.TextAlignment = bcaCenter +FontEx.WordBreak = 0 +HelpContext = 0 +HelpKeyword = +HelpType = htContext +Hint = +InnerMargin = 0 +Rounding.RoundOptions = +Rounding.RoundX = 1 +Rounding.RoundY = 1 +ShowHint = 0 +Tag = 0 +Visible = 1 diff --git a/test/test_windows_theme/umain.lfm b/test/test_windows_theme/umain.lfm new file mode 100644 index 0000000..2c3e30e --- /dev/null +++ b/test/test_windows_theme/umain.lfm @@ -0,0 +1,123 @@ +object frmWindowsTheme: TfrmWindowsTheme + Left = 63 + Height = 480 + Top = 225 + Width = 848 + Caption = 'Windows Theme' + ClientHeight = 480 + ClientWidth = 848 + Color = 15987699 + DesignTimePPI = 192 + Position = poScreenCenter + LCLVersion = '3.99.0.0' + OnCreate = FormCreate + object BCPanel1: TBCPanel + Left = 16 + Height = 224 + Top = 16 + Width = 816 + Anchors = [akTop, akLeft, akRight] + Background.Color = 16514043 + Background.Gradient1.StartColor = clWhite + Background.Gradient1.EndColor = clBlack + Background.Gradient1.GradientType = gtLinear + Background.Gradient1.Point1XPercent = 0 + Background.Gradient1.Point1YPercent = 0 + Background.Gradient1.Point2XPercent = 0 + Background.Gradient1.Point2YPercent = 100 + Background.Gradient2.StartColor = clWhite + Background.Gradient2.EndColor = clBlack + Background.Gradient2.GradientType = gtLinear + Background.Gradient2.Point1XPercent = 0 + Background.Gradient2.Point1YPercent = 0 + Background.Gradient2.Point2XPercent = 0 + Background.Gradient2.Point2YPercent = 100 + Background.Gradient1EndPercent = 35 + Background.Style = bbsColor + BevelInner = bvNone + BevelOuter = bvNone + BevelWidth = 1 + Border.Color = 15066597 + Border.Style = bboSolid + BorderBCStyle = bpsBorder + FontEx.Color = clDefault + FontEx.FontQuality = fqSystemClearType + FontEx.Shadow = False + FontEx.ShadowRadius = 5 + FontEx.ShadowOffsetX = 5 + FontEx.ShadowOffsetY = 5 + FontEx.Style = [] + ParentBackground = False + Rounding.RoundX = 5 + Rounding.RoundY = 5 + TabOrder = 0 + object BCLabel1: TBCLabel + Left = 48 + Height = 28 + Top = 48 + Width = 267 + Background.Gradient1.StartColor = clWhite + Background.Gradient1.EndColor = clBlack + Background.Gradient1.GradientType = gtLinear + Background.Gradient1.Point1XPercent = 0 + Background.Gradient1.Point1YPercent = 0 + Background.Gradient1.Point2XPercent = 0 + Background.Gradient1.Point2YPercent = 100 + Background.Gradient2.StartColor = clWhite + Background.Gradient2.EndColor = clBlack + Background.Gradient2.GradientType = gtLinear + Background.Gradient2.Point1XPercent = 0 + Background.Gradient2.Point1YPercent = 0 + Background.Gradient2.Point2XPercent = 0 + Background.Gradient2.Point2YPercent = 100 + Background.Gradient1EndPercent = 35 + Background.Style = bbsClear + Border.Style = bboNone + Caption = 'Configuración recomendada' + FontEx.Color = 1776411 + FontEx.FontQuality = fqSystemClearType + FontEx.Height = 20 + FontEx.Shadow = False + FontEx.ShadowRadius = 5 + FontEx.ShadowOffsetX = 5 + FontEx.ShadowOffsetY = 5 + FontEx.Style = [fsBold] + Rounding.RoundX = 1 + Rounding.RoundY = 1 + end + object BCLabel2: TBCLabel + Left = 48 + Height = 19 + Top = 128 + Width = 280 + Background.Gradient1.StartColor = clWhite + Background.Gradient1.EndColor = clBlack + Background.Gradient1.GradientType = gtLinear + Background.Gradient1.Point1XPercent = 0 + Background.Gradient1.Point1YPercent = 0 + Background.Gradient1.Point2XPercent = 0 + Background.Gradient1.Point2YPercent = 100 + Background.Gradient2.StartColor = clWhite + Background.Gradient2.EndColor = clBlack + Background.Gradient2.GradientType = gtLinear + Background.Gradient2.Point1XPercent = 0 + Background.Gradient2.Point1YPercent = 0 + Background.Gradient2.Point2XPercent = 0 + Background.Gradient2.Point2YPercent = 100 + Background.Gradient1EndPercent = 35 + Background.Style = bbsClear + Border.Style = bboNone + Caption = 'Configuración reciente y usada habitualmente' + FontEx.Color = 6250335 + FontEx.FontQuality = fqSystemClearType + FontEx.Height = 14 + FontEx.Shadow = False + FontEx.ShadowRadius = 5 + FontEx.ShadowOffsetX = 5 + FontEx.ShadowOffsetY = 5 + FontEx.Style = [] + Rounding.RoundX = 1 + Rounding.RoundY = 1 + end + end +end diff --git a/test/test_windows_theme/umain.pas b/test/test_windows_theme/umain.pas new file mode 100644 index 0000000..b4aeb53 --- /dev/null +++ b/test/test_windows_theme/umain.pas @@ -0,0 +1,42 @@ +unit umain; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, Forms, Controls, Graphics, Dialogs, BCPanel, BCLabel; + +type + + { TfrmWindowsTheme } + + TfrmWindowsTheme = class(TForm) + BCLabel1: TBCLabel; + BCLabel2: TBCLabel; + BCPanel1: TBCPanel; + procedure FormCreate(Sender: TObject); + private + + public + + end; + +var + frmWindowsTheme: TfrmWindowsTheme; + +implementation + +{$R *.lfm} + +{ TfrmWindowsTheme } + +procedure TfrmWindowsTheme.FormCreate(Sender: TObject); +begin + BCLabel1.FontEx.Height := ScaleY(BCLabel1.FontEx.Height, 96); + BCLabel2.FontEx.Height := ScaleY(BCLabel2.FontEx.Height, 96); + BCPanel1.Border.Width := ScaleY(BCPanel1.Border.Width, 96); +end; + +end. + diff --git a/test/test_windows_theme/windowstheme.ico b/test/test_windows_theme/windowstheme.ico new file mode 100644 index 0000000000000000000000000000000000000000..10c5fc1a3d8d9ff264229f4ff1cf99ebc83f167f GIT binary patch literal 133345 zcma&NbyOV96D~ZvEKXnv?h*ndxCD0yZXvjYMFT;DJBtJl65KUN2=2ia2@*VL(8b-| zFYo<*_y2Fs%$%t+Ju}aAS9ev{Qw;zh00a1M0|B(acp3nNJYB=#|C5<9K!9F40B~~t zPcD8500H?RfSLJ!vK2W1yy|~?lJ@_*4+Vg@RS-Z*`ad}g695FyKmZ}(|KvtA0O+^} z0nn#%|9#I20sx*6Fc7Y;D)$VF9P8=R&lKckH2%B$-+}Q|9x!w&wgjFEE67M{dSvXi zx~EX}x-#7ePcHZLBVf>vvGNRH21z)C=WVKHEt_7#PXv<TR@A@R*7su5$opRV_MhTn z;?K$15x;LitmD<pujA<1xD=q6mT(4)Z@|-o81Oz)<t&O{yZ7&MOUGN?IpfwSeMp=g z+_f&Zf81nh7X1j&OZ)!+Yxj!8SK&vg;q}{Dyb8ar)Wy4=K)E^<$PJR9$}nRGQ2|20 z2M7SI0|5et0IFv=9rVwNz#&nn6%=6&%>-Y)!u3lNP(|4j_;?41JR&-%8fY>*do#W( z<miEp7;`;$9oPxEr^pX(DSSG;z{Z&BcyMY!FVD-UKlhh#8T00Gb%_qa7dcQ18WIR= zmlE5j?{SO1If!)FySi~)2u=HLU8D!%L@C*vovWTOUg!=t8pce9_M$<P2=K9!*DyfY z@o4<$K%PAMG`d@D6hd%XP5@m(#7WEvb=cQ&Z!<WvyDAeDbE9H=8&I}HS>}AImu8wq z1n|JU*jvhow9obF-H6cZff;$3ay?m)fn?eXKp4M(HWnPv1;v5n-OL&r@|@8KFs%xs z%ipc9M546|{MJ(NWp%L|9sueI##ZMq>pN;1s67tSKT_D`>!ndXy?tCpiErUo()FEh z#*r$xoDbv>m?;g{qa=Va#)`?Vh>yjcR0uHbrgS1?#8ERHU>~T%0zZI_1GAJW<2|Dy z^D~s*(Z*mBuSf+rAK)(w;)?Y7%%AQ=JBX4mn<f>)Em0AKV?AH9T~upM&XEr)z@oH_ z(T<^fbNt_l=SW&k#tvniC@`D{({I2HW^r2SjH;UoPFaNu>K#wJb!6bASfD^S2Li67 zV1r=bstx^$5(qui0&uTB5cxGh+Vw+>Uv*U^A$49CN8@%-P)WJEkowhu9dWar9S^!L z6FK9S>eSVxr1Y>%0%&)R%pkv#Zyz$z)9^%t%M422kFq&<Kh;6JVz$KqUz=Cc$dw9m z?}bbXQ#AH_BdaTkc%Bf6W*$s8Sr{#}2Dx_Xh*jo@#pZ}LRLPC1+XS}lMa4%S59}bM zEkw<w0>M4H!5C2<R$p<i0HpJkI=2)kOT8ok`4y+@37JJPO=50MG46RTH#g3K_mN=e zp%NVuTM}twf(#9erY|m@r&t^iJTU1#w7ql|U#ME$P3+(M-oP`21y{&<ne6BD>~zK< zXloU+LnwzbDxH1^;1eJ$RyfR7%s!qt8f+l!w&Cw>gAA+!_Ge%aPT5MrQL>J2Qk~Vx zkpYN=W<($?r~0+2r~{lDI{S}b*=Ol$|9T~*7qmn|dx?xZq%78c&5U=RFEkpd@o1fJ ztJXn576<MinLbW*JgRdAx=cqq;j01xkIjdt{vua{?fY4Gi{9R2KY<k5ou-R=)@L4v zm*HalVddzx2Gk%?>l^|u&PW)3P&^0yxEP-ODW!S(+!sNP4m*Wh7+0Vj=8-{^wIoar zc%i4N_gtnD$2|bxB$FG>3eoPMpUR<ska~}OxNs>b@`|$Cy*%K+bERPu8|F<}?6p#{ zi5Y<GznP4qi^9#2yxx12;1!cEB!V$QViA0ckOnio##wt57U<=d{d0G*I`TL-O6Y!U z@a;(#1+4oEmkfK<uXzO)6lK`pPOF#_fGkLv@eN;)#av(Q@Y}*Vf`3z<eY_2bY79Oz z@YeG^KC?<zgsJjfec)+;gnB1XnKF8%ycjQ!feubif~T=zh==~|{7b{n>z8wnB2juM z_9F{uy!mkM;~^$DdAaNv>o54D?aD(ebn}`y1}ER;0wU`jV|F&kb(M%EGLjtFGu?@} zeCcdHEOb<Zywb_mYQMM{U-EPPBXr9qUjs%`TKE5MuxIEI9|{x0Lg1!fL1D@IsbL+} zq^!M?<z^r!XA-*cMa<rM)KNQZTol{%4#q$;1M<D--$>3tI}|>G-Zw|zIjf$rNa}(y zOhM``60aX&^jyk>GFRVY4wb|glEEGE`m>ji*wPGa7KwmtXAiCDh{ODya5JKq$o96- zPm?5ph*f=EO<$VCAE6qDpK-E9qW+fTAN`Oby_yd_{CtH(a(9weV+r^SU#kgt8hiNO zwu=kZy+D>p)_`HQGNKgX$1E~?F_JE*Im*^1?|IOkNuhepeU9DNh2PD8>2q=EsG<@e z?x+fub+<+9wR9v4vg2voN%@V`$Ejx<;C>Dcd<T044Tinr0ue(1HvIeeb}dKz(Nst{ zF+2rRJqdm^2C{Dibsk=Rso<z<=DYW$2DODo^u9ax(frYNH@xnC`T`Qptn+sJj0-1Q zAZm=q0a2>`UHDz^Yk(TGX)IQGZy+8LWi+he`!pOp`&+Hj&=QYg46%9vJ>UPnQ76MW zVu@q_p5-KSp^sxLWYrQZVSp2@(x;0PZPrnTQEu1a+U?m(?@3w7ri6jAj;l*-i(UED zLV*%F7IC-_sql{)7_l2VQ2Y|jMQDi+!2SiE1wumgb<yq<w4hGtEeLvbM8yqdVnobS z#TydYOMYL;-@9hk@l7bDeod`j?#1ja_F9V$@<W4DbASjZTPSe(MMWO3Bcm<RmWL#5 zEu{GYdvMeLdX}fbAFn1bhy7{L5URBuXFEI-6qA!l4{#*R?C%rR1D_4LzS}a4lA6{q zH<&dsP|PPV90;`GY2=fZ1ua_k39K}j@t7BE=;#}#JT8;M|K2EH-@ZnCwDLFENAD2R z8296D9Im5)34mqwX>=qdQCY>I0Hp?PaZcvht}hw-k$`%clV;P|eWK;1kKMBbmKFD= zoqWx6^>Pf*yK=9S6bIdBopz+#!a>Vs=$t+)t{&^vxFR?6=$D@Ij^VvXHT=7g*82Mk znimI$gWn4DK(SIv98DHAcs{B4Mwl`IYB2KvJt+d=f8Yr0q+9`ivW%Y-7kmV13yhd9 z*ktK+tNMMq&#$7(Mw*}B?U>C$4D^YTaB$LcNX7$X7vX=NC;Z6r;O$_}@F5@oVpF|k z0-);Sfq!3)o?hl4C^sqRNq$UCcS`7^`#}jckvia9Ln44f>pN}n6|)u?^wbPDhHnk9 z#9k+IheT6#k1p$|laa|x5`Yn#)OQS$$lDmGr~lpTg@gE4JxOT39tfKIo8*22Jn-Yr zo&I7Du3R4pHby&`FXSU6DU-={#y6aM>Zy)Yx%)i0(o;k^BSQ68`(dl)FCl?8k?`!3 zTRX-TP!bNceo`2Vu23GwDh^zE!%Ra5eY;I^sv=EplMV!BOl66ZPh)RX#i55BNX26P z)IsS)54h#l#aPb1HT?Z-O%4^y_r=xbXX^{>edBL%jzknXPW@x`AM%pw$L}<>0|>FY ziWU*b<yPkN%z#bLS$cUWWPbDRR+H_qKVY-X8Cnx4G5!;Xt##X1IZzk7^bZ&}ex9oU zlT|I@3WjG}rv_~tO!n1{5CHWG=_lYVzG|$a@~ia3*(I&105dCZvg7G7A7@o#CLB~x zi1|;jW<Oew7mYP21wGK`j`=v4BahK~hy!d-_#8(c!)nPu|5+OcIPA3*^ZUS3ZpY$6 z(C<Mw6fN;bOm*+O@-SVAlIQpU(Xnss^Tuc2Bb*2XWQQrc5FHCo=w&BQ5xz88go^Wc z->LCFANYO`Y~yIFTJ3$2LIkM+1ArIb!Ug-)!xBM0^*-G|lWPx#GTI@xik+2yHP)9V z?6*0?^|4h7z<gD#E8DX>!yYMZEQsC195LlHAN+ojanH_P9R!K<<!GVk3&AnK;)KOf z=~-D%l_6$|B4y`u<g)ZKcZe4BRd+|-SWYk~#}v(^jKvZ!&{|kc57n{-5n74igHKbe z{T5zXU<x9?QzX42E%9Pb4A$cOv4XA$J)@R;wBg8`_b1mH2|c_P@(0aN=Y4g4d~<N! z?tk>pzg<miVNM&`Qih87hAlSL!#zb3KEnhu`}H)YDqUTt7V`04{*?3^_93fECN~OL zAVQ8bC*85#d7BV%zG7zBvLl*#u|HiJCFXpLmveGO_ogMdsea>Y`FlNIkejsTd&cgU z5?2&z0u+DdLZiq?eH_6|T^6w<SWG0Ssb3`7LF&gMnm-J6A%ECnk3n=8KS<fI12{F_ zNlE>}%yN7dqS_kJfx?l?>!__uo%Kf`YKYV)`|HL1gAK>L?{kOul1KGKfD-?r55dR1 zLyQ!PuX_ej2v%%+#>m2hH_WVtXw%w$BGOaD@R9A9uPp-wsIV8SQ10kTU_zvQ2!jvB zR%zqT0D>J*Kka<+O&n}pRuPTyLtK`?^2%kQv7d%5?jb+qCx$JW>W%kV3fAkiU2K8N zXQdLq(8XL3>Ut;^U2LRfSEOv-q$QeM`o~-v7qxKa620JvnC1A2e1}K&s=zVq6_h{R zadxiH+b)aHvzqP=pjCqniBOL@O!)U?KbI_H*M~SRFPfY{OXba39>#C(Sf}2je_M%D z4T;Ok5NyhKK0SO02f9PW>?N&2vG^(wW6>dW?VO@RBr&mQV+#e5zK}O~$DqhJ8Lw2r zA#!cl8IUHAw0?&QsY0=HiaeYbD6u5-+2$)=@0z7YI-$|g=?duMcXY-n61}ik#)?W1 zJvRD{r72=k5FGHro6Ez4<45AB88(SZ`mxJ<@4Jq+CDF5!h2A5+YP3adFl@WWzyavu z2MzKQc#;xG3Rvp()w$=o{zfy880wozLUS87>9b&6qeQ=s`r^|grJDNqcix?f@wv|) z?Ps~Q%}7nd%uIAJ*LHMot%zF9n)C#;*yb0Ch;|`yj!l-sL^UVz1+(*fEv_b#Q(hF8 zm{@CVPf}qWTT?)`w3CYTl@1p@8;FhYwN}C(%KR)ST7J;|*3zSh><HzT&FjDPobllR zZidYFyMC0B`_MmB9s}5c4PSt*|6IwC77qNGqMVO?2~!_o#6jspc^2EK%&c1hHe6JL z1hXGfuCAj-FPfshX5gPWQ>Ct%#ho3Jk)qi4xO|KLkdC*NaR>L{^k?#yZJXd6ikB2+ zZx>&t4xd)I%L4~(6Ai8ig}@*r>x&Fd|EdN#&+v62VkEohuiNS|WLc<Xm8e6W&WhBs zuc<=@q$G;<f}AFMkNWvX8xF}dW#y39St<2|O}{%5a&_*LA8LBV$8UFnP&^{B0~%TY zU9V%dUBUcAsL30sauJtlr7hZAb!>VKw2XyAhEM0o&<IXTeULppC8O}1#)80Y!e5E& z$X-Z~dvuO4d2+}S@*Q`@wQx>?-KbQ2%X6Z2N>}Mg(J7?9u&1wdA04@?zWyZd6iQcK zgZsrB@G#uKCmj#{6ap(?MU`lZSgz(^4gBUvpjlbTD-`jd<8E>_BdIB>_6yaInGbpE zfteVPjwkjQw29x1w&)ayNV7*{jQ3)1I?wOkVy9=ta9hA1^L>KbPc&A4qfvYDQ<6Pd zQp_wjw(8xWfYtCn<_${T&F;&MjG)4wL8d*#+5s*gm!9mG$y<MTjbxRX_9k$VpG9kx z(|HPBo(r0F;hryZ4fa%+4Nl9A$l)=b!1Rela?3q2sjmx?o>k+lGFi+^9O%oyxQ}1S zyIXR8E0}oxNn_x9DzrOl`>jUOqUy6sR`k>h)Jp8KV%!Y0OHnEan&u+v#<$F-x+drI zsx~tYk?hXx^sL1+Fml#rKdVW{KP&734bH;q$8~OLKx{iF<#zyfF?4*1RYs-JJjZNl z$Gc9+yWYJ7TuCt#7QBfa(slAuV?W~JKj5?^ggcVkCv?(cb`6OX#gNe$F)#a^!MKZa z)%b9BW(299%P0^l?{$5x7SSJu@@~AXrG*niys}YDHK3#c+ApE3SQSYLH6rwJSd~t1 z%{N#>jL8GUg-DNvTxTXNlkOjq%IhNMe>@_Ojubd@!xtyck_Z46_M5LwLmy6!Aiv1q zi^ggNQ?rgnBxNk@GP{4C^;z)rH4{gWQT+b-LL$3vEP({+@F!yRNh-RwHK&R@*(FVP z3R9LeOJ(Dqo|mKjXeH;1^Um?;=y()Won``qMp?@UGl$QH!Fr1U`#10DxJR@;q`xIy zgsk%`dYQs}3Ui`U5-2Z*?vy`)la{Anu{=#vn4u&CZ%($*OzfvEXtM473AIRq=G(zv zefHbG*ykqBM@P5s0}4E_M-3DK7h|V^xAUD{5d8{XNW5i}-CSRNZLZn97W1GHE!dlk zrQkfhhA-_@YL(xDijbQRE0uO8xhg2ps{@S^h<Cm)MFa=IxEv|Egnth87L`c9_58Y< za>t;fHn#P34J4!L48e~0dyqdcxuGyZ>lpk|^=wBDp5jEH92xbSn~f}}uVb`B53O_* ztHDmJC4lFs^ra{Pf%3>-(*f&WCD!&MUH84lVsVI#^lyvY{~HczWgis~8R`j3&z(<x zUoTi0d!wFQw;Y`tfgVh?udmBF?`v=)^bF7+s>{nrj%J91-YOqXlxn!-)@hKw*)A-< z(?Xvpgv+DflS<f~6pXyI+Gp5hSkALtnAr{v>Zn_J{<@zV1G8^5Uc=G%i-vOtVTtH- zIx+KKXdMsXkk(nHMu_T@|7U-sh`H!!%t<xiYM2wgw<l*Hq<ye0eEgT?c*^T^%s>iY zUGNxi2pVeutpzh;-1+vpjaA!<>g)WJ8+MI#W+N;JariR}zS5&m5v0SxBEv=%oJ@|> z^NGe`-9~0zY}-IC1!C~b&qcN7-k#N&kTE1u5)^+OC<-L!glI%;l;@YAio0g920qSY zM#Wo$H5Q~SUZ#)hZR}x7SDHjFa;yBt!=cL|(cwZX@)cv{kNKmQO2_I>{VgeE!1gjo zC36$+jK=%39N5O;wO2ege5Cq&`maZ(ur|OkH`#(O+d|LL*|WB6E^q@?)Jb*en^-H> ziGs8EW*BWY#oBo@AL`uacgRhaM1p4r-_xtlQ7m$8NT<V-BCY2&cj-0wAqD1+j0-$o z3(S=SZ(Q@vp(fk`BgHUfB8iRGsLk<@(p9AFy+KeksoTiRl*B~Z;K11td@qadTDD)t zxzQ7AdOJjy8W{n{9@fo}4bRf_o9Nb~$gB8v&#fUA!2VbNPXDue0{jODxOBjhsc8hq z+<Zv%v+11CztKNS3om5~+J6In@29@K_fE=MA~A5*TlT$x_nEixzU?63brj-MmN=5X z9_`qALr-{d+KaiP-@<f4L+rStX1aiO^%X1>9s{lT?3s6nN9y!13J)4U7lyyJKOO)( zPGrp=#N1e09@fKgKoZ_LDHsvHMs}t6S`W0m**yPB{qRawZYhBW;-CnseDbyJ{Wvda zqQ-iIA1&=0*ov;Ei~@YbLVpgZJMM@5bI5<SoA>HP^rpGX(&Y6m+zw0w-}2Z($=zlx zNSR;J-10bcD5g{!Jz1uIkM}9dWwEjT9ixw2eD>_!l!}-lQkrf5+0|$<$<liD$GS8P zKwIEM#t;jIm>4aObK+43sIjGk$lfe}C9j}I{;{Z>;%#6x=n5TqDA3%w)JU`L`J1i^ z{M0=zQ7-9k55o6_{_Vx4zsDKEPRh5Nv^1|NKf8R(Yb<l|yTy-dGiOV`@8g=i=<Iaz zL+;?hM|G@hsJ>h_7UinICKqf1;yNpsul!uS2W!XI+kKVA_y+rK=<&|q@d)8(+qU<K zlSKrF9M$z6!f>jj=smfTjP9>-7~h8;`oRn^LzgM%mR$G3S(zom_-KbF%<Q}Za0yUh z2Yf=E?D*h{K16CZb^WjYH$~|p(qB7dSeuu^0qpf26aHVccjn9AnD8x8tUJZix%#gg z7Q18Bdth@OQ#(I8v;0?P16yrp=QZ?w$s;qS=UFeDuhwsTOd*BnFUe3+aA4hE4>;Aw zvpXhWhOB97f;56qK@th9XEr-U**=i$n}rzI5=%1|w@7gE<z8pGk+9>WD}Sfkd^oDB zvYFiVGF?{u)mN<GS~rEYAnYv`ewEux3~l4rDh|IqA=1!T&Py$$W>;znoWy|l>XIMc zcK@{aSNJ7a`}MjvOIMLnPYD?t#v(5mwm+M0m{sR5-)*lRX`RA%YgfzO4jqcriFo-d z`ioT0^KcPClao@UF&)eBN2;pym>2vP6AphtVB;mqwGsqpt)!F=Qy>g1DN3wz-!$@o zzbJ6xReUz~BDC_p->&f{&7CfOsK+BhtglgmYMIH0{W<H`B_WqMeV^{1Xt<6|EY%-u ztQjG%I&zk_b65da>2K4(ULh_gf2h;aXJ6%oNUo_&hb(vNI-#T0KS%!}I%cDYzz$fT z%FnAznKgX(aIOn#MBjFDTD#pOBQKk7#Luo+=eH-(SXbe;V#|k{>*b<ky;y~pQuuxV zT^UWGJ_>uQp{|MwZ*lxVYGFzC!m+?b`*Z*Hjgt7@<25sb&y&K7LAHD}%OAm#nR!+5 zsAf{E!J!W_9>`aAMQL<1#WLpC{eiO7K+qr7gooBUV+K?-l6veiSQBG{cg!xj&Lx8- zTmKujdCl$J_6Ch@o~2JBFTNlKNdW!@bBp0>f3qkt4AOtTQ5|lEwPlM@D=jZgmCB=z zuG3xgE96(%eswTXlbPMx-D9zNmeN{Mo@sqjA0};mQx_-n0dK+znE0jNVqa^jVKnD< zG<xs;(h=kABCB&XMkK7XycPeS4_#a~GD|jng3vEl?%Cv5qlOUJ1fb?Mr74!r9^?i| zP))u@F;v-+iUHPf)g7OVD0aPKgVRvui3)S3Ah8)dWisBuxU$~i2;rY+$cGZdqlz); z0+LWVO>a{THh|N5cA2h-dhgrR=ZrRC=p|!jFA<5g>9@Qe#8i?fTbkxrtc=R^1VqUj zACkGDf>Qdj&$gI<OBmrXprw5Z{DmeDcX?K9&*i^tDsnYWW9pbAo(mbBo{nUqo{M0q zXu@wYq{}p3g(=gfyrmamI%fDYVc&Iju}Zf$xylnO6HHrflXeR_dK<`jHba}Llk>h6 zv$A@q5^LxQJl2&ZEH)^mHLt|(-^qg+`XRbyIW%SL$!foB@FA0F$8!{&{Ag3-&l7=l z$K5YCxZUjK*B)CH$A7UpEqK@SsXFY_+j+3Wk8q0DL48OikgJPhG|7bZ-An6F^!OZ} zCso-+VoFQ@cszXOq#IY#C}Ialn4V448G)(A!V>tfGO?E`h_io)x`^gWcr?3`P6dG5 z@u;9^i0Zd9Uo5RLuTEw^&5r3U@HRA;JYFfq(6%fgqwv8z+E13+<@69fv-u@T`}7Vn z=Qir~@Y`x<M^+a61W$cXpV_iIFDtL~x%b3fTgHl6z&{$>!c4&9TtS*z1Fb-WD=|kE z<=t7%k7k$a>#J>1cT;n74C{#{f8sg6&ou1qC&}u}d}?gP>){?0j2P>|ztUogCFGIs zhE5zcaywwzjQ5}Fkjccjd3Xv9ECYN27AMT-)X#)eFhK*Y1*26ij>zt;O-__Ah$uo} zA$k79p59FuAch-TRU{>n<Xd-eo7+{%8F$p7Ue(~HnIBc;DEm!I^=MT5#U&2J&~Os9 z;8Na^$nwllg-Au9)v{7(RYZJjnD$G*7=?*vdF;{IHvNZ?_vHysTu$^!hPtR^-_cQ1 z-%En)s7VHCnEDpE5H4*{aD>83_C`q42#93~$El|Hj5^4Xt8rM~T>B!;M`^k(;Qj5T zNeE^D858h}qiLN!Ov_hK^i5ayl+n<@RKDk!qB$jF(Ty#kg<rekHd`OeSlfOE|K~p@ z>1hhp>^%=r2Kpf0vQW$C#nQ>h;?k1jGTort!&ZX){H|AOOjeFLcrl_U0DJRE=IryZ z4=#BURF#zIuB&#W<rJ$ZKkS~V;Ns?%S+oJ0vWpG0-$>p}qgc@aT=__pN-R4-ulOu; z!nZ^UW02kOa6_M(=0Mbg5)l=SImiqeZ9OJ94Yy)JaSaoQnqTa%9<8+!)K4mt?!iC3 z@@&dzP)L6a*ckb--ux-XpRve|8OQgnwFKXnuBK=56R56)GTSYaXs-7#Ge?3sH<&Pc zz4&!xuyBaARV7V~sFOH*>m3y{UtJiB%A4X{jCJ#lzVqyfe^JmEvPQW3#3=>O0|%=! z2Kbo<KAZBpA}W&Jk?kd}pD7s_3D5b6s^2JBADz)!_K5}Y$O5+WR|+44TbjeIGF8v2 zFp}(Eh<_B*A8v<`^~w(8H=q76^1&*XKi8Ub`AAtMmtAtb8u!pgtFj$f0=U|8re|}( zg|r*c#3>Oci8e=(=>Ab10c~rc{8k=Tz<+hi=g>s`+cdJHUo%Lk@snX6!#oc|fk(!O zJXN?4$d875L7X0K5W&UI7sz$;a&JSh80Y+n6C=D_|M8l|@+31|=_B5PQCZRts1$8L zdPzRn3t;|>P5YlPF&xrxv7|4yRYZ2X<r2fL(&1vShZ<rDrX*DD=1o|JVi0@NnLW7# zzVzS3w+ZsdQBIC>r;pY-?nr#@9opfc2e<h8`ofis-c5S0iV{?<j%+F_QMoho5xq1` z;>)%fA)#o;-Sy~I*C)F{2-u$Gd*D;|T;-UGin1)ZwT_l*dwWL$s;pD&uXe8r5PFCG zu2j>dz?~wd&^`qv%uT1<)x_ZOc;S;JOn*t1_XR}avd2J7HmZ%`8#k#u+Wx#DXrJW6 zS6jv+0^f*o{VA3qzv;i2VxX&_A?h6B(+NPE92JGz=zX@J^K<_*Z+?@*tW7QvkuaVj zwUsJ4YQ&rANA3s}w)a>&u^W-JbZ%<6<~4#ME=zb%tfEb=lth;_ipO0XdZnjQwJ>K~ zj&_|{PWiNdS~mIT1X7Y?H!WN^(0&aU(I>Jrlw3+@6(}=e&K3e6Xjw>ls<711O^ZDC zAxnI{@w-t9-(n~$?n`|5f4Kn7{|jW$)gQP`L<+K5b~xGARy0#y7xHl}Y+~&j4~9|s zVmZAr2K-n&&XcS!PQ(7ioF$cu)4o8gA^6)LEJT`LP`<nL#TzQy*KIHvPY4oHI8B{i zZ;Qzs5sUV}@fVM&c-%M6HRPKvn<!c~F~PKL*%Rubl5gAekjP{uwfTrL<<KQ?_9mMS zcT#2jlf*i%W4`H{5S;Ve*K>FiV5Rt3LiPQ9s6`%hS~0xoOKGZ|9mT$|7nT%;a^y?S zGH}j+EPU}%HgqgceRQOLwAu$HB2x(|q}=Au5q3i|v3M8#<)>Zbf;TH;@`*;}){8np zl>jIPhUbr55GJnXYa*X*jz{QZMCu7!q+|51!yPh3lB(6=MUH*<-Ng^;dNCLNhYf81 zD4K=q5do*Ix^9H&3*M)uV`>nEE3voY{w|!dMt417Rc__uB~@M@<6IB~D@mwgL-~y( z*(0vu9-vn;vz$$y@PEwNiIJh2J)pQ%%ejUq9Lk3(9Wu5TwwTJPv?<!4(W$Bj|9C%z zq9-zd@R!$UwWiEn4X(6gTVAPx?9G=Ej;WL89oIRpP!ghjyh5*u$C5Ie42<M(D;zTk zCxBzm2kUadS#G7~{5-1zUHoJY7WMnU7<<A5`lfQ{Yf;8~#0p|jbY!*SVa5IPlIW7; zlZgz!i%ygcLI1B`RKCLs;1nN6{e&{p(~%~{tj}~tDG|#-KOX@5Zgtc-1<MT7XMwR( zG3oy1)09JhM@8&FK0ea(v);C<)c48q*kSyHzp8^2ssLxUzxIuQHpOlQrdwT{)D7+0 zidLx>i)oX-(*5QI;LcO0#cQrEsP+0c@yH)lY`!3QqM{!`ra5>-W3-BYC-5HkKJ58y z9CvS?w!G#ua-*8v984!vX9{3;F}hwCKm0{y{BTE;dwJF`Bi1YZ;)S%w%Ghm{$*Pvs zC;N1=9N89Qvp~u>vmC^Sm>V}kmn2TZ&+u~&N&^E{TJXZAzkbGTuES1T%<q^oJqbw7 z)Y~<m`#^CZdPLqS1yi)rhae&%{mjbM9(-jcWN0OB?xS~e>=v~8Hk`Rv98ft9Vjb3o zgjclYpXL$TmFbaSf8JIS3=}~&Xu*KwrP?1jVfO9gX|q73zuJy7`_6~xQypW0<(%j# zTq-`lJ7jdls=?Bgjidib&-hj!G|OX6%+PV!!pbh4$2upv!Nlv4QB%dL%Okt!ysok7 z&m+H3_yiXgwi~XasubYJegX*#cr)^LyL%VeR*{_@);G*&w4rr#z=3!946HsLq;TTW zX{`^D81FoH=aeNuS$@^Nu}+f9S)|mKWYCiFzBS{;p-@LBbvjblwA@(b98;0&Ha?Do z-HbRcapzgz|5b3;6RRC(OJ`3*zw>4$!if?QYiRf{HL(lYwuicr!V|tY&DUR%RiXZY zTD$C$w;7)JevD`nU+YXFXyvZtxYb0A<mC=%h&~wlzkz^AL_2)nh|1H>p|Vn}FRq4s z?*LWW7LGRMJ^59a(z>9X_hxIVI4VuR)}qU9FXk1;r?NL(RsQ~N!#=g_%|JO?yWvXd z;_z;9!zt8IN%qd7*!cUWAL2rb<Ya<l(G}vfT}=5XC!tICXlOShg<>}TOJpsN(|#y2 zwo($AywATCF3ZY=95;zSvPDHl7o6~oX_h1$cXx^ZJ~^E-Q{BuXL)o#)p8C|q$<*(Y z)?sn(vr;i1l@Q4-Mq&f;u?8_WAYRw!2~D8;n{zf|fV%fyrb_iz<?>YQcNu5_tn)rf zp{VmJGN|<OPm{ypjj^@^MRRUkZa(SZ#J2X*+V;P9Cu}V}UVe6a@g^7idA{Zy`Y3-J zHS-GFR+k>shOBEftO;$V9D3$GvCT**S&CQhx_v*nK6GTyhch+2Nka0(X_jdpb=TjH zwz?4gB6hLyD<qgG37lsp5VJ8HN4{Yg^{7nJB*OGVmz1Uh5>TOcu4$XFvo({e4|>r? zxjn269ty5k6TXRm5wwNO)STbJFxBZB2WS-=knG?}^zN~KGCj`5J|0O@*j{q)j605W z*UXFgvl}yfUIg?Ocrc}JKG%?akG4j&bVGh8^l-i%FZX%f^)RR@<jzncvs*2L>GR6% z(t|gp_bN=KjWN<^q-#7z6kh&CRJYQ@Gex0$PMyXM^50}AEJ(=9*q1ifKKMK2Lt}>6 zn<5~nGq+9i&gsn?=1gzPAScfR)mD=;L39Ln>dO4^1A#YnKp1MuHK;D+OfX)Qj6@|b zOQoST()n?fUWTc*;Wz8r2AgcTuNb8gEAq3&nfPv4dk67{0^^bIQx>;KT4Vdai2lZO zy3Fyg{9Nw3^0gA23i7kgVWwka@q3e>`p#TW%<hwRAU!*T8BTsPy0C4@j{McqZC=*k zwET@`n2}A+>Wth=4Uf5s`SN2D>tUJ2A8Vv^rJ7}x3mX3|xKfA!)!cL;0p)mpC`b98 zPnjKmV={CRmQZTZMHz*`_t}HEV;QG>=-oNlkG7cCOdet#4RFI-sVFzr?!J;P<Ppf& zidT5tWi=yf;6U6J8J<ng4q==pPk9civ>H$mOt^2?aWF&eHZZVbiY&C)HBo|pqzw6K zNB0rkVbwKdzS^Me>|NtU9!VePZ*p)et9q&V4IIaYwPNQ@hP*TE(VlXKW+{qCv25S@ zIGRE%*!bb-5b=2)Y5yh`I&CF!JI_w(QTK4q7%wu?HQj#m@ko>=+35a*r^`lwkV~Yd z-!})SJ~m=9Bu^0oO1V<ZjW&Jqu8#DF81h<T@)E`+H6DTe?m!&%19<=k|Ld3Ze;PP$ z32u)f)pSZxhyWsG({G~*jR4gt`*j8F4bY#=TFhU^m+j8w#75P5){v^Z&9r%cT26%l zQu?|35q@!7hZ%CiW|n4JQuuwZ+bTe2JP!Nuq5RO#jI!)`(O8_xo}Y=Zo5(TG&tI^b zzUtDC!?Ly?aC08_$o9PIFG6VEOJ+EWH2bn7l^Mneo)Z6_Rre_~2`oxm?l`9iUnsb} zp10~MIPD3Lr#j8hmfZOOaZDwkJYo(C{6lmE8Yb;?`<)dC*~fzFsiaD!i+gxIo<g<Z zY2+D4+Z_rdKxF-Vb?o&OE`F0Khd&)qO?5ifvIlPD!vrDw7_y^MKo|}yua*V}Qsmom ztQh!m)R{_fOF%nY07iCX43?|CV<pYy!%;fHlq%!-WoAc>n%(aW7JC`K<e1ZRM2u&7 z({~iS#oK*_WxJo<g1y63?T8SS={#NAOu-yJ+@76~tCEh=HGV=o+Ek1JjlrfA(2*}9 z`7>?yfG9)&aZ*-%mi<IOV6hs2Q{oMbs0B|?cdY!ySIQB=_@&0?KIU3SZ=vCAmRf6z z)nm0jqPC2IUga+fFS+^X%$~9m{vt@`JW8I4ysY$nqaKBpL~{_?r1K6<GH6##T`l43 z1fFkj!NtAD!dTpF^LA9SYPLv3h3_%m@^xDrg5mmn{EDboXoDT`!!?~TvAt^aXcTxj zQ1TCs8(Ws~LzU=e@|beWzLxcWdC`3EeyizHeL=pRsUi2X@xQ}2o{4uiCv9!K25XSy zhZ*G-c#j3#3>5u#_D3CQZle!Mm>zxo+fQZiL>pt5{zH%znmQ00;P`KH7n?DdaT2WS zbX-P3wrnHglyA}VL@%pEYUhnML894|ed@BrD?W_aVFw`lLI}KT`pNvWO!*2w9Sfu$ z0Ml>iT?uNVJRh#Aq-VSyFMH->-jrOPVGi!<2Yy*#d2g5!yw$*pY$UVwzY9=UcVOk^ zoe9nD@H2EA(~8e4!QUO9vCe;%UzRc-GIqKm=kqh=60fb)FriXw)furWCGUB{|BwC7 zi6~LBN>#c{5LVwDB_S+_9!(>M#8S5MLlo!(-B&u23<q@Mu2f7xcPm<zq#bnMEZasO z%AYlANX_CvcdZ&TcH&<phFOKjhVb!O6qi`!ZGQbFrcoC)MJ(3aZzjiBv(0;t6o1f0 zr(6~3cjIKGcD3&+v>BWDEA~=^PccU1%D3fc-t1Kx`Rd<^v0u4<k-E6=O4Vyk)EUc0 zP&r<;d}<SDT%?{y2UW^5l>LJUDEqM@EZ{@k*PY)^O@zrfW4UsF7YkgrEPbg2M*!6T zrE(4tr!}#}Na5V#Tckg+`5kD`l(~Ec?z2nTm?VGDFDSLUX~>xWSU)o|La)n^boGPC zFBcL;_^gb%K%v$uCcE^bWN^8aRcfX%bx)*lHmUK8H~-CQbeneQ2U8m=K|`caGvW}m zo65uWO~%aLs?m;nj+F>^A9YK)9&37DS9t@bGwa%Co~9J!OCEdLuUZt#95nGxl}+eO zG3<s}9W8U`-N6~ocBs%A;qW~jS@dY?kQDr47}yLH{}JT)SmTBF5=^S+7-`q%evYy4 z5V<X?<4cNNwyi_23evs3$7iEXoO)M6C4U~)y(#f6NnwIPY+5_yhxgsq#LHQchoe^f zQYKMc*r!&OipLy2ge4Z0xEC}=#Bp=YdPm02?yTWg#aKu`aXuM3#?JJrmL5Mc;OhB! zkZFW^cdL9aDl4&h{l?}cqMtt?Alz=xe19cly#ZtuSP6DYkzj#o$oBqK;curwd>7i5 zW1nVosR&R}<`1;NH%vc^6sAPudW*r1LJu@Xq)d?#j^T>Q{pS(S_jfeia~!5c6)F{E z&NXzNwsuSCjZz;===IK{>eiSHBpm-^UU0u`&oO*8LaKgwnc0IqA{wZ3ArkaMrwreg z*RY(tCF1T8`ETMzQx+20P3lex<6$=f&U;>Ce|c$>b7CRWNeL7DcREXA<U|w<Wd<DS zz=1n|H<jb#Fk~{uA2F=`q1;3Jef91g)xjGi{o=%i0}ycnv2cxhu~0S2Zas||xp=<Q z@sSXf`|juc=-&>6xkbWWgkBV#@Qm?_@WNl9Uz#j`OZ-*@Sy3<+EN^+3K3Z`e&nAHV zl~^L%^Vk!!Z9DM=zGX4l`W3zN%09VThPZ=(gWKiD(Cmd<wyU<jVcT>b8k+mek-U$; zZzj?Uk15DQXW3#SgJ~@xtn@Q$*h3@o%rYnM5(DXYK7!^=DHQLEmP@F@B$$Ww+;&sD z=CT1!92n#nlIxs@+3=J(<hP;&7!poB;9MWPF<r*%M`pj}Mlx03pV>4QT#qD9#VG$S zQ0px~4H!gzKstZ7PQ9vy$;{m#7L&6;oA2vCq5!a^0bvTPgttK!rTm7`%4>;BJOO^6 zAF19LrkJZxrQek1mgIx}){6O`<qE&K0_9vzyz^Nd$nFX7ZN|Z7`S%N>CwFZU&qK>h zRe`h46GP#P)qt&Jx+E4@&<;b&zW>uPyK)y6%+1YuZe>VEx)JnAf3#D;C?MP&6m}oJ z5elH8H8i25)_Yd2DT*1~Jesl-R({8n0pxKFUrM4NWM7<P`u!7-FmkY8$dL5dD9Kjw zsF}?7_0Ga<mUuOGSPVBeH(B3_EXr8&a#=$T2z)+67Qlr((dc%b{F<!Lv0!%w?LYZf zm;P4{q}1fGSd1U|2|p?#-)xn_L`uJQrzqP@Yr|{(CLf4XFW4K|sJMx98tDmVlYF4d zy70(U1|Z4qmmL9oAn39*QZ3i}YYG<K_AO89DhELTD>nVrNBgY~i>?=Yb8;P&F(?@n z#a&cU(6B+(a_!QY>$9nF+xifz%Tu+S9Zy#j3%@`$-^E#_C#<qt*HrB(j_O6fK`R}y z0IbrMDXyVyk-+2Vz+dtBJpTIY@$2hA5wwG#Mu<4(<D7ONJXlCappQ+TjbIZZE(71+ z(5V|VXa<?Ezzq8(30?yNmk*kL@q;`9DrHak!uY2-+$+|*r0^7)I{8-L#L`#(nXmhO z+wCvrJR~y(wxrml)%{1X)l94^ALd*b(?Za0D;+L*w(mk9<^t+db&j6*6v`;Acbx>Q zU{?{C!3%*;wJ~}X>b5HDhb5u@!EH4~E0@a4{FQ?fg2tx^X%Kd~vC!+Db{xL}yZtXj zqnJfAA$>li5zabySdc616gXJ`CIPUJ?(pG{Wc!cJdxe{aNbT@Me;J{h!#)Pu^fhUg z11)4Q%XS*^evD1TG2Dw)?0h_Cf&ZNfpQYDY$Kl)APSV&{;J=$s3lM%4e%;hR>)GWP z(A;!Jah<S`Cypwtk1j&nw84NYeh&`y_wwe{W@sGu{K`T-d=JxC*GK7|{2__>b_^)# zqpoy)A*yt2s`^*PS(K?fu;F8Hse!>lix)@fGOUdV#spTbqyb`cZb$&E*>g>3BNS2b zsDIMx``36hgW`F-v-sj@;o3PDT@LQ;#I|IwQ&aF}gF|L9BL&aCmnF+^hR`Qn;jgHF zo&tR57i&D>Pce%z@0~3OT<9h7H)hf<v+pGqsw&jki*6)0-fCYERNBhhEjPb>pd?Vj z4;-*}NGx5aLO7^z$Ova(X!qrrDIKNm6BT<X2SYXIiq=I0M@76fu>S*csd2gqm*Ev} zw?YlmTX~rs)lHZ~c10T|=>LzIQgZX=`CW;nJu5L3?TrI;qTl(xU+lb1bi~x$14f^= zm(h1AGJl!x|I=i@8^t2yDVs93pSbYrp}xz-UOy(_3Q56C6H?%G%FZwOp3mq%sYn*{ zcsHEzxkuW1%bMNV!_a9R72X)f&|N^D43g^UPMyEAm^D&2k!cwHoVZRr3)KZ}Qevk- zK`@;f8jxIi*oxBWl3VQl9%cb~WMFo1iF84ceC>tY=u^g~Y9vZZPIUUG?SSeTWEd8O z-e%&0Fk@a*&)YegR+8Q&fmk8h)y{Xd*{ha?>E|-!)O*<i>dsj|N~D1|2U<VPKmg*x zPP}ug4_19r+)*L;g=(HgUD7^K9sY+%eF{K9gavWY&eh=zx}AxUO7&Lz%mtq7NWhKt zK%0cvDWuJ@<A+;^PTTfw2d28w`A=SfA&!Ys%@Tc86M87Gy%fB3X|#mO5O2Z@VE9ei z`At~ged*vMV?k4oblc%7dc$Ldx@-<#?tSpb#1Ys(`};^WltiU`0<@i3rDo4=dP3LT zCIC<;C+obhbEo+80_#BkAo5qX11kq#eKivq9>8x3bfQ)Qy3s*{8E-$m!qMd9oNGZ+ zLBX^!Fh$2fPtYncAWGq2fC^Kh%?+lAJ=8BmA+Mjh<KE#wp$L4BJN!G#Pv|6j^t%5+ zOY`&%utQFZsdV6IG;(>|v+LbEXGkD*duiJ{K6em~^NT!ydsx~aYKWmab1*nS=u^ma zHAbQ~JKwY$h)}fd+`Cx&556BgXeX^jwx{lA!2*EQ3rXR<B@?JV^$NBrt5ku^v1RC3 z?dqYKfQn%`L0pCCG2%oiSBDW4PL7<M&<s<S4AWAeX?{Xl4@4Z7b;uCt7soU$n6y*z z5L94%BY)@EBKs4kBL0D*Xz_io`fHU1vD@>lMd9`kqEi0c?_~rMUoB&YQu&rd7uL+3 zz&L2f<eBk{MVO&4zD$mc#Ys}m+~VNwEd<;*XWNI31hl>Pc-Tg-T>k6Mv{bW5%G1lc zY3i=#soXeh`mm&t1fZnU5@r;-%{;{c(T3%BC0Y6HcSIh4<u0P!KB?&Iw=<!F?z%xe zOh9?-R1fqq#qY2Q=VAR<QiPyt>U@qRC}Ddfa6z;qT%uYMBLSkKQYYNbS8SuHRBl7O zjHu$ronYoV=M@qOJM`bhZajZ)Jk`v=qDUUgqXpE~4h4;<xOA7`t21eFjy!vIVLJW( z(&Vt*PQdBtWyZkn->kc9sz%uOSNOlhx*pv;^{}>gx&j?h-DFjV-|^97B~Rw(?KRQ8 z6MG<lITn=xOz#nQ<UN=c1xA?XC5<P*4ytr<9CM}I!r^|zi2BzzXha`C%^)yy(u{Qz z&R>M;F#NTwK%%wh=jTRA_31nWJwETK`!BaI);Yc$qF;)rAMp8%??EUe{Yl>}hD1Qf zMm<6UVSLt7Qi|eLi*E@)?<$@zMU0rE@ay%l&yLs`Vt!C>V-FmbX`?t8SI<jH?h3S+ zUr|R(Qb=^nqj<;o1y3b-P_x85`ewlemX&pC^G0BsSu?d95{;Q9hYlNN$OUB2gq}<H zLuiZ4#8~Iz3|{bLf~;Qc<FQ{}N(ZVDN6)ad{}>aH5J<DVvYh2FU&oUzLhBMcM@k%e zq<#9k)pqzurJ5;VTl?NAXYg~)_|T^);`5(GT_Qt8+J3GpIFcH+On;Z!Vw&wADeD(+ zV^-1H^NRc)G6{{17YIr|Gm7@O+De-@1)^J@D(izxLzGpq17Ui&^Y!dh=;}Nluj_v& zO9D$#D7=(7A|L;6E&$ca6+A)^8ho9d+05)gx{|y7OsU+Ke%N&<`%h6cszQ{i3MDB4 zu+wej99X&RbHWc2)p?yPcN{PL=)NGKm)}udQm}5*z<_;1o)bNUd%f-Z(5KcOo}A@r zCFLpRgnUcsWvBD0a*Plyd{r6Snu`KRB&22G3@4F=^aMpo(p%X|ZbAUn@_kd?BqxwE z9cL35N%*ymKtj%HIc6PUx1aEw_2_zXzEB?vQ(h+o<+T>K{GCE_4i`2oBL}xuS)iBJ zT4LCI_O^4?!*klKgmxukL;wVvW1sqPr(}la>Os!_eMGP~_kLux3f&=_a54MlnkvK; zE2e7Ax`TPdwCLm24o5H%0B{on<%9lzc~DgiNcx<jppXEr(wW&NODf2bGll`k^&@0h z4}c!gof{QAaGTF=25}h_Ui}z?Dp>NPV*3Gt&%nWmEtdGDi35c+JFo<mE15?@+X1Xe zVN1E_*8j*~y+jiy3!glejf%%c6!BZf7fAIPPm#zJW3x)Tm-w#n`+n*1)4jxq;NMd3 zEX4LII&WhayH3iIdBbF_+gnYEkg{4@^(H{^mH_Cn6|g%^Xe~pPEVnqe3<GkW0~;Se zZ?+9l0nq3v9sLcS`3C!69D`g>6~UrcY}Bt-Wl+Fxgh=Ql)EJ82leh*mxT`M>5sy@r z6ow3Ehy;G5Y3F~5iMpll^59ZaTr$IrZczT%ogz_oAy!O+j?7q1wOi_aLd19o66hT_ zXix7{OIa!qfNzim#A-j6u=Za0#KJ}0_!NF|VZ~wyj6!L=p2DV;sQ3vFX}}R?`oCN) z2`I(E{`T!)$heRWoJLl)27iY3Rct>b`Cuj(?X(M?Wu-2MVquN)<o*0EtarBl@#;I1 z-vyONR`OP-i;LSxm<!Al=kR_eyTk3&4D4!}ecCv{DI^s9pY|*GN4E<5hk#n6>0aSN z*ZD126(e<s7|Vl!WMR{RH6g(l$a+`s;J2&fB%mfea$m_C>V~3e2~(kSv?m6}nsX~& z=RdMJccgqDB9=8|cV;llO4}{LD+kd)+&v(JU%Np)Pm$>y){J+=S>KuKheeAD(-eig zZ<yT<@4na@5YTqBvKsT;Qv`oFU0mww%sT%MU_03{P0fhA$em~u?xos|v&RP_;p|3( z)1RJ-NImh7%*3?4EP+-1iGdT&S%z{antR!-6!y-c%oet!-y95_K#;8(3<zAGHbHT7 z3q0D)5X#LH_w*b6XK%mZ#B&vueDynyDHVW|-SOlXD4>|FNM`=DSYfO^L~(9T`a&RK z$=uqw3i=lvpjHOy+=(pD)?VZ@y4`KuiolB^n+Vr7k;;z2xm^8cx0GGI>1lndrv57V z`6|h~ta$h8Zxy~M0DBcEAQXYh$@&pPM6m4C#SiX=f<<LR7wG?oh!Yjjtp|vbEKXq| zxqp8PGkiW@?<rKIHwYAWqn*>fzQv0h-jHOP2GzQeF=9+82tFQzUbQv<1MAOje64FL z%)s6AMOz<>3wDDfl`RGie_<*7QlT>aAjIB&+|Yxw(`fuP!Eb%$dd7%3xzPTXn@b~& zceXdP-kdXUY!`eq;ypegMx`P*b}V5M!%SxVwd4xCyMCQGNicPT5dx+SI&y*KK}l-> zWVt^TsGfT_91u2uC$d4@zi9{5Lu6Fw)-^*veT|VYC~~!DAm)6RbMfMQTu61&s9MCL zbn9tdk>V>pwV`t!V7m43Qu2MHAO`;O357p_f&JBxGuU;$Le=g3_4jvsK6i5OFAO8H z{7=fyxNM?JYLBgSJ3jdYhMIu}D*+66JWHSjwj`xG!?NCQK_wl!1GI^ZXQZd{FtKtR zKg=-UebY};cH3o1+||YXtZc-?P0?^!VFAhs?DVR5&`#w*?8N5)%mwF%jViwBT6O<J zOcYv^Y%j?Xp_WyhN0{PO-$wfJgTVt9Ma6@B%ON@7y6VjbaM(X?CtN+e6LP)-qFZhZ zQyTheUdG6k+-s@AeJrUMg1Y&-{gt&f!<_Vma#w(ed8tjO-~|BP)e@zrg&2;av*o~z zvCKR4tdO0RSQqGarAHA!XW~M|$d+zJ)BDcqyowfgI|&P!7UPJR@F33rzB)oCTzPh< zQfWsr`Kf8xs3;ljRxPr_TqO8fWS6fMy=`-O+53SyODB_%nwYH}W7;3d!(8u%b*lY& zO#ZRwO7)Ny7jY@N=VQC$^T^zKbSApB^AEm7t4c0?oNbtL-+lfRyT&3(-<973ocJU@ zblDw9Y|oU^mug3w^Ut+Vg2&l|q3Xh@dQc90I+;e&ctE#yy^!*Cqx>EB-D9B}H2!H7 zt_7g}@N6w*>WH2O-kV+ml6YYiv*zro{q<;qhK`Gn<pp_TSmJI)=K-cFN2Zk%kLQ)& zev+a4_AQrtho{F?R(n^Kv^V44Vwp4X%9ARfi<Ziygq?c$^MZHuyI-KdmdeXW1-w@b zXELEeXMfxh<m8Zz|1b_aD$>lf({8c;fHr=+kB2j*x_#t##n`X-0!DXsR!WKQze)V| z9AMumK#frV1Sanoal_G0Q2t*oz`>3UK4P9#myzV^fjc=>fXodYeN&OpvGuH>3Qe&A z_c>mX!oOhdy0XZ%U%MQ>OHm}i?<r5NSbz)l%kY=YW$Em@*B=&61wQ!PFbUK;p#LA5 zzB(+*?|J)Kx)((0MnF`g8({(IMi3+yQ0bDc-K9n8knRQvKXf-pcZhU%H|*}q_jg_I zAJ6|YbDlZpJ~Q{ZXQ*yNK>1ye+#q-FUWBmuZV4CkCp{)gUpoDdk2cxSdV~EkEo&dS zib+lQtf?1)%B;~d@xFax6CjP;mlIU|cuJ<_g(QBD$CYUx*fJ9p^~`7X0%Q394X!;p z@+bGXC({Rgk%WZzJ_qVYxxt!hOzKW4JwKHRc3y5Mkg|ub{PPs~OOY0ilMJb<<V|F1 z&?#%x4yCv-Li7xBcV{E{_iO%rPx_o|drH&&%(3xyvC4+f`-6SQ36%Tl*qchh6pqia zJolrOU*Y}nd%NB4hohRLwohC?)9UbhevYg(?k#N->=ki+$dw{#`H$XGovz3$vp5UW zhe2-ChK!+9Dsl=xfo2fXXa`2_fcX1JfeM~`(fx`znCRZ<{p|Ng2Rv9`14Fy*E{}Da z8@2_3{je1Q53A(%aBvKUC`htB`Q|7tZB{44AGUIu6ttU91FAtyXE8NCge<qyqfbI> z4jZj=*j{l<vo8}ZZ<LX(;xi>W_ROomscpHDp8<&VC(_(5zZZ!iT5pKEYK=F(+y8lH zU?#r$O9uNY3~+mZh|P*Kw(y^REJz_a{^$R-0n5<7<!AYFc%U<35@zFZKp#ihtu7rx z1S;$S6q5Yl+rXr&<mdXqEYKSGxKm?86!F4`F;=QgXrD|VD}UI_%R332aNyu1s@|`7 z6ISZD4O_^v6)SG2bT+J*3yy?>d-qb>!ZXh)DtfU~mwy;*EWM8#<>X+otACj7OsQio zebEq<%GEwh@kHAGu4t>pd{({L%!xgrPG^;oPSn{-II;z0KL4yBz9d15&g+u;^+&H{ z$qz1cg%>|6M*FW;3$`d9Tb5K1I(KPkluN{mIR^)33=p&z^kfkcg5BPHDNG58K2C~d zkl@vCU}uieS>o%a)Lu}jl0QVVK84HF%{Y{vRN!N{y>$x0h1G9t3Zp1Uj{InlEq%5| zW;VOcm$PQDYq7VV4Sv{I?~N66;<#C0s66<g!Wd^bMQdCiR^Gfe(7Jyco|7D#p=tN* ziwzP`_%Db8NAerhfee5BQ=AfHazc7nnrd%Fy!_*3shHZIcpJ1q_iJ)9|JK3s>tlBO zy2y0`?>F^(EuECM4hKsJ#zLNK`F8Dg#N2MA{MB2f3rw*kW^oylmPFjlIglKq=HQ|0 zTQv5aO8Mr4Mn-okZT6p}S>D1~uIxB($*m1d`|h4wmp6?|iq_?2p_Xz*s;~QZ9j-{z zMgHSh{o6STjbuB|jrGs!r3$Fk)z-qtjjgh(wA246in>s$G@8u8E;_`qve5@fyS)88 zvQcJ~2C}r;-~Y7Dk3mk^+FY2IhdioxL7xz!qDT)LNLieS4PG9CBg<lmulZaSPVglH z?y`BR$!^$9%y>)q>6|N$$VOm}V;g60BQJ|bqh#pl@EU=eia&xU=CdZ~nL!D1?>z6i zDMe8q{s7KH`bIGA`q#1A2kMx<6%xMwk}4L9g=kPq!m;ndKw$0%82$;C45k2{DuZU; zEks+DOm;<RowwMdE)Ze{bs53qeyIHVRaUs9ebdoa>ilU&s#MgK*I)rK<tsD!SUI?u zlzFuiH?c!{^%lKSUG2ej<_E@Xal`_mBtvhdI~|cRb6O}DMN1V0LuFj?WuoXIy!g#D zyB|*gA*`GCT!>y>%091gS@l-xt)teJ5W@Dr4MugWOqaKX#ov>c7{F*%vyPjennJ(D zqF`#}`r9-A$;KAnH>;d3rAme39zU>z;Eql;OmvTTex_VaF+~ym*CWLGPhD$_@-mYv z+hii9(Vn60<6cX{Ek}A+-k0UNeP*2kr}0t!BJ>cgm5pg0eAX9bRnXk&{izGAs{<FI z{8*pX^-`ek&G3P5;p-AChz%W-sP+{X@!Kc7ngs|Zl%g1!q6ASQbp#eQTA>KGqj&cr z{f*{+>CBGw`BZa{?Ww;8j2OI(6n@;kzb-TD+hzP9thg2VP!7k{`6SZ2m`XGimZ-do z(91||)vKP(jajV{>@(hpi46i+SXFNC&lFx%Q6LgHv1ktmQhvr+%kr=~(lWXPl9C9# z0r1*mR}!I*TX`4fcZs?7XuxcVH{p))8$w@525(F-#qqoJVswI;5s#r&4>2Q>QKa&b zBX_wXWs!BQPw&ttKYU2&+_A~ndFD17z^fAZ<J+@o(q(?|RIvgpu14!`Tx215w(YNg zmUUj+5tX3pmxKDB#B?jq3z09{b+fF3MJoSO#>XxC?{%Fl1J5GDr4DEmhUE~Nmogha zV&Nh89<|{_`hkv~d`l(<tn5f(+M;ZK{)j9GA}X^zNjM?JBRM3niLr}NV=*{R!9=}D z*?55R-|w4;!7;xj3RgE%fJKyyya}C3IVE@yH<zvFF?=9k*XG}&OQ)zb6iiZCdfr_m zPkGBEj;SxyJ_s87*5FWERxB}ZDD#MVmMF)Pu%W*hVzQa4qVX#KlY>xSb$8@*(i}W0 zO<+Sl1!<<~z6o&nPBo2r;3;jq{@?C)c$`}qDS!w*n%2wqxlEus5clu8uU+4Wc`M?x zNBa0{L0yZuDSAdWQpP8K17$8gV>5pPBg>!d>==-xSf5U6igrQhrQ%JpKtF+RpHgQ{ zVbvWIlM0uap)&BbLOB|zKiWTcuLr&|kt9F+v-;`l3%$W?TFdYB^qb>L{CK~BNo-BM z4Pt!YtXRR+*nsu}J2;lm7xMKSqvtmm5hf=UPDDIH_6ziFuJlq`3KL@7)m#1<Mwue+ zywr2t;YHb|upI8fm@DfC{fYg>(!984tL9;zeIy*q?Jk|B*#o2HbdN-5kbfJyOzD&5 zS<G%FPXD{6+MWEGGIQGyap1*ANR}NL)gGbopOG3@U@od2plZTy7tM4$kbM3Np-N(O zd&vx!x3avPA-9MGyj#1P-FH;%Eyk`(sp$6tQ#sq+b5yw31@}M}5?PT8yL3a2A*$Je zB6Mc%^8WK83orEh3~FN<6|STNBVVZ#e<qAs%q9OU{T(sZ_|c$0gcLDxBNk!Os+<UH zZDD#b(~f-oI*iu`8ZicVWJ32y+1!%i@Ij_S#<4<&3AF9ZkB{5`=?8i|N(VhuXZt>k zsnj3;L(@-x_w4)knEdPCuVDn(4K%zu@K{K?!QrqKj5f58d@3UL*hl1QzG}U`b4v65 zeZV{ggcU<N<$f#RW05|f$8X=cu_Xy9C;X#h?)LqogNccf{n)c-KtvNjH+Gz$Eh$kI zsP{CP9|TmBYI1Yx{XoP+$ModZv*udXIcOdLr;TaLlXefuAJ+?avz3dAVxMIEkoE@B z>Aq!kuZsG<A}1BDb0ow>`aNhF$*RUA>cmfGZ7Is#WRmXa%*x6mocMk&l0!c_6uklT zhq#zt15?{{MnG2A#iJOO%YXA2V>6)8KgR5MW#D9Eh12*hgOoS@Vj_O7@Lxv9{eg20 zhfVc2^M*F2tY3Lv20i&9h;i^yQ)A+CUHZQz@(T4Q!sH5vgd>3oPY0Fq4E!O_4C6Y1 zr!VD=zM%T4_(Vx^;`TrN#a@>$z~;HKhG1lpr!+rD*x?RP#+>4F-HN<>StJ^mZ4|T< zz})_jEIFYN63c(!?uh0cY1%RVBV0<%$qt?hQ?Qg?T)EtT*SfE{+>g2a!I{7sb*8q> z0I1qfSs&fgc;`jTL6}{#!!N%>q{BrEu%QN^>`Q={9Yf3KAUa*z*SHO;cFV$o3rV}q z^D&<vim*~*qncPcKGGe0bWpthU@?<`{<`JmlII~c-QPC)K+C9H18O;;<$#%n#iXhg zUspHBu^*l^+qvx9RaugHIa7=&I6jje94Ywkx+^~&3ig7Krh$~va5oY_ugd?-va-j- zQ_2{_Zni$isjB}!T_UEJSR>3}pqVNK1Mp8`b0}yat9p>!*kjLZSLy-9M{8op_(d(T z`;CdJQ*2IXt~KbH-iUs%zk62g`WgqITe^Olv3RFO^qeTA>;B#Izrz&2tu{aF|6Y}^ z_9#=_>VvvaOj+dNKo<3lrCar=!NPIs=X!QBOLG)z?}rsM2vE#(N-AWy?TQtpAm4Vd z`!pU!4t0x)>MhF`I*8+3z9exk@FzkN_v1wR3SHU;5#lM<06!@qg2^QzhQv4Pi#aJE z@>iw=SXt=Hl|m8B7^BDz;3yEC-(hm&s15^tcWD#D|9#DDl+ft{a8~Jd=hRu%BdL;6 zs%YP&uN%TTK4Z<=nNlkG(*Y$srBf1xA)NZE$`l)?!Iv_Nd(nyhEQ))N&3x+~(BI$} zB1GUhxW<$5c1hx6kQ({b4}hZkyqf80P79}33ubPq_EeVdZV&n~=iFx#X*^Rz=;3~p zk!`Jn^yi~pA+`=ZX~CtmCBn(F|6SE^p6-i|3j{P~{}_}~J0u+Uu`|t0D2)I4tBIoQ zJh3oMZRTwE-ES%HK;wM5XInb%Z{E|M;EFtFA>3l?T$z>0`i%rcp2(!W)_$jKUDHb2 zOS3b!-<UP)9y*h=FSf;w-%;3Lp_LYlU+|cRYnIEmE)w3?iDS$6@=Qb8k{0>%v$-p| zS3VlZ_Hc}zehJfKgYh47;OWLlv5!C6b_Aac&en(IT?PN1-r*163;tkNzZw^DsXG)d z@0||Z_44LM-i>TB_-o}vyD`>a2yjF=;H~vW@=Qtwk{lI-4)58&k>LeV)svx^&WvFx z^OQn=_HK6nywX@hIl^ef>rg6-Q<I#Sg5l?mWSYVc67Tezw`k_nYaN!=*vQD<c%d0t z;v5}rM9uB#_rd34YvRI3lvqFyB?j=N$BS^m=bS_tu%ZbCG<5?vGNNZf@y$)h7qT@v zMFFcrqZy`B^2c6~*>NP}dLjCZv$@ss-$h^w3#AVsLTsfJ<?@N3OuzNCWqoHntWa=v zDg5@8Fzsl-?@N_m=`knt($sE&JD$qEe{B_CCar*-1tVv>3El)unewq9d#@P7FqqY| z&+%olYFz$S+cgRDdE#g+-9MA?-L~W%yYEe9oFWE^pZUgN?ahxqsc73uiOTis7;?h? zY46MZ!Sr&Ue3tw7e;uag<99aA&+!6z@|Sd<S-Hwa^IgDzSYJk?W>QlBj6|AP*%9<0 z>wiQQU0hT7PrmaG%KvNL6mk2swqzkpA$EFq?zv)$#<P8c#v56F0ErUM1No9)BWjB# zRl~4p%gMQ<LcYt7pZx*uG#I*9{#v{szRs0c0F!nMQ>=a>_*3VeQ)bJ?hi~{{y4i`? z{WV7*moQTJVa*UOR^|m#;cg_OY`MP1WD2oL^UE3baYk%}r;LEV4gGXxX;<-1H$0$s z3<^?{?qdU>xT){IO_{P0bW=dbsnOk6eBWLPy)W`Zl~|Jlh@>B|)tG<I8wnhhyKmu$ zEm0K~wxxwEb#X0D;`O0Nu;KR!tj@R;QPpQEXI;--X8!GrD}CqSFQUh`Tz-3k*y3rL z^VZj47pFx>7<QE3HMjHKm`a4PZRfYjEBZR<<uoI`ZOu;NO>qz=OLk<QCQspf&tIk$ zishi7z*JndvCv<L$97Hjr}@LRQjQXZd~GXya+!%=u#M)cm1BKR$;uYyNR9;WsKc&6 z^d0rD)`u<;^nYOgY5J*0Xjr38oF*E;ABx$2ohjita(>d~$KeFs#uTYM2You@dem@e zsyv$je{2898sGl4VC=iCBhvQ3>!N86c~VC^A8gtD>F=92x6`B0(?(n3RvN%n7CQ%b zT8AO-7&|&;Nwox|GziIG!oKX*%b0q>d<Yh3W&{SLxlV_-C8_X~$RFrBu~(kEztL4m z>7U!PQhmN(%`Jkad#OQkYx0a+^30y@?>&}K%k`wyuF<^brZwQkRT3ZK{4j0lY=<VH z`-c{g$MvDzwk6V`IqJ%=_kb?H_prDvxHg+kVDyjc7w)y&5co*(!4#?W+Q?7Ted*eb zZrN1ocHgMjqTj&oE@3pwTD?=pRTiP19pCshzwL&TA6TV5UO<pycqiH~E3AmCXtjIA z5|TDq<o@s<D8S(Wn)d?JRuoHM(G&ou3y!lt*%oj5*B<wc)NS*6^e0w34gB=CsFw_P zTb7kJgKGM2J)s@Hc(Gm<@=A`u&zK^Q{zY2cXY}sMHm!0b#l*=?pW&InF4XDfYo1ZI zp}}J{Yq2!>xqrZDyQ6~+U12yw-<5q|MkpGykp0Bi-=X}Q;4BQ|)th@=%@)twq^GpO zsP0iE9@;FHVofyqYDru@cD)sh{Gc)4?BU~4kGHUju?Awo3m{b_z_9*Kp?as4b7M7M z9U!OjHOK1je|G%^ukZU;S(AO^xmp!I=DiDx$J>q3$dP*j1!7zp#eu|g`^YNss<_6| z0k{sbq{PSji?`6R89Z}8H9do5dioN%d8ey5p;EKVgAt0an0ye-o=&7gRHKhU+WPZi z#xeUleC`U<KpL-Dh1mNgGsoh8_XUDI0Pu`Y0(B8cC3a4?bmgyMejXXft5DVohkn^0 zdc)}RRJJffu2|{Arm~i`1BM22x6lb`3|<*&yVQuQsC=3WI~H*jcBgBk<qza=KK*me z<9bG|LqKSln_UX0C0V?Wn}c6t*3P^;@0~*arI1UvnBc=J$=#)x*MjdZV+Ru1T+YU` ze6jR<6{n7q5-FKZ<jXo<)|t}~1!H=A5bgQ}^BOj}H9h%rQFwJAj3Z9aC)nEkfdzJw zZ`v_@eD}fuHoI)iB?!E(gEO7Hsy`X%sz2H5N=mI?*dqQA@}~KLlprq9vrhqz`Il-L zBL01%y++t#Wj@t;8&}f5_*EaT_Z{{SQKmHP!bo;9#EbL7xQbGoq=i_VWHF*_mcDk# zs)OA&m1ANx{%nw{XYNqP2d7AedBQgU#B#E4z8`>lN^Z%b@EB-;{xnAb6Kqpr#z5G2 zm@FpW`GvaJ(x%|5e(ItA@P0WZufJ4Y+ldaorojt4eX)e<FqPIr*Isc?904{?I}M4g z!Rt|eY`^<pg4Smfwl^J=C{fskCF)h9keuao%{C+ORI~P|<(c92k_}18Q%g;*5y%Np zV?(y9N@AQ^o11G1ycSAk_m092XiR6MhDx9{89-(G;vMcyCO}ZoiS4(q0Ox}|5z{+- zKOjk4kIi0A=1y>I1Ixvh^)R7o6q>OB#!mpQd&kMW>kg!^3l5V;E7CtVtXrL(?cbm% zCH<S0&?<9&lj1+|ruq?@T*IrI=u5c8&6G?3aUD*Ph+t43`eu1$kr7QK<}F+&nH>yg zQl@W7?V2^_)8zh86|oMj?QYe+{Jfa09gCwmEhWk>(}F{q8kqQke2fS1qs9lMbrZ2Y z2A(MmfIcxN$QgGZsr)pjX^m_;oP+DdeyI?f9>A8kuR#aR!cG*nKKD2hXV8x$xkhli zHa6F9P{-&vH(XfG+7xNV!&yaFMZ6~Zf$I*_Y9W*Oo@)5swc|Ly!|V6xQy0mn?$0L! z;^kZrc2{K=rEZ&7q7^+{8Tx%#Pj~_5FwmS!uxZujj$}6+WgNv(Gc0*OGj9y7Du233 zFW&T-1>5GUohg3Pu1PSez+{YX6|hP+Fo2^Tm+)jm4@SJ%P#j|a5ggAb8oJ$fWxBf4 zVGEw(T2{#w+6lEgBl;2<Cb}>}@RVsdB_P;<7FTABLV9=_Fj{G%z6#^O{3~~Kfq7!{ zQCM~ME;sbQ1C?N|B+H*~ejf22I$bG-IyRvquR85*5%Qz0|BJWj{y;6^4rUHcUsQ${ zXjJlsu>&bTvV1AWTA0z5yp8&b4T+%Gt`=cbn;(nU>UkT8UH8eIKkv~0B#j2gB)zV< zGroW_K8G!yO$XaQnj-oeNU@h&&H!Y>wMWHBjT#co5KUkAO;NWyVUKl{K)4oQ8dfwt z+mATQ-9DMh^ix*jRR?S+>U!uTz3;pvkke>@fs(|a&-Gwcs(t8W_cM;$kRXDhuMa`} zXh679!*$^?O~S-m9OvRQB1BM8Jn8fi^6cPg=Y(V<NO1rVX(L@?W`1=Ju(EY)p_8kG zvW~DW=@_UiewuaLkBFAl9^%fp3uKY0fit>G!G7gzMIWnRPB?C6Hh80OWF7PV7VH=N ziA1{#7)23aVC{!!EH`%ERw#ewMe-7<wB9+Lwuon_i2058+V=dt!%$paTPaBKg>8$V zvDamX7Qp7l_BjfV0fN<fWcX5H<A(VXlQR>6)3!VHA$_iK-GiZ7TxoLlxv|)pUwDR3 zL(ht}5Mt_Bx+dJR^^S3+;uM>oLbj*aj1GCVor<8b#hNNx`n<3ufeXF%%0rbd#y0$^ z;o#w9w$c65KKlif&SX=goPiTe=xL4-5_<<cXR3I2{q3x|2E8f}+wgIa=%uJ)NhkJJ zhhfXFE$Gg_N7*=neX;C81;#K&h`nMRE7*_0Wj*o3Iq!#{k+u`AH27-jk0(@v;Ju{t zuqU<*JPEam3Y1%`fTAt>eYaY)#b0KIsT|JeH#hhNKrBB1Sz|H&9`AcJ+Jz7oBh-|t z|IZ?g?^A)rxVbHH(A&DD#QIy?h+b|wTS>$>8j1U%6KW&R&DCk^hnHio;FnEQqL&Zz zfSWmF(vQxjH!qDbxydx(0oe;Msfibj$WCKd->gede+IO6kHs+K_<??>c_R%MW|Oq{ zdWoNCAYRC9@u>L?$=8d&?JtW^X||3{Qr73QbkhSa{kR%K5Q*FFK*egFRb3fvCy8^8 z%+4`djdIDNu#_5BvRr~pja3)&`$9`gM0`eX%ANll;>EC-D6sb)i!J3fIW{^#5a0~< z<hob^0w#V5@)%7A?c$z(;}m(77|<bxLF=K@a^1A_QK<5s)VubV(eJVR=SO<XY#Krc zmcSGjQ>89p>ag~{DpQnNn&-wVo*upr<WpfakxBd0yqppEQ7^4{$+0!nA%r|i$<jc; zt?%<1+@gdtR=hqWwhkytOaW?6+>ZGgGfNZ7t5F%GrK{@AL&#;<?kWjP(tk##pCT73 z&spi~jz<{P?(l5CYbVnsB}Pgx8h~kdhKMq7u1ia2Z*-q?ikQG}2t6Op(HK>^lpn~Z zhDWSx1sidQSXA%0i%|xhjR7A@_>5NMQnt++AZ{@8?!UV>KsVO>H7GtXZ1w_MRo1h{ z?x$UB<4I^)QfVR1e&|mks6%MY!A<>0g_Jvb@i?@Opht#MrQrxAgaelOXOKQ!7c1a= zr0aXkFBMA3_`v-KocR%3vo8%SAleEF<lB}O1$O7h>c@$sMK;^>v@ZQlY<g^#fxMs> zqr{W%l3&~y<BVm1p$BswgV7=yWxD%Fe5`y?+PgT_S*~0hFLo)!7b3+FMd!7o#Dwa} z>;YM0k9(h3$=;OgBmeZvKwL!ur|<7&OQ-f~<h1dOd{3Euk0%-XSTm8>X%8Kl<_-sI z6M<sCT|G=b$+DAAviGWPgsW3H+1kajLyi5OJZX-{WxDuCW7;gn9FSs2^bP~>qL+-9 zDwYCUSI_)R5Xb$`%3>U84|SF!1aq;BaOIATv+bh>z4?>xgw;;w4fOLt4ql@gs{^)C zWv^;H_b=;&;jN9C2a%T<`)zarez#Z!+?nmR%MTJ_-ncxK#~<t#J*Y5!96Ppb9pBBz zGq9TT`Yi;cG{;?y{J57^@z{9+)wv$+z$;6UGh%+Rp{9c%A+$Lko<fU}XVK#@F>ef> z%!NWosPLx<-tR=?PE{#t8$ziAa`&QLy7*>-U7<+PMNDz$$*2e8=2Ikh&8bMK$}C-d z)#bVHO@mmQgy4NSMak0w-iP@(Dwx`{NVAMkRQmmIWI>%ppV~Ea-@i_P^CkwWS}T!9 z-&^k9IEYeW2bmqh4WtPXKiqDDm2e04z-@fuJ4SPEKr9X^A8398NZQa4|11gglMbo9 zyzKEr3l7F06i|QyD>M&7Yco#HMN3Y25q-_`h@4c7O?I`%58q#J(a*WBaN)DbB4_6x z7QPDXm#}eSoyl6pFpB+nAN4@}7_Q3eBgU={##^cJ8m}fXE9NW*y+EFAIrVtWt2V6X zL45fUb*45Fe=mie*+TIM-j85EO(4vrOiO=4@t}nRWTn4j0&0G*{Qye#_U1*kxMX)e zH}#8JMZcksD)?P&WmAdDE-niaGb|q8%Ac&?&P7usk7T$rSnFD$TNrf)OY2tm0Vr&{ z<Br#DhDDcPQsgpFs;jH*(!=S1Hghtr=tH{{sT*o{SUpou>cx;%>e3&Z0*b%E1K6aS zEJ4n|o&|Hj#8f`BKB8#?K28K70i)fXX*2weDzDepO!}?&X@Dy`2P4uLlaCNO6!g5^ zp-*e3<um0Ga;{CI^``r!rou7>oo65JVWc}Q3BYhuI(AkXRd3o=tblqdbu+}BCBWYZ z(fs#5MeN>unIq6Yiz4<5UpiLNT3h^+0D^&?F({8PcHx(3?+k<6@1(t;of5R<UOM7X z4z%-}bCds$+l5+63Iq|WB`D@<M1VdDj|B3I&?n_pJ@GQ2u3dH#K6*ISsJ+#HrBDZ7 zVdUgu>!=c#@7CqphL>r=P6QaxEv7PVuv+XQL;r@blkX;7)M)pdG<tAvAjgF-fUlTs z9H5;eBy7zHiA83%0YPt=q50@FHNdT6C3)=a^OfyBI>C2;(N1>HN@x#k03JdH`JSdp z$}+Lm^2zC0%xe^5{iP$ys41%Up!&Y{zVf=f?eERUCnOoA_Dg7kthJkG66pM8O;dyL ziUJFA=WVtIoZGRqaQGo!5R*ORFdlkxZc*+B07WAarc-eF!<5P6ssGOffKLFAG(?Il z(8*bM?p=U9XmyjtvzW9|Oi+Ko$%+&a<~2}H5`OqVLC-k(x9(wQbfwp$t|8<fpZ4-q zi|2hGs;uiq6a5<haP0Ewk&%Xg`x>^9@9MwP^-?ZKSB&-Wa<T6H#~n}Fvq>}QGGjuZ zSpZinIcg3aOIbLY?!ge+34ephL=Ii0X&>uX_`<gF<E~t}mOqS1s^BTcdf6w^31<o! z;TeFU(z^R%VHgMgBT^V?AJu@wL{t6Gn(F&+D(Z`d?rWj&(@8}S*Sgc!r=!EjJseDZ zdGGZKbfJMQ55-s+m1~@bt-8lm;FC48E2UxqE55?u_&f27CuVuh3J2)Dhv2LaCGBCI zT><cZ0RV{BJ~`f#8dh-oqJ!2)1MKgL7{`)8;k&=u*%#tpScj&rPrL79GWO9kJ`^g} zD)vphAFX{`k0#HQooy$n7WPd0A%fIOF*Yn^F}Bx9Vd&YUY>Oius%E24JrYd&4W^aR zOT~nl`Ppu`m#RgHl}lVIxWPJ&g~Urx-0AoL<a^eglT|2Iz+WZfpUQ8uW9}wkA)U0) zmk-kE<Ls4&?#*3gj`ir=`fJ7qiiw5~gA!a^hhh)v&DW+dt)7%rgxGld_^5UYbwtxN z4i~er?^eY>Zw|F##E!W8$a_iNbhc9ZPcv7unG??CDu4t+N}Ox}jf<GL*nB^tXAVdr zQc%BCFfb^JKtv4pccqBDcv&7rywz9J-HP!4EM6;AsX`oMb3nZG)A2uQ_ipaiJ(gjN zOXvNgxsLa(*b-lT;z!(8xft)ZJ-0{ELVU(8Kd2y=+e1d8(<iO8)dB)uS1$%`PrHww z#{fSXe#Vg#wwFJ!GBX>U5YmE*)S_ev7_`yY*OuGoiHdyhy5QZw&!NZ37a-0yONT<K zSLA_T5=(m(cc+B;RsF>}122+nlvNyP9`7HnwFRg}+|woaWvGbfa;PCUb`4-Y)K%l? z#|=AJ_Inn(gpAoDX?r)Sy^Q;w(!|yiO>96DtGzOyvaIo-9<H33M7^VZn^6LSHlwlJ zx!y(GX*h#8wUX(Gf>%IUA2LM!D-Vyw&^A^8ENfT?S{D^$dY_Vbcw_zjOqTEFeOL4X zu0`^Rx4*Wtz^W3DG4D4rFh6;aE60Dm!VzpUOZdgABp$GvMCX-ssYqdlJB$!hA7xQL z3h81y+;X3P_fG4}H&osg4eE8Ji>1}DtE>;bH^qsR6Wzo%aqOcE9mL1?_j@Y;jwhHu zT!((m;4ft^{a9rbI`-@&c+48oDS!VQyrL1c<MR6Fw&fc}xt%@vJ3TTX(9a-{B5-y# zw=Om3^H}Vqp8H@W@`@k&^e>lBxFaG5#PF2RT$^!NhofGdbgP_eNr=^|f%ToA`1Sfy z6xvef$D>5Rtq+ytok+nW*|$RX$zpk~2Ve+Z@LzQF5_VrimE#O5a<7Q)r8u+^G5X1r zaaKwXs^1QB>@OSBCB<o8_@&$>tAc1T;xAZNJ&_TZn)WrjWO!r<rIy!J&x7#32ZzON zUtllZz*#S)y0IvJfuMH3*gei2`M8l)K_3;f#CVIS5Gj*cT~tqG_&j9{-b1dxN5GX8 z5^To(hl_ScFQvA;h1bWBj2+qcQWzbW8@L*|Jns1U$<8`oIGE~7>M}>%a%`dEjY7u| z^ikl>-`YYiaf)*y=TN1v$tcOWrMJRX9RgCK;qwo4|Gh=7GCpZ(WVe_TIbNVADp&(Q z>O*VKS*3(HxSc7d-yM%?&<Bz_6g!@wW}$)zHenC|`ML@pKA(`<PXYbUv9=H$A<O5j zrNl4PIbtSiM$5J|uE?F^sJ<^Qj{J^LRI=et?`Ur|<gMghEs_0!?H0>;S>C}KWxh2L z_UU>qi!FO%=*536scUefZ()V5@Jq_oq=pg6kJ~rze_*JbWEIdM?!?C*hU8LG$!pNY zIHaX0OhOL~3<MpG*BY;)>KNurenhUAXwi{E10eHH&_NyeEM~2c^eLl6bM6<^>Q^9k zL^>Z=I`4?!H<?*$XOlm>lUejXT<<`TGM)Bu^l{Y>PhHw-(vk#%;Xb`4i`N3RW$+=f z!Tfd1XfaGp`uFs08=mNztJe<=4X6B+Y3y;=%h+vEI1`NeBAA*BU+jhw`ECenLiJPL zeLiUZZILCKn$?ZNIn~u+^9cW}yM?iZVm(C0l@X1rP^%LneB7v@ZWZ_$JX@fJ3~g$C zHrO*WB&Mv^hfj0@65k&U>JQe`Cwrnjiot@TMcYE!Nd@S9U+3OW7KXH3R6uzB=bl1y z<g5W8&oud##W)GI-XjsP7QSZn%}K1K^|#jNJYFW9?IbbKFofptKoGF`nYBm=)l@(| zOj3!R&9RtF;&8>={k;-edc|@)=T{LKVyoh_>xD8>ft9ycRAYcRu^&30K1}>tm(mmv zcHfh;dHEWX=@|ZO21G>&gE|udK=;XMsJb$z3D5A@>CnKA*EG~@kS68>6NCmd85i>6 z6#>s=P)yePEN2lp?+Zn``@7?{gIxIUKYG>Dg!!s^rjx(3O-yqIU$d0nLn00znc(}n zaN*O?G<YuMK^jy|y2hzoLR{oXK=>u{Rpk}j>MbOZq6`GAbXK5v4_Vv_#GJ8NP6R_) z`$C);o@V#UyqTLa@@Fr_Ysxav$WrgjN;2@gyyb=w6t<pk$~9*Y8@%3ZyqASWIt(v! zWieGqcKkwAg-PKRKgcL5mglwVtRRY}HB+fvzu>-P=gDMr)eCW+M!)=ZcRY_C%z4~O zzJ57KsK6Y1_0&p|600aYJv4|%5F^+Ns*fA$ZWB}uO!}L=RitryFu-x6B^uzSJl(LA z@*f;+^O`{#mQMZlvi0<>5$xSTdfy2QW4X?art}Ob^ok0QBi4B+2Qqv8g3{oziIxf` zh4Et|mzjC+t`o*_?TxPQYjX_&q=J3aMZ<{fs_2Cf!62Z%pHTCcG~PZ3lWza(hy#q8 z&)cD3W0frRfvkb5IL`wQ7-*ftv>T`F&E_4>m}q4Ss-<ztCQW2}JWrT)r&>xC5A-Go zLyE&5La4~BMJ;LT3&&cwF?*0Bi(hPX!a~s?`1YtFvIb9x0ia??;~_)f6)q<iVph3G z-DGAJV$Xm#NQ5=JFcu{Ky7a%epfpj8z0jEz6WRY6P;y@7`S57K?I3NO-wX!vL7QO1 ziIvEVqC+Wtbq+YW%jUg^CT%eqgY?PfsmjW9?8pOVJT&+Zn9*y(x}+4Pr|F0m)<OH{ zj|0yEUf5pDxi%I!`K>164LDtBN#!e^_6tp5MLVg{(&3e?0mR&?f=iR*v!--ec%kGy z{!@8wu8#e=to$r4++(Y^tp*W-zzKAwS99|`A&*seSrLAC%MlJy)(KS)&G`(hBo;<} z_)cEUgXN!Tj(+xn7?B5mf3m;Dg|7riyx2yI`y~KLI;Dv-XBLRDTR`T8x}^-mQ|7FX z0Uy?8x3Q}+gxBhH0XK1jzL*M>uxcjUFU+I|hYR}<m3_@d`8a^dO;>kI=fQv4DFLi% z^?GhB^DD;7e_29;1aR2Q8-icxOj>a=4rKoo$7S1hiY0hX{=}o#lD=#IMhW|6nWYt> zs5R$Q@~m`!BdN-<7|_lb?$A421ww$P4e@Tp-JnS(OTENRH4R2hnQ=47=1ELjtuqBb zWWG2S4Z6Y#y@w@`zE|1Y{Y``e+4F1zwhhj3$=I`ymidEy5mFJKJT<v8xYW~g6U>N@ zvZw$hG(l$-`hzMaDS%<Z35#?m1LJ*<EBTb<be3-28Q6uj!y=%j9c<W4(J$SIZHqT` zXhq-0@Idq{(K2ub<FMs8aKvXS-qm-PH#_HJ3K`<Lq$v9NbKG|;Hr+Bx@Hd+2H2$V( z?v)iNZq*Ff>j@p<3%wabfrr`npDCb|Y~j{ZHO>HwFBP!wXPfIUPeJP0nCc{?;-(M+ zGlQ}?95ecWU#BWOOmcztn~%-<eLO~faaWQ_$|e0Ay-H~n>Ay>AA~&sV|2&(4h(6pF zi?W-2Pbwh&m?dFYgG6d1`1?I!`IVC3mF$G+d(Bs-$@E@}m!$8fO=D4P`uA<+C^)OZ zz<(m$ST@sV3x(E{r$8G|@chNMt1H`zLR*|@Vk|?AvFgn1U`|pmt-}B@-pcesdj%E& zsJfnzInp-L;ci9hU1O=%<<<-SZ`9&I9>-x${g~qO!^G0xBbX!|2^V_ma9zAgA>ME8 z2`B^c*WoRvq_4BW#G@~`cOxE^YFBW7{scjmMX27M4m&DQ)A6dYajmS`+U)(eepB@Y zA@EU4X0DQ{9r7$_MG)f^OO|}#dP>@lKY|o^Hsb6lh2Qvkd4%7&(Fs{`X&#sRiCnZV zJHFvD@538Piq7CO1R-ADwL8NI-!Nm8>%tC^+Z&kcFO0oegQ$U(nzf$4zt;=X3*_zL zu_%Gt!u<0nt71W5k_Qgk^RN<j=7YmXrpH~_F_qO|!1Kfv8T;(wXeWIJXPqq!ZvSc% z(xP=-$P>+f0^@h*i~bnBJW{aF_)YLY3Rx4eKp)9l>O8T=Cj=yVo^!nYE8a`4ty1{H zM|_~q7%#RNL!Um`Ts_BAcF9^}a!vR<P7W4TbCZRCx^LQp=bU7E9rd^w(kEATpY$L5 zYB-_UswkEB5+4y6*k*v4WvVNV>r*VVYGb84-VA7->|Yts8k?n_=O&`2!BxO8h{MIU zcy*1!#vx&-6_3RW|KlV0rKA;+4tgaxhwaJ4gz@=YWn{WO5dfDI8HqVyWu89h5m;{Z zYSfqH(B72-NecQ%4I&uSiwdv?)toJyI2QG4n>^N_b+uZ*E)?LZ7;`X<MUv%W@0>6} z_j46=%h>@-EvE{sB3V@eu2Dm<N^%?ZPC@34*TT&@nn&Kr%0@&Ux>S|ElRLuy!%IYz zTY@xt{%ZT_-DJO1{trh4?zH9%Bh%*4$++WV7^+Z4|Jv-g8OOiAPP7o2>Tc*W-GXh> zRwsUeB!rIokB$~s_d$~8ikO<d*|s52L5~++qf90vC=DVML63X1iR*U5Fn%9eG2Y~| z%;$>6VPo6Mce>X=5<EU>Fjc#fwCIZrARRu-eW@RRG}sV(!?zNhcZK_ytMr!y$}UCW z8M8I$vO`l&bqQf9KiHV{SDm}WECEDqI{e*WKY#&DH=J6am6Sy*-1p5_(kxJ&<C>z+ z|3iu<&Ztl9#y*vN&Qp@Ix95FYJGbZG`1@9CIkHT+#f`cV9vG`x?#K_jd2iaTCUqM{ zgZHC;*sCJ)Cxp-!LukR0)mZuL96`&E7&1zs<cD89L%8s~2fvq5)=F-D==4Zu?^8nO zpj^}8r6T|k`hGYvjgH7BSW45UOtw;p|NFok8$HYT6DiUaqrVDi5_uD1*x0-Zy*iU` z-?fKcwYLyuIVkMUx$nQdyCybJn)e&^Ic*(<5sDu*b5ox?zJM&8UD+fiuUGU{JThpt zP5+_yJj%ZeMW%&jG76^zCLc@ro%+;FxOQ0AzCsRa#y+0a#DsuU<pU8Dpqbo!GlhPI z&B)gVac-NqWhG6BEe@4uChOwFOy%QrW8ihxtI5K90zA`K(yYsMbG2U#vvp{(Og8Dj zkt=qAiZ7iyq8OXcXW@+LhPD4ZFsdmi+)@h<VSM)aTD!%Sp-iEs?KKpnNl0ysW&`=% zHD2*m1#)l+`D9{-&w#Zto(7%@nt<X362vrpgm3=@v)!}o`G3ZS<)6gwrSK5`yQ-Yb zOb-NK4J+~&#jy1=4`SXPbG{!uZ<n~lc)^{u8^o+<Y{;xxXRGYFD}a;fA=pA}V598e zx*7t8(OskV(Cu5uIsq?kD?Uk51`F@Ri=+`-f9I>1y3ARO6B^uWOuWp%d!y;Tl_#W6 zdfsz0y%?DbmK5Mfq82+#uyMAV@WK+_EyUCH<p4uT%y7npxVBUNGN*oCTd<JAdzh%3 z=AQYhm0lmPHx+G6@Uc)?`KViMSkf#mXvl2=UzT{_8yiNgk3=Qa|3Y{0c*bVCtfYMQ zC3ZA9<NHYGi`H02ntIaJp${rpCKo5~TI5Yl7OBRSBvn&%{ge=?o^3G?BZbs$1Ha1D zNpi$QKUsXz+4X>}1$WM!@{d>u#G=migAO&S7ia(|^X9Get^RevJ}j1Z#=087$EsT2 znQNsSQn97uUA!Lp2#vFjX#a_C@r6;gD9eh~DV2hYyXYS;TeA+$t{twtiI-i&a#w1| z1ii7yNqgT(Bpm5ef7IF*;i^En*0EscvShtO-!+ef{8{I&N1QxcX<$U$)8$Yk&9&uG zBc=%mDx{1}_f)Wgf4^V8XYH`9*8lwU0UO}goM;0!2^9qn>&VKmFag&@oK4@dDn}pY z^nJgI`N1h)%Z`pg?2TH^_w9c8;a>9_|7C@u%Bg@M)Lob?7gZ;pf8(j>>FiwfF}soX z8oVPj(=dD1*-d-<Cw={GSKBwnJH56aZ;x+yKQ(%fJsmo<3Ic%ri@4rIxvG1C6HF45 z$@c4pfm;qP$SjgO!<PZ(Jm33JbCF5ww?3+JJ19FsJp4fOqF;)z2l#S%9r6Rlo}XY2 zAdW`Xrfvya{yEf7s(ijGX&H#zPGHez4|P2TsFF;WRon1*kS2<2`)z`h?t&OySTxGV ze<D9)xQQ2FspOB1w>=9S^|SnF+CHuPkED&>!}nqKv=)Aq`TOw7CuYE{7N^RK!|E2M z$^~birV8x+_2IVTvYWxw7HItsDc8>I`wU&U+nV1oSwxgMT_QzvgPa)JU*Bf*zy;W* zwT9p%6c5<?m++1i+z%fxna<wT_~yBsXMp*(++I4VDE6<6Mz9K3(wY=>rO8qI#`1dh za#~5f{X!r3J-Pm+0Y-nz<LE7NwwUMmYGJ4rVHWtR2!ZT&;xU(zT>QUkY+QyLs--Ix zG`6tc4P!34k+xlXxJ0q9g)-}?r?-VwBpHS%o0R@oIzL#^K1!y8`He<+3$Y^BsGBBt zI^_~=gpf{JFyr(zJ2<#TN8cx;oPT4_r5M;6-M6O@I(O`-l^R!O%WbMvZAfZ!%6IV| zb#!-ZKzRt7Vh4VWeX1cD)mqn#Yr>A%!A3P%TIGbeCmDN-!%h{{Hx#=L5KUI5AOyI7 zv$4H><LYZDC4k&5ho2cHN!;?4xbR1Sy~*PeN1%QGj`}W-@A2GQ58s|l5QjM0G1i@$ zEOxO4UgYuVCfDBT=0v71mD<CPTv%@-_B4vyrHO;%MrE*U{G>`qu^?Ltktn&cct?tJ zZW$$H4yrZlSDT1|5_vmYMSPpY!doZgh>n;CA;2}w7GxUyf-+{Yk#>tK+8fOESLtiO z5P(!NlO8q07{YfIh;DV$&JN3qOg!SMCc36kQw?*q6uzqv4|=8HH&_(n=x%O8@NfA{ zec@~z9?}HA^>9MoO9r&sf&~(<?2|o+h1@)_VeQ@z*m6bji=*@_E>aLp!s?A}Qh$NS zwzXo))$Klk8?zQva12Mm8_DbH^FLaBnXi!BP6>~*ZJ`Ots}^r(>27Gl^M&?}HBktR z=n<BGO+BqDoYF5P{lonI^X;A^##xA%zGRuu*^2Ox{!LqdWImLsg`M!WG*u`lpee$e zFLwAm(DV5^wV2{Z4`vHD*_nO+ByazT-1uj9ZUu(M47fjsbJ({e)~j*cDSqkD*xa|K zV5B4!Gza`|4R1g}6<akB#rX0x`JUoP`>osigz@qB%eUX3E?lm~?U@D>TyC1#!wHaF z1E<>)%A*STq357@Ke$5)&)r?&okz>&4`5%D#RNhC&L`}P-dzk=MGJZQBNTZN_*g`W zwr?O4CYM2Xi>&NSy0IbV7?oR)sU=&**B!jW>h|m1f3k5=GkuyVG(BN$HwzdJt&+tq z`|1b2_f5a|5087U#FvZdyqL|^+Sm(1wne3^+%l(N9tkD`N~`|vSGOR~>CmEviRGVn zSLYUw@KcR@?bR@G9_e)dkI{!PJk>uM%MI#xloual>SxfgV>UJb(Ec*`^snl6LBHL0 zfd^*g$AIXQLWP)rwYaa-$BjPPjQP-5sAjHFlLKPP5E_|uQPSKk!GxI+Gkejb6r}>J zm-|=ZOa68mQoSZ#PgHJNXNh>X%+9mB=B}5(>sOigf(y4l&<ssSNUaMKA0WePSuOAF zShI%VrC<(HefD#-chcNDM#Fab&2!t-Hk!yw6)D7+%g4NE(gd%e)+?+}D+;=BQQ^Bg zJwozA<0wbgX*iDAG+c?gUFLSjydN_fGzQ87x6drrp$_4Ry9*D@(=U|?ucCLCf?CCo z6kaCkY9Vw8OvHj+*|E9fWW-Vf5{H@6uU0-!D(1h3T7yjns*1bIMWJ6KSpELcz^s2a z$x*WJt@cjXpCpH#KEUrkpV)}@2hY6W4SD>@&Hvdy_U~37`*_CJ%(HE5TO#MCw6hMi z?`y@zZq;5&Gv~41_J{Q``xy^L<@rs|GQeLUhkfB~Aq}}vo!lE8Ofuwt11_>rk?F7v z!x)@*<gfxjb#L@zP~pQH>M{yUsOG!^4ule+rdU{K>a(xmE19s{9P#{x((S>P?U=`n zWTg(l>rLha<&Htm6F$zo3G2N7IP+Wx9!!KHbOta7^H@{?=!wpd{}*Qgn~$aGKd{`) z&G+(d#|J%k;SV&uCVnM7__m=*i899-$T{zu!#UK9$7+iB5*hd=gi^7Y|DnpxPd|76 z<+4Oze62&ilG-&UCd`D8R0kt{|9C&z6gb-VLZw+8T^72M8STy?pGenux}QX}-DQ@D z6fk?ZWwOLiah!}o!(w4HQn!22xII{3fkqIKtNpy2)jrAtoxPo3JGNp8tnj)zNFk1B zk1$lcl*ttfqm^H=M~~z^JE_W}&Rey{S;^NXc*q!e@o7lJW2(nOUUkyM>^Gd9y%w^| z&Nc$y9zDHFl!_R*@SJ@AA#fu7Emi%hyIsp6gPr(;yIsR=nWBe#DA<64xP;!mtixY> zKG{#5jgj;Q(?QqI)ZcW<_oSqv84U`(aQhR5`yvBIoiI-LQtR8vOE#?Q^IQ&wJG-)2 zZZTrK+n%f`lv2wugg2=B6|!2Tv>yO^uqG-C+9AhRsmBa+F0*jUp?wzJ^bSmkkR!j) zaj(T;XgH@+VFEkE0Fl_wlztvPkhhvYsjQ;tqwI3BU<Kk|kes0dpt{jm)tZ`hOCPVR zPHgLMkQJ*NvoOY<Y{J7ukFXd2n($M>47~+`b^n)4zB@t}2TQoLUyX4zx!v>rYm|Db z6#L*{(nhkpdttKb!nVuVUs%#nksP3WrNxg_PK?ZT-E|{|fx4hUMe2|5y|1^<6XiQS zz{MyRyiyeQ++Ub)%wrxJeh8!bC6z@u&sh2S7>v{(t)+#<fjBpZ;`Vr=vTf1yJM%8A zNrlM}<y4I1cnlbOu+Ez@^oJqDxHISG;;BRhLv@*VPJ(@7C+e}HSrFM4o?Xb;yD|wj zgSqa$BC@;}%JsOu+*$Tuy}MfchVm26hA&6#d`oSqfAU4EDZKV0f1X+&==DiLUhU_m zr+*qSe05b|_BfhbjAaM!8N^CU?(u?cQ`TNFd!YtinWoYEth?s1IRt02AgqH9QEY=Z zwlmL4)i%I?WRfvb0lP)_6$Q!p7O!LOVpb#emF(@1rm^1^aC-8S3C_H#JmR8J9U)>3 zagu~vAmLrkEESZ9EG|n<#qiL`3=NDw{P{eT=10tLY1(&iOa4NQC_6S&Mo<cyb1hh* zpp-KGpL>q&zc;Q9|H6(AXLBrPB_Ee0bjf&@b@E>w#^^*y!Sve9p+I-g?U0d)_1Pu% zv*VKnlJl5>3ITtf8__)AWiF+r1QMfNw|-RZic>WWbvKp2oe?3>QFG6^q_Hw8%ZtUR z5`DZJK7)Y?Jgy2R1S~*4=XH+RmbSliMGin?5$){~;6>rZz)0J2%=0WjT@_-BQT%Hu zoIM&tCC)-q9QSPzp8wS2v<WIh&sZ8%wKB^8i+KJ!HYt1)&S2QcrK9v?1x0r)-%_(I z%CW%$H2X~yvbW(<R}j=>1E$2e**IAj?7=b$Wz@Zk!6;HS>UapVWOn27p?ui<n|&OW z?)AJ8E8g~c^0kyUvj3<8ntT9D{2&fyQg)~f(o2)Lm!B<YY0^%PL;|nLqapU*Sj9)M zJh6Kj4*sA%|4$@IySTk8yY-d*&!8E}31gL&c$g`cNj(CH5v8ZO?xSG~{AMV~Lc{Ke z!zUkM)_+a?b|ylzd0a8JN$zaEB;G&sM#=79N8GjbyV15%BGp%iB+L+;yoI(mN+S`U z>e{cFZNF2GJvCiFA&0nqCGCkDl`V53$AP=S&hj6Ra3oIWQ{g@-a6W4LH_>0%uoV~o zBjyNBc2Q^&K3vRH-Y(bm`G&7}6Bl?Q));fN^htwhq&y<TifS%g?d=PhSKzKmc91nD zJ|qJ#Z`>l4PGegD+iinJ4HxANxDAPhWfb>UHQYyh!R6U0ZV?;8H7-`rK5R`%c#z=- z%Hv?4pz9CXcshOMSx$RiiVRc3hu4GreIXi>7rIRoxK;x4hp?1&4I8*&>HV9ZGc=e? z|Aj;FKBP1{<=H})BZ^(!Ie<`n0Cj<<UyiwZG`#ihGBg~qEd7f?L|GUb3AJ1u>UfSh zMx@hiHw=(@NYKzVO=LNIjp#v$?YeZoB{!&kb<|L_Mjx0kA|T}IOoF*XRb#rN-}YRi z&9U@Ry^%Y|^G*(OB6c!6a4RgYA@MYE{dnuP30v|D^wq;JsvG~yb^uW9*Gdt)*1m3U zO>J>?$3@_gQ(>!EMjufF;g*TXDvCQBj4U{U!E>y@oIIB}9{|wUD7}-`@mI{U{34Z( z5b_@n;uOGsx8XUtm4M|ISIdnvRd7yO|B-iTt)VFMu<-1UxS1sLl-*n7>aL)R>M3x^ z2SOe~pB*pcW{gXc)cKQk>>QG|ENv0~uY1|t_(x3{dXx|2dy-f%<*SdpCz!UWps_-% z_PmXxQB%>0mfJ?dW}Oc`*+%TqjF=V-6G%fdobOd5G-b-&L#kju+$|#oFq6jsZy<ki zQL!U%h#5!|t)C-Uzuk!8wxL$HH!43mQ)x^R=OiB+FOwYVO;f>ClUZ^#<9k8_GF3Yw zYfRyOR-7PHB!a%p{qj{q@vwqIJu1!j|FQQKa8Yet`;?%9onR0KA}WoDbaxJ+BA8%; zA|W6GVh3WPBB6q<sDy$b*oock)oWhs+Fo`4pLJ#i1_b-QpYQ+s=69dF&&=S=IeR^O zt!J&h&z`q)t=z&@-@2*$J@X7#Jn|yrptjRXdyA2a#2Zi16ZdcSV!=KCzphWXCa#fo zrH)x5FX3`VYW&*8^DY(do&2i#{rr-kyMvec$kZAAan7l>Qh`e+t?^bIxN%X4lJr%3 z>2ZH;bxsO6ni(v!@6_u~fxg2gv|sY+;IW&^=WcX<vo@>UE!(nyKX0}O==ARF*)abV zDKF;xb@$bYyX&=3#`WC(cQoH@Y!dzVm4KPzrc?gua%OIsX@l%K&6`LwX~{9u4fn3K z_6R-ZoEB9$@=SOPhO*(o0dB_<TdhuRcd>!yiF$8*r<7@n50X2U6jLUDG9qw=!r)`> z?VE~q&|TXkS5I=pg4;fQJyh$2bz5>febuS+9}54vJonU<t<Pf4%=WLp;_eIC;7+9) zvxnR}CuZR|!T*(>XI-Zyt{%#+H*BB3J+s!u@4R_o-M5ZsY;7<u7EQnGdi#7GQ?FN@ z*4&uC@U_3(rf_Mqj<-T>)!dhu#N=&=J8_)1dYp!sPTkh=nI0DKH-!oP2dvX|vMI1A ze&4xmkIZsO%}1?u6OSrgPcq!u+wjl=%}M*QMcu`~gBs$PH`{hUzwtQ#J+G}V`<C7u z>2zhuqc;g@QK|#7CpK>Qe8x=gHyd^ZN*uYKQZzs3j>OHTD?Rr8bxQT-2^T~4H+`S^ z+*{+{zSpahiA|;{W{QnIa&v{rRoktfyd+Jxn6x%Kt2kh3*}eyZ+m&32IbE1;`LOY7 znK@m%d#4po;=Spd6?(0SbNu;%I=xknv|1D!u{*eQ!|r=i<i%HhN{DD}-Yj$Fe!Ucn zzT2h3uJ$T7Yg-t2r;o4H#qxqNZAPVbEEy?w=dn+Vn5?nq<uyuk?mhH$(3tXOy`Jr# zKF?$2>zc*9ANj7{v69yv#~0RJy?D)(7m7!66X(uOfBz)F+9Ec-PO194dLMY_E|fnH zXz@()QKGn5%k;Bb55#M@r1eI2bRuY{{*B{u^<Oa~v(X>4p%#lm#NrEH&lzlYY3j=< z*E^KBNVhW|lVzG$zrJ0+#ALm}Qkx2-<W{J;rD`pm{AP6hhtl_&E%jU=zAXPmx7D#$ z3y(FsrOsRLxVEXEg=B!jI@hHW#K-U79`!Nn^&{s$CGFr;+8a&#<M2J@#AOP4NoC1R zR~kgUXBh_U&MK|Ji_U(snIGr1Cw9)T5>2s{T{?(~c^?lb{6jpk5cjiaAJ86=(@c3u zJDH3O>%cP&<D<s-3|cfIHZ)Z~vz2lk`G!T!N4@Q?mEU>!wpK4oT7Q(%t;2iSv!jgV zU*T<*XV`hzp3s_`()R3hqnO?&PP@fS7Ym3>>3c5d{bMtoF_HB(%CDs)Y#F}Ot6VJX zK@a~1O0RhS>#w-xzPtHPm*vssz5ltYnepD$p-*|E()X%5i8;jzIwoxrOpBH^TKDnj zvb(91&o`4C<1FseELqGqTT}n=$J6VQuJ@1BKH(`Vd*6ItmX%xAJsIt7R>!sRHEdPC zN!NxN-3P~?-O$9+vDNS`E^%AB+|ZwG7FRE!v2*;z)ptg#ZS8RHVE))%=GPiu)Vfz- zRFJT5n)lOA%k7Q!IDCj#f8akZJ|NuagV(ZSs}dC5u6>e624yfpr`Z6H$<3yT)qQer zO%J2z@AmAwG;3aZ_Sn|pZ=1ZmeI$C5A$<FOvH0<w2N-ViUZ%zilnHov|3-=FqT|6{ zF-zMAHNBd%(lDaQ>?pfe>ob<lQ`xe$zxlCv{lK(o+E?q#Pt$JDvAlodK&fGp`?`xY zEgI@EK=M_5*ya2IZrfjsn)4(`ar(o%8-j<Ny|ma})}ZWyi;2%47Y^;r*)w6~AC_-t zird-#xu%!cq7^zzdcW&tl`qk(rL^4J#1YT0UR?V0qj|QbNjsg+=u?-+0XjEj&MQia zDd>oc$3M^fqt$Ac(zB;>H|eB3G8RiT`dFI$Ea+h4=Btw*Uul>XHt^2$UScac)G6w= z@Ran+tx4*;$7-I=%s+i*^AM{BTN1AgQeN@#s?@~|jUIa63?8#l`IWV{>M@y?4<nVs z)@^Qm)6rDZGyL%RR?Ec4-2U_4iiQ67rx?ys&9EMNU#@IQozfQp*_wa9ypJgpy|s(F zY7L%O7O>Zl#=X2)<3NeC>-IHnowacPx)*EY9p~JQE@&I^Q6gSpM6sVm_}@)E(yrNs zzwA<O(zJ!9wTfT(f~>jkf>ZY`QP|RTU|negn};@^I-6;=sykYIZ96lY?Nj25(q88q zo-Z)BX`ePh!y~0!=V`*3jm8oa#q3|boxLo_>%+{DdrvNW*s{v?{$ZnLmP=x8+e`6w zoGEsAEZ1_SA?41h8b%*?=wAQhWb3edFL%mM+`K)me3GJXshrrb&Gm+rmL1v=kY;@S z)Xv*BCmi3b-#eq8ol;%5)Z$5*OKew%Y&b2Q)qaA@l-8}w<9V*Oy_Ke|NFMP1`01j~ zc@GB2&mGv%U+S$=Zi3ki=_{dHQ>QGy@keW`Cx5;)&D+?Z``;tljV?H$mZ(8DG%j4# z$wWMZ7wcc|Vb?cHtPR6#GMY=Q^e}(^QWm8O$!BTvuf>a-zB#(LS*!H-iFOZyBmXMQ zeZTTSiTku0%cM-V>_X?Bkg`x)G%+e-m%+r>??$~@?AQ51uHsZlUpMJ&mG!q<mH8^H zdm<h#FP^W^<WgFXXTt~7-}H~{zz=owJ7&wz2u@%4dZJQiG1-m>Mj38<Kdj@5Qv>>S zow}uU=Rf@S8g0Ckwf72d`^f+;cX?%t@V7$;41N`OAide;W?nKM4F6iQQapM^L!Bva zRO*KqHHZuD?ENs2_o>;9ciRG%j=k`xLuj)JD{;2=CcDOcSeEq)6DP6}KApyuOLXp( z^1}0;tL5~gh3T7mYAxP&w2|dVNx!labCs5)nH2?z`F9T5?2y>h!@{m!u4YzXeG9v? zzhp;B>+kB~Jh=a+_`b^KncFkwAJ)vx95*O?)3~kHT2oqH7!x<D<EwQ(@`-(p?SBxm z#N=d##rotnmtHSCWGOr9Juf-2x4qFW_kWz7GMYvolQY?5Y*){J$^NT{@43kv)sdGP zTVRrPbIONj^KzW}Pk5rH_DK65znB~Si}$9TvppJFC_iyz%MWGec8g7)K4s@JGXtJ) z+l=*hrujd)e`RZjMNcjU9}mkDcUN1X9o=xHht=vJ*_+vWJo=Z-^BAJA_N~s2B-=-m zn;&jBdiSupXH_1#<$aPVGkDq7v&<>@l$`kdWq(ODc_n6jXJm%~Jzq)c_PTLRIp3_O z*P{D#eAPnox}>Zt`q<nqH8G%r{nON5@65fTUZ~|A9NF@1){`rLU+sG>$IIO?PHxH> zjf520tq)Da-exboaL^_0jcoRZMYp5}tM|ycQHQl1Wj@#I#4|?+pJtL`%SK8{h>dq! z*W!R{$)46z$Mx^mP-||UxS6?giB74(v?KfKXx7WxDI>P}ql8jszZtGgo$Bh{n#}VZ zQxZ_W`>jKB2goOQ#h&~4*yY;F`)7wfaB@4nyi9I*;LuIt;T@z7KC2(I;6Q3vQjmA# z*mJG#jW#$l)Y)qPyth{`-{tiSc(~q3*U^>tc#ZR&t;dZwtG>DT?!AT2yIX5#FF$zo zd5~MGyiBv>+pfAWhuyMMo_ogin)u{Y$cxN}2WPAKWG3foEW3Hz<w5^*S3gC}$Tt~s ztw~~c?PcO8>v;{!Yh+e>Jz6|0dt6CsL4S6<(Vx+q3tMeIq`Sm(#HgY*t@>YRXA^uS ze1KC|8UG`agVTHTI{q?rh?>&lb$azqtL1kV@9wqOez)Vg(sT7~k2UY2ci~0zVe*4l zly2HL<+{wOdf1WP=An3bVub%z-Aj(;0Xv6wINtjFn{k<v>aPdJEhuO?(O|2?R9)3| z>VL;Sl8+SMw0`iKTlM>nvAp+UmXer6YH^3@33f+r_#BP6&{@X4=%wMLcfI~RIK%yo z_@Rd%&Z=XGf3R^|s=hzWlJ{riZc|y=3ESQbcX(PPZaOAhTdZu)q$OrDXNx3D+~fu< z__VW)neC0Fej$%<L|w7<RC8#5I%Q02aighWK8l8K+S^223C^#-RYp@|Xi67XwW(#N z)yAjGEI!rg=$Ya9t~yd0o79^Hc??MJTP$_#%!TK?YfbKotEJS_Y}RW)=6;v{kK=cr zH4R@fWssDOVk5`<-a|ST-9DIna#r9nr%^4$#q*|49y5905|_*FN9=ny)ep)rb6vkP zr{v?p6N&~8iW25OssGp2B~2{uMJ_zv&eCo9dGUd&PLJ$j+w)Wdr_GHW9sFTmVQ1GT z>p$f2w#$B!*jVzk$CdoxvPOO-a$;MY;=L7*m=DKQUKZ0zXfKwQjuPg!r|JLf)PEXV zW>{~a*xtPN4I0_Kk;@!+WAvfXmj;^IWgnRLck{Ombe_o_DhX4t)5^C=IN$p05$p6` zjour%<_wWKF+zHbo@ArGe+Ic+PZ(5x#j-k2Y<>Tkqba$rd;PnI>^3+jXH0siYIn%3 zyHQV9mA3lXy#6Cw*4sU2S&!KrBeP6YPn2{&H1O_SnLTg&4|{(6@}0U4DFL=_OY~=V zZTQyX$cU(jXU`A6aAjuG`{s?A_GGf#^ttn6?ykuCxbah1%A*L?R~yxzN&57ZR0>^c z*3zNP{i%BO-e)dBQ8OtlD2|;kd1G|)RLSlB19)Cdx6W2NcWzozn#`%K<4QdqEtMWM zbNGvRUQ9_}E&GWLqVs+2>-*0XSKA=3RDM&we2>o1ql+$ydu31Gtt~yQ!DR8K3j=Om zeD}m;bk9SFQiC*2#pB(tJ>IhVUE@3V!<H`zHfXNd&|pFSJ>G5i{I^n>=VF(|Xe3UT zH#?nvyp_d2KDy2RXuD{JdY6D+^-MBC>Q68`^6<k;b<dRaPl1~j&Xs+9>dekF<JI>0 zShfD}VvG9g<hjGIiD%p|M-qHg>gv$LhdbUsld4ruY{*WT%{O*Ny-72jcl=dbxq!oq zHaWas9h!FkYLr;vH5uO9<Okt0c9YgD#7_l{7}`N2&11wa|Fu%fdW}!lZj$L^B%#%C zu)?so1pP)EU*8&5*GWs(VcXWbVjCm<O=X<p3T7BQj5*}L_~hyKf#=i2Q#U+(F-Go2 z)+0mN;P|`urj44ovrz&s!_Ta4!@~pjBu)y|nxwFPRf?N}Q62xKKKc_Sy%gQ74TGl6 z{qX9;%P9s=O^k>09{Mycc+-E~)8n@Td|pp%Thgbsm|IBGAp_e@zSBmj^T|3xwTHYf zxHe{O$iZ7O4_libvK6yi&HHe*;8F6GsFEYyCLhQ+xN%0=5^+88wrZW8ukeb?s{1xQ zP+D!YzozD53CG=94VLFOJZu#|$l%E2H61%N(zj~$yxy?<_8whrR}Aq!Ju+Zr>f%Rt ztx$Z>SU9k4Xwv$Sy`cl0#inW*l=gD5xG?+Kz0U6KO5!i?YcXA^Ztpj|y%r(UF5K0) zd(dUV8qJ6gPKnQz(#rE+^<R_`A24~*`{3yz{Sy_(kJA!cIc#Bkp7_k>$@T;C#Kj(b zYPbFR*_iV3r>;ReH*eUv{CtM<>-Lf!*JAJQ2sAY;cq(}`e66LCOji1BgCUonr+Ap@ zuZ~~*-dob`n7MU5qmsO(feKk;lh2$?TqQoNbJ_A4mQv9pTTIS2emQaavtpUiF_uO3 z+%)U;ym>@jBh8`cq(Z{Uf$oxF_6?0Mo>W_kVE$h3&r^yfvi`^Ru2o!U*W&d<NwG<N zAB2wm`|O(+dvn@_XiNKro5s(1yx^=xS@c``hi439ANfDr`7&^bn3;jY%@LB<-1>@* zHja+Nugqut6M22&j>t#5BiD>GmU)<?@Vc4ZYO(Zd#j=-1zRnyxpjEPqdgBof8q6Io zHZr%&Q%uIX<&rgh539^B6QAh!HhGt4Uc9+Qbb9QqC-aBL%bz}SX-#&i`^SNMqmx$6 z6;nTS^r^vz)=?j_lgd3$wW!aU92&l>j@D!)v+xnE^ADS!T|G^?aM-%Ll4i7;4J*q= zMcjF{!1;O%@7kMzdt>hWse03MyvDMN<Icq^j(-8)tt~FSUrKw+JPB>t>GhKJlg}Jn zT_m%)v>@c4?r(y27F<2-7o^<(^7G<mI?p`RhHU@KBH~f|Au<v1*}0im^JO~xv3TI9 z391U2k7MhE{w?EXRi?8+LEcn+!Q~B#X-OMX?JWy?oITj~ne53sT~aqRT5>jMP{W3q zn>s7CyE!t`_QV1)3B_|u-FXLd$}-wdy?HhM`e<+C*I64E-E%oMr+r+OLHJ#d1(A;> zGL}!cb4%4$(SG9m_jy)6x~pEUU)fVfQfcN0$%RH%%6r=fJ(@JQrPwpW_XQ!>@@^;n z6IycRNnp2ged5Az_c;5y;JlnsqX)ah??^{<C>h>cJN9Fy_FcPqomV)_jgmg=JyNsp z<a@_^WW88kH2I{QO7o^eo`+mN)-N-s<EIsRA=n6@Ue|?#_Jpq3QOffiUH9#P&JI`s zfg5Bd$}KuP@a;SIP7j<z=bf3|dbma>4IkOt8h5A7HR_}0a%^9z_lWScq5#W_dtGlv zo{R0d>gjrqcdPPti#2yQO%5q5Z==M-hS%vI9Ve65N385cv0p*lz3$TxPj>b^b1J6s z*w{xljb{gDmcL1AKSV#oc6FS(cI=hphkq5^8`fyoABstVndf_+IcU((WTuosv$6O6 zw!K+3M@M3DpJru$X)lYvJXl3MxF91??nuea<1ucnT4YO%80T5{ZfEG2DQg!lOgv>5 z9W;q2cV9gIpX}Wp6Ym!;UC}{fR%Y9jf=4DU%hR43zMFac?$p;S?Tvl9zfEW$-MCXv z4>K2wK~mSY+!Y%xsm$U{PCWYSMt;fDW&NH!xL<O2(=o?4=dMi&Zqek3P45BmDUU2x zhsbKj_M7+c+H8b>-_9}?>jp}%KDW=Wb;qR=%A+%^;}xHa8^kE*ANXTJq-DZGnc*gT zW)0T1E1j9CJ1tXFGA^R2`M{<t`<`zy#Xv3O(}s|_&+c{I|2*a10heO8^mggfpB{C4 z(MG&fqE1+=51qQ)Tu_9Q-aO)Fv(cLI<5fDFDevvlVNB0KQhiFYUmg(CIJf=r&0U)l zN<t2YhwMyTy0YWg$h;ht^~#NJmL#>$D*7k+!NbH0x%JlJN$;ZF+j^xw-;mQ{nfb7? z9IuFRk}0|>!$apuXZIiEJk|V!Sf5QwYZsTPTTdU<e&RYY|7Dn~cUEbl)747Ky+M|d znB8v2{LoKUQLq0#_2GUSRj*L9+k4Ya*p#WND%!_LOgy_Y&TGRzW5ET51tVMTD3shh z{HC_V-)*eq6Z-m{eBtr&O0ePC!l<Y13O76V)X_MD5=XG&xyE8{={f4B`gCoOv#O2% z<~Hk+g2z8-TkO>G(2$!$#|>E5*0=M9_MI1qH}jYxzplMXqlLB^;zlRsx0$z4S!@2* z#BGDcTfOO-SkG$iJ?rMVrb8M}){~alc|FByPpah((|+AgEbnr{&&06z*uVW`PoAmM z&whf(n|dFH`rfqFd3Rmc<3@G^>66R+a?8%uH5@Rm?sWN+H=1iHol*L8`3bSiRgw43 zr*HZPeH$>&v1nD0spkXZO{se<ZtRJ(JF1oSPhzj`{*N0i@V@=PwPbGLjdsb?8tMcz zy=iChEK}DnEyK{nH1@&U4=>-XkWxKhAG2(eZTiP9&kpZW^)fxa`^{=elT*@?b}n;T zn>VcgY)JdY&6I2hHq;-HY_p_^rkGvOPQ#dj_7^iA72bXCbZ~X-I;VU2v!fmqq+Yl) zdYjeF?Pr@eu{VE`u3}yG@$VL;^Gkix+LXF2H;L@AcG3FLx}CZ{R*TW-`rL8eoI&r} zl+9g%t2EIYB&B=m(7F1Dx@CUs(P8?CItM1bDIQfc^5`z(w2~`th8p(lpL#s($xDxR z{oGSRw%FKP)b(E~HzK=9sdh}ZU+Y5OWkutyrz;gj2j5n|=-TS#=4DSU6Ry<R^-mw` zl5ruDZ<|~mASSz}ONY$0;!9=jsArbzHQ081tNW(1ahpR-O2YK7UD_UGyE*8}@lioH z8fsk2@1AzN!T1M%wbazez1paaqsQ6tZmD<8!~05Q&*<CBv~PD8xs-cT4_Y<ueXZcu zB9#m&UaObxdKvLggE!|zE*P&Le<NPMb=e;?l#j_>ovd=YzF*+UTchr%D%m7QY`*2+ zN-JaPwsxt}+phJK)=v8<xy~slC*^o}cCk}W$Du_=uP&7vX{<T!62A7B^@+i={}eNG z=u>BMa#M-<K4NC|{TCT6X|*RK#c0XBVsGr`nl6*QG9W!|bGfVYg|6@Zkod>R=2JvZ ziSF4qn%=ix+3eXh^v1q!R(FcpG`DOcH{d~+BX#`(&E|>!-O6hF@^Q0As$A?csU)~< z2j8PlwdzN_pPl>Q@CsY`7^x2qLpH4R-#^#$MbCI_sP=bRAJlmliaE|MJ0};qMQ-=U zjyB0-lOmUzpA9+bVCg#}vTdEdyzIFWCS4!&-+8^xF4<4(U!?Z#-gEukgZV*;3MX-Q zp#RJfb@s{m#U&hV>~|p9dR$=gWMy65Hhuq^yUHeW=A^41dp|52DHZKguG%JJ*O`#b zS@-JdENC#i)C|T`w{&;2S!&Mfoq1x^o!+slJG5Ea#yv>Ia<H@V`$+W>*++K9kCJ+8 zF#M@QY~(D7>q9%b2j-f~W;KxB^7q-<@;fF<rl{#&xjMMbaG9~kGM64c>6ATJGM0D5 z_HmR%EbpQ1_AWPXcPzPrTQxeunVrkVLTbY%GnB2CTI6>BbApXtqRr)mD=kx&U3xZl zy>Ws`{ZBi}jrET&p1b~ao6G=doZrY<eYGa9U43%ItifV)LK`Y*#qYRS{Ak}S?fAe{ zvl7En@wZ-dG7)McHXIz?Be}nHlXEJ6B#m4WmbK13G)evAZ6~oo4aXkbzqIk#T~iwL zRmMj?nx!!1!ylt!T3sz~JIVflS%*fYvZL;o-cB+<7WDFIgT8g>Z!#33UZwsilh>x+ zFsaK~H?NDmSAg3X6Mn{HA6s>{tKxvf^G!a6TrQpPX~wa(|A^sXZ|!2W*<!$?E;x>- z7sTm5B|7@w{6H^IPhe>=AK1BDObFZxDOMfA_ox;A8{uc*rT=^U#yudkGue*pDh<>F z8UT%fW<X1z6(A2N0BwM_f|j1s>-0YT9sPYHKo+PA)B)&oh1{U;`@hHkTn~uan`}e2 zCVOEQe9o@QXvrOF0Q!ItU<OzLwtzk0C<1y;uhaWxKyN?~=nixS6anH0eLj5;eII@A z|M3Bx@E?4TU#4+LW3nMY_Gkx?t+Bv`vz-H455NcT0|J0Sz)&Cv7zSW1=x3nU>3#Y; z`g?z%FW?Qh0mKngfWAi+pzo#cr}Lomq4T2i`#;}+PWXin>Q5SHG63~8`RI-SR@`v* za6(HqB6|%1$hM<_5MYb|W6=^qzX8I(75>{`{2Thb;Q)O;eGh#f`48d)oku5t;s^N# zI!`*^|KkBV;U^w6mdMBA_Hb^Tkx$nIEC6?a#<tMjWFxYd(8lAM*OyM{+(K@FhC<`? zp6#2iHCAbnYogL3&#Xhs9TuHh71J@Hmr}FM+HISx>e{N&(soT`XGu#*MB;PC)d1oS zeLj5;eIN0RzMsy+1F!<fH>d#Q7wEj{{OLOW@A2(DAhbJaH_~X`029Ck=m(I09tCjr zhMnVKzm;THr>+Xey?V6$!%tiBt-r1k`w{flRr=(s+5WLx_cj-;Ra@`vrPyo}@kl~K zEF9k{$_r6GkbejShzqU&oiCj?oqt1s^yvTb0G;rK2lWs26ZKbHpa(!>ihS*0U?dRI zu&(rk?h1|5EIYT_#nWha8#ew#b`H=Jg)$4!S7rnB1fb(DLGX9<Z#dw2#pell<fo(b z-o0C!%j7dUw`jOfN<tz6=Tn^z6m#f2>3m6lSOZ!BX@$nX|M38wQ0+l+lKdpuo%)IT z%LecTXsib}t}7jDq|!Wx#`b6S7T9@UPbD_Wqyrmo-<eJG?8+AO@5VBRYO=K>wOGy= z9kz9>F53~I$F>WwBT|p$h3RsyuOF?=Rt?i+%LZz&IlkRkoU<AWwd%x%7^-l*3E=op z$qC|HKOM!__Fd$UsI_dk7{m?3c?!88^b5gwEf63LX$MejXb;eJ()Ipt*9Ua{q?76c zq@6SXTY&m2P)0%`w1-0D<<9B~C;RIveh3hbae=)D_flcw?K-o$uvhkQEmjb&%L>Qq zv7%^wwlk(DD~>hbuq(b7+ZAuXN)vi<D4W!aalrFk^qS!PU2z7iBo_Z}LQhtNe_t4- z&k7^;*=BHucrn>sjg2<%zy|8W&)^*V1wMkf&_}ENGl~_8O=aic+=V=#mh=MgAPAt? zMb}5yNx7sfAO-wy<N^75(#g#LRlp1&e;L@Mu5>i%Ga6qNew}O%j5h1YW_hc#tYNTs z1nd*Vx7V(TeEaTBG-P|H^=A8K8nFX&jM<^NChW*OQ+8y&DLcBrj2&HQ#>xqc%-Au2 zo*!MnfB#6bDLa^C!Vb(bX8UFsvAt7!vod@hai<u(*clB@jMrydz=tLN-PuH^E^JUQ zetZ$e3OZlU9&N98lW&xYYZ2uE`2?XqpzAaTx&X}q${ndc{x>{`>%@7|cH|3t0;HD) zHK{KhjhJ3SahBho{JstBt-_7HEcoXgG`=x5OUT}n4A~yoXg_RyD2Z?1<0%&GRH`LA zlWxV%t*~JivTfMKHMZ>XT3dEyy*<0Sk&kPe?AbLs-e8B<?eM-G_jmO7=T=y=(`i=h z<T49(e6cylor7~sz=_^$_hjNjFV3%!pUEAk%a#o2!N%KmW`hKNfzF+H;H}Z_PrY_c z*HqJu<PS(Il0I+;dI3~hqW<{*-4~GUN&nE8?+Q?i_iG_59c9-=e!rit;y)F3$5@ZB z?!=Z4(%{B<5yqa-=6k0bF|r4Z=MzgUI9s1zWy3D7Bbz#~8(W;%tz0K|yTFD0xx<Cs zEpladi{0405;yi>mpgk<hL%t&z%EyIuNZ%`)0O>M=)&%7cjo^6jV+Gc=UrZB$LRBk z6UUcWz@M101G9}e?omwHi8w>CVpFg-o9U&_h_8ITP{D(Hl%L>QMg0NgE|fp^2dE#Y zKd4`*fBt`r2jt(VW=e4$TZ4+ZVO#_mskGSSr=$3$((Xzu!nPAz5lCYlaeRUSH{SM6 zh0SNf=J3}imszp17~2=u*s-gd9oWrWXV}bz-Glw^m$<WsyFJ*Wy`JnZ;K_a-dvXx% zAs%~r$csHYj23uy7{^D@9`VBK^d9{k|8F1f!@u3<fzJU~z!Boc?H$hS<~ApGZIc7L z1b=cCK7#xMageyMdx{||L7bo%kr%GV=J)Hyh8uU_@(-au@aW$5YP%-&X5rd}JP^hN z(zxUc^Z?2ish|FTiU&e}uZ-(527J4;Y&geTv)!Mh=}1QnfbWcTP-Uw}XmfTa?N*v# z2%l@r4kw$j<0<gznPhW}@y(9xRvyM_A;vA)yUc^L^<SK=Vb8<f0{fzceV-lmX3vj# zv%iHX_hEkv@S@zCy`b08R=iJtf7FXTCvFg5zzyQW!#y7C{w_DppZt;U#IEB!E`ft* zGp%uc=Fl3Z&>lvd#w7ijkKAHjUv-WL{G3vWk&Y#uSYKKy8vP*T0mTIJ1$_XjC#nFH zBT&EnH$TXaQJ&KTPy=im$<!I<sHSiT@$nOl3(C)f%{s6ZLo_*UMdQ6J5&8{wKQbTw z9b^66D#Ygv80XtuFb>IAV{DdrVvO_HU;Di<ZfOjAb9i>d2R0|b_M9EbmOd5u8^?bi z`vQmu9E5+v=gxC{)>HTl;=^O`?61AV1$V>?SHucuc4dP-JGasnKEaaXE9plrHXxVC z3)5q>eY&wh22`Km=a9%3p7xM$v>bgR<N)~s(ggzm$`Po)sNeou^F*QTo8lU}1I`_q z%T4mtYX6Ay^@#6-4OG}1A9dty&~&i7aJ(OxZ_ZApS|jE{n{A?4PI1{CW8ITI*iC)! z1-pB*#|ORP*L`4j0ArroXNT#y5|w-q@&c_0A4IwF9K7J3Q!C05`g`~Ydhc)Wfa4ap zK=FdO@Nl;$r!W4<cSd}0WaqPN!COmq1b&Nhk&?Jx$Vq#$btAP|giU8bJ%Q5(pU4-8 z)RBk-R6iO5xBxu>(ggo)93b11j-eX3Cg9dvxp|gQ-_e+#;Lw?2$2L}k{Ehl&5B%n# zxfI*2x$%BwqZ8t~E4#BpVD~Z}d$h-kJ=zDm!}fn2KnpxM-~%4`z!o9^4~Q2P+#r4s zM0g>3Tq8%whrr&V5ORg{D*_)wyd(cZvF8DJaIe^%-O6`iSD_Qmt+It)wB&NiJyXB~ z#I)_uG)w$@FbE$(ol%KVO-sH(-Kpp&p)Vk97zB_e&;qC*|66n+*`D(HR)8*QwSF$@ z3di_)yfWtlll!Q1{ZJf>I?UwW$hS?|vBebMk*BS9VmG$BvOl)FV@&bb{ZcQ)U2hJL z_WE%4{|g+T@y~}3T5y0sT&M})#%CNMzKHPTd$_`JhwM*$LY(1qiMUAT1b=|o^yf}@ z_6Ib|mGussZaKElob5+lp%k?z@&!~=im*|w(1)mXywPgYWCi+6$bqpq9uDvT(uLHY z)UVXPzw1Ld?&)0h6<biFuU!LVrIRaXuS$8yfSw;;2r87(b8lb;{EnpzB;PcHG&W zBKXr1FXU+67*l}2{u~eXdL!;v1NcCGK?sf?RXFnRaix+s{P^<(G3hZh32BoDrJm5J zZpcTS*~QiNT;4)HZub-;Ry?s6+m8D49A9-tb!*bVq>0Qswk$;7iE@B+A<cj21Jtj- zs~;fSQ=B4SrK8eZHncA^j!@&pI;pYTP+hKeQ#Oe|=F1mbVQkoA%saE|Tih__Juq%~ z?A|UfjC*f(zf8aZArFWPA`tq88e4GWVRfF+a}l1<@vq_v`4`T|P+X#8@=yEVyP#L@ zLa+Xj=L(JJh+M%IIjjZhlg7wv4Ot<4LD~=vemvlFfZ0M5Ao&x{Cy*u(a)9z?>fhf{ z570T0R?$>%)o6S_ZN-<I?UC0_^H2lV^-)(hK>f@Z@z9(dUy3^0Do51gT-l9W59oRx zZ0`k)=f&>A{`Yoy^FchQ3E;$i5pceu7M>7iYUGH}uhhhoZ{`X={s?0bzZGZ{#3bSX z>6ZJu;0vH-uIIS03t9HKek;_lOt~5+<%%0H{}gQAu|g9#tG78NEm5%!LC67$1*8Y4 zpQ*opmj~skO@JQyip{3<7u0X59y+gYceaCU4}Z6ND(dmcmeBUL?80g%Xe2k}@t*9D z9bWMHUa-G6#)>!l6S!MaGb(r?<cF{YNAA_c6M9XACv^Ou;EK?<2>s1N;y@`6*XhCK zu@~1muv5!z*kQzjJ<vdu_vT=JGSmVb5afthe{iX;ltdKzmEwU=54cl51JvKYLw$hi zR8+fG({9rwMyT-z87Q-5sEZelCw-4P{WQ$O%(r5v((T!WHO{EpxntaWvRm7|*zH1Z zzWs~Pf&&ENLKO&jLEPZ@K^zg{H|7c#g9Lu&0mlLG92~fn=f*CrcVwqCY|$^~7#l|5 z0BQx8Pl&)=f{+8K8D0_b1H=K!3n(|F`T+I&|74wzVtze9xtl`c(ZV_|<@K4U=j=dD zoMPO*>6l+zXbsytpniwkD2Hs%W48*t5c9p^`@P`<(1Hgw;STYlCj1w1<)Kiw!2giG zxw{kG2L~>1aAIc#9H81E)jji34~cf@Qjr_bnkErFK)E5+2W+X|0qTFU!+-ig-$`0j zp>uONU&Q<86z>P>tFY98mE(Tz44Pxbd|HMh#=R@Xy$8FV>&0&70c8IIZ}0#B4~Pp@ zAm9aYgX0Hr<O@8xU5z8V;cFI-H(;Ut)tSF@2j*nbj)RY_G7Agn#^yyL51VVwj;_I6 z5wu4Yzfy}&p}6$jF-SE2P@Ljo7R5Y_fjoD1Wur56upQ<SEV-PBVkL1v)DPIH%I`y8 z3-bc15d{FW#*Eex{7>Wr6nmN}HI=pNtEKpWZ+j)SsDBS`+>_7WJKc;M_b1Z@w%_E* zu5aaW_P+u9-^lZ3H}gLS#|I%VL|gEKc=9E#oLJ||Li%=Pit=&{=l9K!k(OjVIyGUw z;Krm8T5Q8?Gj=}5?b~9|k8lMXxLZuIz!PHu9N0h{uxCf-TOd{%!w>Z0=9WV(1)31^ zBv`+ihif7ZkRGI3F|CiHoPhM;e^Lu@F<-V$9StANc9(@ZKM6JNLX0Uc-oxh~UI3q; zhI;5~__>Xq@cUlu`Zn)M<a#3xhyZ*bPKe?raikWW5O;)Jxeh)}Kps`UuGBZ%9DVlH zzwOwr0qg6a!sbQvWCxM2d=`hkHwF<`1iIx<vOoOJ?Lr=Eo$iPQF7T7~>@aZvTA4KA zmNB~Anl{n{R3D(V%s6i$2PiLe1G)nLDJ?*LpXyMGsOxQ}`8%rTPQ}`&9axW39BT+I zW6BQAvt}n!9oe}o=$s9nsN;LFYg@ee*ow9qs_>#3N3M%<M0hO1lXDwAn7OX}S8a)N z{^`GwZ(fgiSu3$66Aak}=$Pty<uiVW#-tj!atHRO+=uet8{6EW37xUdzz%tm1!|+v zgfRwe!zgV*eSp8_uvLS)Q*bTB0n&p*0ULnU()=gtgPh+tQEoYoKZmEx##wd7Tqn)d zQ$5!N`90Q2EOTUMR=TnaYdtacz1ZbV-iZC)>`D$=QGgRytAXQ5HGYWlMU*QSH}RN3 z_m)3xSM=R?e}<Hl1T)lV$zrj_^xz67PNN95%D3nhjw_tzB_BXqh=+0E0iEQ+PA<3O z>LfH5S{QA>(g$nu*BwG*@p`nqB{F9ejB}?o=RJXcI}Xs;m+jWNu^HBA{q2u)p?Qo= zqcM+zbKEt_7;7P{*s&DkwwbQ%+-gsDalIGDzc)AlfCCjEF4P9_<1?NRXR7gpp8MLU ze7D_j4Zr-~h>MG{p50oo86mpt%m&N>2{i53^hzaPytrKWMjnsLkIrX1qef(l{xriH zRd4`#^mJZV_*%>%!{=IcYE_Es5RC)0hEA321N>Y0K%IK^#M=32wLd4+dufAc-kxH< zG21)Ck{w-yI(M2YJDUX_ujRr1-tcMO>>@ZoJm3H>ToM7tjq3dP2Ck$}G-1^5xZd9i zq$QnA+OsW5mY?Zcp<b!pQXWM4(2aax8xMNWot<6fgn2O=^s6a<z5p5|%o?=;%$<|( z6<I$(IpH9{450N@|3+<)G+#p}HH8_%m_HqB?04Xties@ZavIjZ%)@-=GQ_Wy9vJ&x z?EG49Z~#~*0P&zEfEUD%DjXrMh;T%BEab{X#FQ>djeje<qkld-pY9!-usLJ(*_EyQ zeCIQ5TP^>gyohq+t6MzTrFCvx-h5<%73Pdc5B6f4;9r7zcffoRx90q*NPUp<0Utnv z>;#a#e&s`2O`(xCYCJDUi_%<PF4naaMq~Yaq6ynS#|D1iiJi=FXJ=ON*ts=c;D9$f zzXkvYs-p@og!~X~YvPH(j}T85MfCoxuW_-z?Q5OdH)L6frj@x5X<TU8nzSvaZ@`x; z$d}Krc7cwzhZe9zKO1qrHU(?XX}uizV2h5eN^spmEf|PnsteIQG*lD#RS)7)BVLbo z1^ir3nWZDwDvZMY66E)1S|QhSV8>Iz*A<@ZOqLfrn@#ZMI6yr34B*8%0XU8jPridI z7P<<*t*y~lwVs=8ueNOOa;zh*N#BAK*N7K__papd5Ia4v*3=nsz!r0cW>^Dl$jzli zVjU610a{Pkwo$#A_)g*gX+gSoMhj>J{3;(HZPiKMQ>gbRI(Fgw{>}-wKVphGJ1`fu zt0mB^=^pIV3NLngl{b8V7kGeH1ZQzfoTw2saYXo9HLjcix8&;8skM)B-M{sFjpU?R zX1wW_^B^uqqP&TRe83%b6erXLtkK`5m_zS{wPZS2KgVB7h&h65xaKf`>Ow)luk-;D zjT(tH>8q`HmDVT>#r<%bux_g`ite2<W&37Xv%?FWSb3^Dd_RwGf8xN(Y5*Tj3vh<x z2XRCI5snD?@+Ge9UF!N<+ZX-+)$^0CBf(aH1D9*dfw<TC_P@9eJ{7ri`4W4!f3^kY zRQPd#?s25r3+XYe9a@R+75V^LN303_3LQ|#rCXcfe9foCk};pV9hz@vtP$!w78v^u z>}ZNBJHFhLoq+vMW_YoanUy$&WAX_#qB=)}&%eTzBWWJLrSAV0yNmu!izap1;SAKr z1Ugr!Z7XtQ<Urhe>!GJs!?$L*up<j>p|wq+1r1Rj*5lTe)424}Xn!BhUg!fz2Ur7i zFTu~`0+dHKz}(Jx(tN|AJ99&!`J!+SRDv1CzAfUu3p={h9row3<7r;(c)AxmAq2Q^ zLI92z#EokFsKS-nctWoWxglt~DE|K1AAR>#KQP!uwK6BFDG%awFmkCiJa%@a8!KPx z2p?dD{x{+D*Svnvf>aX#P#ey`cM5#~<pQ+-fE@6n^#DniZf*U9V}EWR8vD4%CDs^m z$cpVxa$tuSxv^u*JfZz~tb94_Paq!D1n{B?KZHD?_B**UHAw%feT(b<?f*u$o)kN= z(z7y0u9gGwb7#t>Nl$xX4&4Phzz#mZ40eDIKn^yH))HfUdTF$~gKH)qKy_idzmVn! zX}uWTWBnr!8I1jM@-IP#g0UZM$chup*`ArU?BG0{%VH09Y$=Z&UB&|syx6hSN|fWc z0^kMlLli&572=VVZrk79zUcI?p5wf+hWy+|m;+VMg9NXi%Z9$nbcYUbLcVPcA7G4o zr3_e-Z?}rNAlBY3!*`Mnq&+BH0A)Z1_}+&E_PTHqj{P~9+bak+z<nB6w>a5~?Vat& z4$gOj{dw%j5-;d~U}+5?PSgYuo)Bj$ctXe3cv72RIhf+XdUtRA)&9kW|JHvqz)`g_ zClcm7pSAp)`ZRn+d8#Wr1Rqc~)g1OPWZMvv>7G*>r*!W*u9so}tr4Jk0m=n`L<dSi z^XE`p8H#({w}j|{YZ&`+W~^))a?vCgc5s0QJG6+$4yO>jI2=LC0em1{R7b5mA@0<~ zlWIPtI#-Us?>HGM{?_(I|9|oP%(N7$<jKMuh}w!gNRT_9Ude;La%V>uJEHDl&5Dr= z>_GjU?mMPj0698CJzxgDQ#dzhf#cS|_pA>PH|)@=CH6sjO>t$CiyD9IM;oz{1WUGi zCN#)gH;jKz*x!pCS_n`p#A0xvCI~q4tGRLn@yFfx_m~IznyqVkUt6{L=lL?}T#-D8 zYAW>n)C%NS>7MKu>Mc}vDVuD@)nI9kcu-G%J;1zE%boZh@&U9*mIu%gpgktORR>CV zX|x^1`F_-xGKXrR=0x|oo5J_ovOTk0*uG>Bc3?h_9auo{f-eBTgPKsm3n4#5+nRVn zuYD(1$Om;-Y4*i&hU@<=-|wm3sv>9pQoejzkRP2)N3ONZ9WlTGYlSS?&WZ2=$jKwD zRXH7qdrO}PazUCGq8OkJd`}FJ^wU<{Pw`}gaYvRvP9Hu7Yn0<GSm{&;#C}({Z>}dh zFb}q$=f(EV=i>m{&j4N!KPosP!j;k_54I@8j79d*VIy3-vk(srmNdeM<xFy@^d;ZH zl~pm;OhQ8Zx3uw>xMy$Bo}V*|<Uo`U)s!=zKt1II>|dVh!Hz6)Mh<R+eFsdrdfY-k z!Mp%sfGYN+!go>(7y_6A-<$`yb!*oFapWWAl{0vBf35-B5sfuz@z$(#niJbS+nw!A z;(-HR@B=ktKk=d_fFs#4w#-Vq9h0sjS*xwuwvc0^+<LI>)1WiH!>@E#{_pbvA-o<n z=gZaWDU>JUwG(Lq`!9Ay4sORvCqoCu7-AnYJ#IfD(rgZD@`qt_$^&SPm?J=QLf=vg z67Q$oK0@gG*ACal{wuv$;RMV*Bp}{QcVW9{d9Xcmcx>++FYurmz=^#g;5Z`S$)349 zHrQUB)sd8_)$TaQD(}^okz!GObgTLm5uZ}suMBlmuk!D!f8Ou-d`%qYP6hc;MIKZ` z4kVfv9ZU6uwsvO+lO0iqwZi%UW7w%Do9L*<#eja=imwDQAQ;z7_YbN9-?CO*0=|Dc z`TkL+o#6X>;$B^>KZ&(q#fc8EzdI|N$zx@+yx5-EUhoBc>;V^Q1vpYX(}S65{CK`@ z(xVOA1>V)@Q$%9W^dW}-RrXhH*ZA|i`D=L)y${WMB*hao7-zO;hAn)vDOU?f!TRfg z82>bWu{JviHYW|xAJ755Iwz>EE+!4%|BQ5hBI*7xx@X;(6-`8(Npyn$_h6+nc&wB- zFw=_<aDn(B#BLmO5OAXi|3<Szi=VbjudecJH{uTY7g4_=8iN)EoB#Cppr8I@pRphq z_eFgzXRcOPIfmzyS05((&v!*0Xpgmb=EwsK*~Va9Hb9UE(0<3bmhaL4zM6`LltYsJ zm-u7vt?*u0&yP9BiQrhG3)?l-gYB9Q`%m`*4{8B;Q7Yg@3I4Ws*IzeRALQ7*($B!J z?5P%iRz}$V$87hrJaI-{h0BeQ?;QGuJg9;v0=`h~?Z5(PG1TIAC0arQ7_sf)J+U^Y z6K6lSZf(xv97zKV2WbCMx>w|jb%6dliW7uuJT{HeLrm$-wnu?marUskD=V4m$x5bq zp#`WFU>8~r;KeQxc=Yb{OYKn)dLVa_Yen1<#vf`a{%o4)^h>`F{q@T~ud$pAr)~Em zf38W}epXMZmID!2gg7|ggYBCQzdXen`MC-5bOV;?s>aoVV9U3Hb;48!a03*9FKR%n zC)r7{Kg66L`|~5fsc1`9h<@EU$&D3H_GCLJ^H}i|#B@=B55>d@5zHQJ`mfB>hPY`} z#-3_1s9>`DFSpmP;DCHHS!S=N$R_yVp2*qIF}3B)wQ1XGIS_H=AZjoB=XpYhyC5&O zMIFEl_geR2O9yE1_ZWg7@6<wWKF(jL0XyNC>Oi$U#NqoN2I%r*fBrZFjDK({+M4Zv z{fiRZ+0H~BD*|>VdhszCZ4D^GYi*l1_*ZOU+znb#;A5)CAbMTq3wb3j{9iez>Ypo# zI)u4qTQ=TTmu;BnSgT$UYL{Adt%$a*skX8|ne6Y1n!E$*Gv=ID-z1^|3>2Gf#JQ0M zq;+B4fm+vxd#Sf=O0l2v{$$_oxTgd6qDGj){<duUL}ykQ?*Z^wVFED8s{%zht^#ml z)*#b=t)Dx$X;Kk)YKlKy+BN;xz8n4b)z7xkQDkeQQJWLZiE7X)6}huWzWgO^`x#fb z+6&nqIoUKv<N%h4)kd&ePZo%ETBPA@I=9|~b0+)y19Y!otvNs+EyZ5^`95WqHc%7y zq8qaONN_6Jj%}ad%C^UOupMzcKEQ(=)xhzBxB*yd{CjgUofMi>jXlCRB#1%2rd_^j zBlN>B{~H?fTx=5LLe=viQLQ4>D^)lm<jXg61-TINp*_fp%TSjqPO#$YbX2bkwd89+ z*KTbt;94mM7zFeNngJ*ceE#s$RvIYOe(OeHZ+x<UgaylwvS$So+*m=ZC%|I`6M66l zA^<10i(qq<+rR3AEqb&^3=+g2YOBScjnOVxA1eLJ?Ta4!;%AO_(X5&iiR3{QxiaY$ zkzARMD|kZ3A~hA!;~IIw@rB3L<4RE{El#k3{Y{Xw^<?qbZ;8e~_FMc5=PjHIG{bRA zpvLte*tcaGX+PS(VH@sQ$PF`Mc@dT@Z@dG`k8xx96FgZSkUxP}0geyE2@%Bk{CjKj z>)@WdB_nL$V=Chh#iA<l2hS7zjG#ULb8`Xa8twS`59L83`A_xZ>UyQ(`Hygg;|}UD zGdxk3A^Y3G{)pFDqcz)`zb=e^%Lcz^6akylx?n4yRju~dRoqCN35NF13+;*Zg+?ql z+=}H!I<j1h>D*{f4tdc$mKXCm^6?sR!%?@x7sm;{|A*d}mXc%%eR|g#e<&V_#-K4S zI{y(4v}q>CwoP>B$D|tapz66YX_ngZWl_FVaHD#iMT9F<XDOZTiMos{+X?#@#+Y&S z`lSKL0R(lx_D#6^1BRjBX<azoA68=xz+YE!A8G$c>`TbEzcJtb5l$?3ygN9+W822_ zxR#DT19*`Ov}!E-L-zd=FXWrZvH3%+xY+Yq{GnJR5`Topq*bDLJsH&uzVz8Y^PXJY zI&A4ko6lm9FeX*Wk%c;iS^-b0=gdFEm1&;fo*Ua4@4$A%K(9p@vXw)%EB2YC->1Pf z(maqoKx=|)vA@y<vOn!TlNV}$@o&Pmj<aT4!=2c+2zRzM(vxkC;GyMX8(I$F1o489 z68P)uZ@pC8u=QcC6){IN{^X0q9nlyh)G5T3<)iFa&(89{+!v6>NE(9Lm>?Ib5`&;i zNTUdmJJEwJ2(o0cKD}6!S5G!Iz?iMZxDxV&+8^Qy@n{NaGKm8FM_VBN8?v<{baB5p ze?5rcw+G1n4qvptp3(!dKWT?tvVW*4+cwUIZ5ijnwuHL_o&c{B;KCLGI9}j*gpKwu z8*BBXrPv5NZMIF|SGc%Cu}6q6#Goo15$Kil(e}*ONR72`(eS6wQK@A^mO9Gzvm8h? z2Ia%%<J|O^c869>s+KuVmDY{f7*`#ZALm)Yl`m;qAz!L+g=%uJe_@;>SF0;P%`PWI z5Ay*0HGziw8h~&fxNYs@ANO{`{)w*YuzfGAD>TLUw`E(xTv*OH4}ix<7~1LpFLD6$ zZoek)(^P83mJfHR@GC<9!nHNU9(ql{6KX3sB0R3fm9&xe;FcEC?<CLU>VA8!K!59R zuFkf`cvQ`Ss>PsicYW5n$+yQr;^h25)LBW_ifG#^K819yh)>}kQ!UOD|Hd6PS_gss zd&746++JLy=Z%z`Z-dRhKK^x;-jV*B?hgBpCHtGPEunTSC)AZ~hW$5_{X@Obehwiw z+BE&SnqPgHI&7q!4$G<LS488EF#b?mQw;iEt`J`q2imY;M;&IXsm#>dHD}Em)@KsP zA1J49*{DA2-K7l+b<txRBPb85%zwCJkr=co(v6vQYyU(3h1NxcyJMYgwYrM%xu~`k z@?;0qTjF8=csI5q){zyAx5VCI(Cs4jH|@|OUts^Q#eeQM8D@EP=i7fQ+20;x-WBkK z-M!i7P=MMBRB~a(2v>#$hCg)eI6r2n+LkR3a;)lG1b(GT-1$lj`Zu}qCBL!_`H_aw z&+SD+{YSpzOZ64fx57HycG!LgV#oG4*gr;S|K6zG=;QaOXrBOOCfHN@8}z@@2de+f z@azWtN8{fd{@(%q-<@p^gPp^C!2@5|p5XHt;(0$-^Hplmm?ip{;qxlFBs>=S6|SY2 zLq4c_>=8cyALdG^^RKxliL}DZ05kS2+O}XKY|qu@+~E6#_D9`@vp+xn<9BhlV|)qo ze_9J(WBmuUf2{XbVpBZ?@qe5-+ZN`?*L@MNeRy9S_vPB0@IGh-2tx~uw5@vmRDY?1 zoSnvYO=u!pmjzkru#H?y5%no_{5coC_S$D$5%Qv@_Fv$Nv0A$tud}9W|JrM9n>Aot z#(PxMS-9GYpuQp+fB5w{)a0l>7v;#W*M%Yeqkczwankq~tOFT}?=%Byt^dLP*sq<| z`*%hBpnJ<K**41kBE5iqfFE){KenYhBKjc@=*N6}_o#CHK8D@d%2A%oUaJ$UTZf;6 zQ#@5~+mhqT`mwMxd<usz_?607Q=KEi=f4M6NV9&;uHW<ia9gd)8cQwOmTGc&geX_8 zPMa5Q$<=Lhf-CJ$znhEirTV`SP;31k_P<5;k9AOk|A+nQvm#wk`|)PmqWa@p0?<~6 zKie7wjQ3|A29;|so5<B;>q7ffaAjjyUzR%DooyV4*93f_HYY;pPlWB~euaEXj*utR z))IgCF-gdY+S-4QE2dq4O!w8siOvd5YtDZ}>Mi^loCj=#`fQ{FetXUWzP~qHkNkgt zVEpSVHeHSH2mthf#z2kpKd}FC%KxHlQUAf*57|F261<A?g&zt)?SCNJK`eKI05Jnu zt_a3B8*%(6zdzmsu_L;6t`J|gj_=P$BwC<0zar$z@6MHhW*WbA{F4@3Iog@4u?Xuc zHEUZB^q(tywLN}o%mTi<H_Hyx;p%_1PZWMbITbdi`#>}R+9$Ne`+#8oLb87t?jy;k zxj)qY$*<&3=!?4RKwt>VpEy(m^n3`*i{*oOG2P#hrH=3dR|Zvp<BJGS_?#Jle0=~o zBE)|`R~8Pk`=$1Wo!Iog=6t>TCA|{i3ERW|VfOI-7<cg9>4UT?>c3b6G#8&m`vG(T zYFz`4xuT_H|KUa*G5&kOmmrsncE#LYKej!75Zj&*#GxQz7%PYms)j&T5XVP8+Wa_C zp3resuH?lI25*1~gs*dD`3N2xYOT+Dt0}QA&<`rDnljZkEs!%RvOdNdY)U^HmP3Bz zU-T;#TICBdNV(;&-IpTL)sWM|RkW=j2ih#qwOhhGxEkHIFgvbhlZ*Sw=lAWwtpTL< z9L?%W$H49$fHF|)zF_`3ilL+z2led0a>n5ID>3(90Q>KV>xUZm5Vm8|aJD0HL{&_} zbG*J?hy)yiAENjfu4DxJFl)`usKb2yo{>h#C;FLau{EK6|Hu5wa2vf}>H{Wv8h<et z;`4%Pvo35aYIWOCv!UAE6nFk!p?>&XG1&a8wLkogSb&bABiWzo#;XGLu=a=S?*jY# zq3#&Sil&SNMzg{y7=`D+WFbbf9g~4XAw>A{om`pX@5E$fzP&b!OW^vxv1~mS?`!jQ zzk=BFt7FjGF+PlHn8I`U*>*-hYuH~{YpJGb1-00mP*1K#yLGG$#=i+?|0rAB3kqLO zdj;bhh5)9(7w-Y|)9%ob^grpw6n`zuBb%}9F|_W_5A#$(Z0EGmta$nuwo`~{IGz^5 zp$Ki!R6=l7ROd<sPlzwIb0yZ-uGTq|Z|PIgppl+fyHu-Q5yYQwjX~eR6(^lvv;K_g z&uhkbakUkG4%DYIU*_^5AG`-39&8Dv@h`N$0UKqE^<S{Rqni9t^w&4t|KsH)CJ7Dr zmN+oes|V&0aZgc<6YSp)^VP#x@r)2wGBXqyhx@sN2*vB77z=&~@jYA_UEy0Y$N6In zegC+x&I_tNEf4nPV$o+YNT5|nvwW^s_*$h>+t$pL+VY^qf$l$52NzyP+h&cB6Y*;+ z!W@XpnXA;`P?tk(mgZ=;Vy-qf)EK`RfZSg&_h;Ur71jQ|fp5D14{Jcq1n8--7`v{B z|Hh~(I$+&*A67gq2>0KOVI{M}uzo$F0wuG<@tOd$(9R00L<L8_k1O4k+Wa#AX{o9D zMZJ;}GX!guUD;SyGd9Y}goSxnu$cqVmlK9m>f0aSihie`uLZd1bw#~}Ut8hp+Uhwo z)#LhMeFo|?;Vzh~w}$<V*vcWu{a{0i<vMMfQtsyjeAE5ETn>PL7uXv)Kp6HHin4|M zeX-se_MaWfb|po!(z)Y-C_a*KECPCe*Bl|hlR4kVmE;k=zs&ZyX4ary-3q_L*DZXV zvS7G3GwI%u(V8;)yXufez0$0ECpLE|=6SzI+X}cc3mmHcncw?-hQC9V`U+_kp<c;F zZ6z1n*cR=Fx~vb@=eQ#G!&*J${z*QS+P|f&4DAPI3Vbp4<9+S{`}-1a2I_ZUS;Oew zUn}^3tTCB36t)ayrOD%2*}NEFf&g?3BnuIZV?HW4QUh0ZfipF6#Z2SZ#7<lc*Z-sg zNSkn)MW9#wtqi{Tz9iaT-a<o_ZJrQVyI$dR?TAXA5MPi7b#4FSdsDS&D9h!?`GVR? zMZR1`uTVX%KWa2S{5maaKUB9HZ_B^`3wuJ|LqB}i{vZ8&bZCj3@e^slxxTdTiv=sf zy8q&7gIVdEv26GJXtrm;L?DjsSqN~)6WMOG9OlR3Z-khDW8fRPvMVWyHH3B-+U{4h zEgLr!(JJFun3v5DeZE4o#%yKy01<6lsaHh(ifA4b@8|GC-$mCwz^Z3u{E4rut?=~< z>M3!EPh1TJb=hbi<Z4dv*Qos<_Z!j+zZnJXXWvDBH$IE<e&KyzHP-#`ew7C_;91&B zD%!p)D~QD2BC#&GuWtY=OM?9u#IU`K;@IBB3BV*li{nK?#Irq%fQ1ABM}+NHxUwR8 z*bny?E}+JL>)cDTA_f)D3}?+6e&6qciGFVDrj6L@s38J<`?<E|av&}b;^#nxQ-Ya_ zeDfO5OZ0QAA5-jEA2kSd6{@X#?pOG=71ZM5hoUAsfEC1e!~RtJH)AU(_6zF%y6u{z z;Il1(@7n(h8gM@407DHsVGaO$4Nq{u8bd!;HfJ>3yD*yVOG#k+mrQ2+mP`?}bSywh zBHkBb65Gddgn06Gu1p`+=ezr=df$mYC*PeG4!<RcKl4WW{q*N|RA|Wxr&rH`NZ(e? zfr@AFbD$YRJiq%p9rU`1)L8g>Mbxhpa<vtTG2|BlkfUQgQlu@X*JdGa8zA_7kS6tI z=)P}l;CuFd;_5(L9RPK}WrK8aPk{|9p5)ESW{+Td7ev9<li0y!Q-NvhKq^430Q>Pe zhoxwjf*(SB16Se)d3|?ZiQ2pRv4yreKmG_}&?t`|^ZnI7cZdV_K_gvTtF{%%fshXw zsD1n1bfi}@!v>(HLbVlsedRO1GL>p@;6-8(){6OK{CmJpTjRbzBiy%5za5L;h|*|# z6X)#;r~op+H$TkG#H0r3D%~d@O>n}VzvHpLP(1DXG8FcYWCxZevO{Sz*uk`!Rk0k; zm*aH-4lJJzeu$!yC&ZWPT!{(z{;`6Ks*y9KBeqNns}h6!ZH;UE+uA;(w%1!B4v<z6 z`W3FNo&$;GL91hezFF6pAm^y8t-!BTtF08Hw!+omzyYesQEh&E4CZJe%vl!pL?+!% z`rTBeCC&N%@czI3^_1q34;X?NkTcc@`+qng2Ml0)=Z9jfC&2DA*rAL$*uT6I^!!l9 zY<4JJh*>!1<6zpXYFw%03Gs!vGIz|NUt%A!Ta>@MAPxy*&m`t)*7KKrer8nA7qqQt z4iwGtBy)Tq)9$SF)q4;YhxD)6uT<1l#&I>c;_1*BlLKLYUsiy1Kl!*%f2s%V|AsaH zI*K2gH>gW{z;^@WfN#=$_`8Zne|;5QVGLN%55M;igZ+P|^kIAEj>7nlgI#B`!z+^5 zkri{<k(J4O(6IoA(H_oBss>T6fFsqovTjQFFR_nKmkw+n7k8@0AiJJgzvT1LNo@8= zzbd|^Y91uae}pYR2ckSEXYyDU>+i<8)==Zp?5J4Pr;u(D^((@<3TkjAGlDVx2V>2* z7uNh@t^@b#4KpI&ufnkJ%SoK0HP8mA1N_h<0JT8k(>QB2E(R1&a>xCtfoy+D6g!+g zogG~@mmSSoAcFZg2383%56APs4Iu<PA->eWmA!}`b)|o<?o0E8t0#rl7=z5TyZtiz z#}CFnceQHUc;rL1<Uy1-BM)*j*8S{UW<vi8^W~azX7Vejx6KJhTo?uW2Vh?g+?S0t z-^-!XgucIL`)0{7xF+yjy01D(Pz#D6A27(EBU?EPdw<6|;9f(2wr{~Wb~tScJGLsB zm1i$x<*OF~iv{f>b_~xs;Jss__y(?+{G^S%t&KkOE5aCL*yGpLmS&CeuU*?#<Uf^p zP<5^tRXdjsZCW9>uAVQ8)K}(DWM$B?R9h=eqVYc*`!V&!{0#2RhVLJ5hnf$@YJVNY zSM_BioncF=_trYsU)|O<Js;ewZBy9)Z^|d5vG3N7XzVkH`%m^HkA(J*!`Mz@<!fO3 zwJGfQ+NJFHx+Q2uk;1)Kz6LD^aAXbfq;{@M7}EE%ae;IDrq@V2Z<#%z!nah9L7m$D zx_!ttPL242wiV5Rs>Y!Gf;>pAn*X0P#OE`;Qjs%LZ3Xp|y{My5{jCgj*plf(p~<~4 zKVyxtiM9U7)o85Rs>;(^Ps;fk0X44k!|UJtKrLWq0PH`=pcBg;X^1^$oUnhvKz1Ny zJUhB#ChWb4omjsVNM$EBh=882UseUj*AYjA5akIS^SQEp(X_8>B%FMWe9%(uz>hyQ z#Gw7@Nw^RA=VFWSd)u{Y&S_cBuW-8dE84b74n+CTG^WtJNyRm&wpU<#i5E5G%v@b1 z1+^9U6fUML!1$kodiGR*t}eGd(u~D8;P)Re_WgC0-?o;kM{|Acfu9=t`1jnSPfz7m z;MHsL0TZ2jKnLP?C#Lwaee;9ak@Q4%e9e4za>Fuqa#K1xB?5YWa#I>Rxlt6$@f_H& z{EJ-a*zU*X|3>-XH?oAWN7xGD&ytwnivGrh{nYU=PyFspb!{uigDP^MYTC9sSGp?m z?-gi{`t%0)l8T(U2Cf|7ctIS1rkIbpwVA`Q7n=|Ku`OFO(twN4^jm-qY6^L{wx5ps ze9Kq<AGPF#q$>ya>crLsV;}GY>;XJ`Fgv(3iXF?E4SOzSr#Gjw(_1px>6{g40cvyT zIUlDsXR=e9Gel6ym+D;cbNaD5O-5owtue^a_}Aoq(o#|^f8Hcc*Vd?QSJbR+tMCO} zDPB04EeH={r5N|s^Pozu@aroFC_i2X|B!-tmt@SrPQyIJMC`|ex=gGSjeQl?Ur+g; z_D$>YU^Uv~@rN~krPV8s2I_Ta2Osc`)`dmG*X)=8A21d1YC$kNnlYK3Sht9s$;n`6 zwyk1kwq~Iff}V?FB|EcaCHO%Q@T3N=B#a#V-Q%-$^QP>0EitGpZ7%j2|21o}0zDm} zX`ydLbuArN*R~Zo5amLmd5~xh^kuG8_!6qKrDFUqrr19M8q5cN*p{ur{OsVKnCrpV zbXHfOd%S3kN3Hw2Ra*4>j}TktkPa9O9k6^TegiDtnbQG>-~*0lCt>_A=j?wjZ#6rc zw}zeLK*s{)qRq{&1UPcGf+xh6N}ep75c=KrpAh6P@GYPFm2m++-~D;$q0jz}l0s{C zXw^ba<C5n6OxKEN+sYiMI#(8aJ6AYgf*MN3OpJfjvF49syJpbXcY)?NV`D7^nqODx zjY{+Sw8n$(?Wu8ZkMFm5)qgRlw`v3M><-;iHp&e52aUHtJqY_x&L0IIFo~Vouo(7S z$<F1kVduB6W9JLj1JqXHT)|qrF9bM(zvc6U+DfjhnUnZg-{Wj*ea+6OCp*Q(otpiM zs?yKjm)A&6mTj05&(|&0wQYr_ty$at5Ld{bkPlgzgj(Bl`2HBSdk*D%-pKiE*gVwa z1`0I4wQ6gc=Tiiz&i~!DKKz?+df;9XAIdQXq8>2AM+ZE#$DBZac5q2JJHC20e0v%@ z51U@tv4LIOv5{RU6o8I7Y~bFb_b+S*KSUwIlO4<F)oAMmvh`U**n!X#x=FWN7nT?j z#KoHm-%`!5>|ecvyN@3iSmS4Y%j@;(%CMDFV=DA*b-luAT7j<pnzlXhja)fKdG%^= zU?pnm%TY&IfVhv^3)SH_V~t-BYJD_E-&d>MEm>*l9=Hy=uji-pJ$%L&A6OewKx@l} z7^<?h!N#lvdjjvB8_15PPlOI!z|Q5agpJp;i$$B+rJaNv4j0kVYn9l9W1x@^QLgM= zv-~q#SsChA@<oIz=gFsh!LOWvU#J7khKu?k)J4viIw`VJuW-7yQrn(_zOAZjp>e;M z2UX@kHF4!Qa^~a6r#YXodOkaniF^_D*4=XgF$ar#{ZWIN=-iE4--)sK$xyjD)%pK5 zzh9jU{`xAN@x5<J3x-&B#o8nME<Lp1frX=4`KqbVfvN1`_BEW%FPCg(d~8EY;6J}q zyp>%l-cl6;o)BNkw`PB4tF&noYvc;&OUR!HA?j1;7+mS|gZV`Lx^-E`)CqjeQq8Y? zrfav>pl!dGD;tqBbABY1tA`xRLhMgP-Dlot=<PnJzuK{d{`id-_+RqF_}y<>+e><% za{YhJ?|;St9p%x&I1ulqi8%;+>_OR|9a<XBPOMFW56HyW-pDTRg1t*~+11i~cBL$j zT`3hsF1xaekIQH;?*cyv0-iK$+^B*BQdpyTGJjnqUqrZa5k98Ur_k}|ex-a%7VD)| z_1<(`Q^n`kbSziD9?P1Q@O8aXUDuMv{RXb^b087DLR=B#$W%``zIFk|{#15gQ5b$p z0qX%!cUv>sm<`2#`c&)br=xgRxv{JU&b`+A`KsIPS3buYu}%D%KxdXR&;W738F64R z;=lxG!3FF>K^AP8!>;VkV^{YSuxop^bGVB3>TW_oCCYGIme1h|+W*(ycYsxOWo?^G zCS_7gqA^7y#g2+1RRKX%x(Fx;(m_$|Sg;E!hz+}-Sg{urL1{{pE`k*Wtk{jls7Yof znas@k-?h&@mwPE{%H;dL`E#FVamu}%v-aL=uf6uFOe>Zqb#YX>ojVFzk?5o$t-K0f zsZ2jelY_j8Wr)VcTlL=ikT3SHTBO2L4%V$bGJmPR1}WuQmT#MoZJVGK-Y-{TpDbu1 z2|VCH9BeDlDPnJRGG-+BKJq|>S%Kf8#v}MH?EC-d)AnQD>;Eomeq(gw-&6BL@PO;A zdk8k`L<c7JMQuQ?&l5uB+vihdQXJJB--j_xq3Z?dbfYj6a9jm4abFcOKp!%s)5>{a z%5e(u9`-_7X^gKlMJp94dui5~AX4Qos$<{${rBi_>~d<9uP9^{mRVHg73JC^<l3J} zEAl=Wv~dw@$oWH?Axp1<eQh52{ut<5-Qj;XkRqIVK<<`w{Ej+bL<8rK0RNG_USs3k z*gb`hz>mBS84ub_LayN4MebCzemtGowTLdIY^Ez&i5S;Zx>0nTZWd=zZE-e^3|Vzh zl!fn4)PaIdmd}|+p(Fh0e8%Ay(hBpISK=#*^`{&(z<UU^New>6nv)X0TDb-_$yZK5 zc6r$v^a`|c89b><!ADq6a`DJk=+YqbV?WKhwVdeTkoRqA#c%^*e_?(*tml^}fp<&5 zZ>r;KY<yqko}a#Eh%5_)T6aSp3TxPiJ*XsZ5}n(#m@XgNN>{TFVcuuZ%@T}%X%5{g z&Bd`k<WOyi44{t^nND7fR_b_*Do^2anWykkT7yP-H1soT`O$}zwk@`94N{X;SZ-03 zRTOKGkX7pBTBer<vh9m#MbHTL#@N499!;S0iLq3^1NOq$P~;Iu%x$=XSX+a7^IlII zYrf{quAh9+UNQE+o4ofjItVb(ie%kj0Q}Bp*!4iI0Mw^n=0&9&Lh0PzrBs!=4Km;n z%<oLPbqeEOc9L#0oX*EFAJ+^g>)=!#epd&-h*rQ;ZZ^VK%2M{z+i$(4Ht$=t_?R+x zZEBQOBw3|#c}0>{Ag}yPT45Tw!uuca5MDbj9oq%Izkw=tEylc=ioWwhEeA*15)5B{ zQ;a?O*4seqv8i@T1B}J*D(k;&>=oa?23<tFY$u-UqzArbk2=xbpn)lLZXeUYcFe70 z%<(L`a~fk`UPyNsDvBgfT=V_g<pmO)#!&$1<d@QllBeVdo)Y4#?qmK5#{T&3EsfGj z9Z!MWq9&^}&R1TYR=`WZJ8Diqjz|OFPl|^h4g4WD^4R+$kGm7?KpotX(DAUo)yF{V zJF8BgTA{E1qx!v99P37Z#<9b&K_28aihTv<4Aw(FN;}l103X;0KEO1Pww<ms4U~cg z%Jb>&nPNZ*-91|h;3I&q#r<NsgQEbMM&x6CT2ayo(@PVya_S_;KZja;RA+bp{L@yH zm$<8LEviQ=jjlmW@Rg=%1?!G-4Prhb?QgDRAAz0(eE$gcdVAnYy8&arTpIg?5Hsla zI*WM^+v^XuoxX5Fr~hx9_iD7@Z>jk<#y?A@ff){{Au|iQfu+Og)P_lPcJE^Ffvuo{ zBai_=0~G~y4`Y7s{3*H*xOcux0XRRW3Z=Ma8j+z1TB!iN$TT7!RcPf>UIx93eLaw- zcA6SgoN}OXz5-fNU4Qtz!5Z|+w4#t}A=8Te4)(%Vb1?Rq82e=CdBF2adEPGv&&Rxv zAK6Fjrxg3VADz2?=?Q#U*85+x-m49Iy^Hbf+rI_<!~vNGLalosM%fy5V_7D|zF>D0 zRUO>~dEgLe-~{G+A>F@#@xO4I9$YM^`xh$!d{n>%9PynJ&f{E3CrmG@bi(JL6(yZC z<STiH_fpu{V5-c`d?BrfHK<9xqAIJrgjQIN#hQ1M_cw*<82e<5{a);k;qwH2X-Qlt zc%%=;-jUY%nLxiI@qEN^{MET@n<4*~#$F}`UJD?zY+xE-ThW98-6$~>wc}CuqIm6S zs@M?@zPBE0;C{MQl1X>L2bcyPRF>0&OK0dI;K8M{I7-0x9#pDA1!zTv--=cgJf*QU zr~zM5$Sy)wQOGOxX@zB4@DYh`fCjMtxt5EuPv1=!55r#|VLo(w<1sIIk7rLyJPjxa zHWiljU~7Nstkas~_Wnor_wsoD(j)dPGsM0CG%(7%3&jSSQFf#=YQ_gq*_Ih}A#o*L z$=pe`MaLiy<beiCG3FKY@G{1|>KuJlbslg*apd#!_>SRR9jMR=)5|ZXm9m%el?Lli zQ?w$lIgoQ}OELCESod=dL;k@Y@4!kbhmN#x^=RlykXIG^<ynqBg>RK&>~A}DYs2=u z|F|vhmyYYt`9Fnz0KP-*u$O_ob|~_jBTxCs<=#RjIKOuhbOiB`2@cWiQzz*DxgvT9 zKJW<R|ET&RJ*ttR66Z4D9)Ab;sv7jcAn2q%t*Fom)65HL1^lE^<|%x95nriCE09(0 zv%Dg&JCHqC4!wo3zfo|EuAG1mL<)Q)A^)6;M@-s^0O(4cX~$R#_)@@@$MM?WmC);! z!;go3H~&wp^{+|?e!bf2qkY$yz5}7lm~7L74on_^nsrzY){KI_cotne1YU5QdBIV- zTb@e~upWE`KEUJu_(~-`zIqAA%k=ft%Q#noD|}yqM>y8e2-Av!cK$=O!m^5>6P8aP zm$1yrdzahbWwphS_wr!#&VXI#&}ynkm<N78mQF756m>e5d!j}o^yb2c!$|wzUIuOB z^uPGxFaK?0FVl#Zar+OTBiJXe2P3f_EJh!s!>*Mtc)>(EyL%z#33PDTdm$I3(Y-S# z!3$1fo}Uvmz+?aQwQBnMdNn-}#~N|Y*Wy0kyH+LO@zpAII$@gm4QPeeAEryywdVds z9)HN1kY%Mge-wLr*npBZpiNO!7C!}b8Us+H*_HO9KK)ea$b?Rh*Lp*(=bn0+EaU%I z@Oyc@n>wP#-~_b!2i9u@_320x2kO$cG5uiQMjdO&1*bPpAzl-zj&8u3xes)Z4m!xk zx?V<)tIuOhD>1&8>B)^6z!iFOvxdIGk>SaWtI|Dj&e!<Ob*2#+B-(jZT9Nn(cuNiQ zmAdujA<H3EkTtMg+`oW52*&+R8OwOk6K8?fAKOUhq3<{i8K(&REpPEK<WaPv=wU`; z4V3KnLe484(!H%V`s%-m-#0b(@_j{~!E~7yj6hAMxz74@D2(@supg}+fxerHK7?#= zG*03N#mBHuz`Ag;6#U>U=JG``&v}f+_}5;eZ)&dtuF^L*aSnK@IDdk32|yn=nMPz_ z8o2`cV326!iOgHTM;<c>zQMc$Yfz0OUp#<}%KMeOXQ6N6aW8;9APatvDH!+tOCjIQ zgg;COba}&(JIRq^gUo15KOM352^r5&>lxy-rdT4*Uhb3sACJ&!JHd8z%O5f80f;Ra z2|5Tz{hP#THjoY6AtR5VvQ5w*?^%fb;##_znLs!44?|AOr2FXmhm~awY2hY#0ml6) z=;7%t1x?(#j_Vt^&({(>!SM-x$Mhi4i$n{8rwCf$bwnC>p8H=_VPA^(-8&DRWd&># zCD7~T!M`mNKCegCLAF~2nni8|>~C4$bv(j}Hjd~^6RmZneI5I1g6E;ViLUw@T!ZnC z4|;(8iZLf*&@Vqq@}WcrbDa%n_oRW)8KA!PN`ESa?x<q>Y`Sn@3FcWGT|KdjZh{u> zl=Aw(@?tU8<#MbMEGsZ?xCGvR8N30q0`mx7CjdOY-!P4cHG<a%e9!U&e#_(i^)<}% zD;ES^@$*;?y$4#mQ=Uz?A(PxVd05D)Rp9X#4lWnEj8o8W7p@us+0Fy99p*dsx#Rm| z+)W|h37b8SyJqEZeT{y940_`|;H$6Q*3VdLHRi})nGOU$Fz-y$9rS49NYpf*Hwg2} z6YD8>!p2FEr{~gz{je`5ucexdEp#2S!p*`YF-CXGv%nutV(bdVdhxK5_Y$Wuhq153 zm_Dkiz<Gr@=P~Aa{h$)MhYKax#}$H}^00q9LAQ!iL1Rf`{i)8_3V9B3E(Z|vxHFu} zAgAzLXW3QAb)f@kv4=7Ed^h16EBR&%`R<}mFHL{ei8bExKklmbK!9HRPcZ%wkP)AX zbpg5~o=3qJT`9uVh_;XGkJvY7K?{Ye0>Kv$f3O99hZqy4hswh%sp=Sfk1$?WPwW&j z<c*UD1+QTGs4YyUTSX}XYKvft!aZJ7uIC*94ef>ewp09X737XfNvptbmeSc>^WoPv z1Nw>ypudq~%ySl@7AoqZuMIS($+kUdBxp4dYnWm`$9(>*S1-+BXxsl+vYqN!{+e@+ zJ%_LHbm$E#g`9}>lX=1j*u_GiGnqBmkk$nDrTx=v;UhIf$XbP~5SP4eG{$K>=I2zb z7l=*T3BUB+h)>xU1=%c`E*@M0zP(hyMXV)EGyEO@=FF~e{Ld^wPp39cq>}X^kVS*2 z0CSvm73qk1+A)3rE%WM4)9myx=3T_v&N^<URpwoQJwClOtbR?~|M*|^9`G}4+Z8-v zG4uxyg-rppz;lgb5O@s6T6L!dLyT!nKwrTh4j~ubaqx&d)M3wG=0OE9-q?TpVU6~O zOc^ME$F&Ii&BB!dRJg*I^6{H|$m_iSV0t<_$C36-wxJEdmKg6oG|gU*g8Oy_{dEy? zyvmsW3pf?}8)=Sa9fvyRe_Z{RJ;3`V9|H}0AtxAWUBp~Np3-*kiIwPq?I)~b=6yu4 zMK|acdeCgtfS*6ah!%O6U>r?prLVc*8|#Dm(rSNmTJB>CJyaj)rHyGG_8wuF*CGA7 zQ?R*?cn;H8AoLVG-mJ4=`onvlfxjoiMmve+mfy0Ce?05edw_G*1|TL4yaBdGtqT~J z9{_?E^H?!mh;ak2;4x&K8`Fq7bQJTM`AH!3@Z$IQKS5X0So2yAzN3}S>k({PhJVO% zuRcb<=P!j1lKz+Nd<``0KogUhF5ruhfw4OcdGe;8vGzA&?(#gA;77j4>k7v85`LS{ zK1|4?6bZi@U)W)dg7vjIkIEmPKYHMg9{8gN{^)@}df<;9_@f8@&-VaHpNMA76=&k` z4DAY6xxvkRtM2-rqS<xVwc^@Bc_TtxE1&ppaoOxo^7lVT*UjYXTE(^efC$C4`~bzJ zjDhra+<Z-Xfc!Xk^NJ56FMYzrb*+5a%tBm8D6gd#C@-ZKD6r+b0%PUt6fdlMo%F)G z*GYX=|GKvB`2vAFm1_+k6rpm>2&r5Ogz8<ZpeGPiK~Er9|5_DYM!5d9D!SZ&`q#Cp z??(gbU+1ZPuR-;%Bh<ccuGU=SCTwu6_H%SVy=%4RqXT~KwOaR}!+z^)_3mxf?2l{p z0j$>_ul?HVdgJ$}pL<=8e%^Y?b@O`Pi{u0<Ev$d7QIEdsUt84s-lE?832$&+tMWFU z&-Jg><}Wy8{cE)j2ZK}VF!1>L*J=%}&!3oosp4aS>j;$_;keGLcU`M;jjvT3AQ-v| z!h-**AS{+36$AzURzc8$pQnOAxVWxEKwQ?nP_cd}U#M8&lrNB$2U5PkLi&H@3lx`f z3zh4e;(^KqQ}I9z`S*%0Q(j6KDgRx;qw9VyHL33B;s>f<tA6z}&$$qEPk=w*wPu?5 z`@4MD7A6D7vV90>1^5`idFt68M*beB|0_O>569nV0W1J))8o1-lN*7FxEBE6Tx{IE z9HY<w|3C5J_j2D3eEs!i<4^;9jk)$G$Lu?QUgoCz<xLO0FCTg7YdnWf!gD@Ho!A@p zT|Pf$rqwb<`{Q?4;(w;%e?tMD0M0Y=d&QeG-W=cV+@;kA>lt5vBaOd@S$CvWo?R(n zR8Q2y?S-7@$Uip62w*7Alcwm=_E9}(g=aUKHn0O?C^Y|S+xfF%Za1FAW5s!r^Z@@! zJ3RMMU%fUTZgKDV<=trmJEDG0cjRg`q{3)ps#w>DE^jxdYX=}sC0Ww#6u?nSx|M7} z*AJM}<peW2v%Zh`ZN_W^+7Qs4Ciid8wOnp={j~W;Je%9WwOl#p&u>jvA8UQ|?zAC2 z+FT7o|Lq;8M<*kV>1?bS@a;$UGWye_oB{OY<Uo2_Xid+G2h#IWYXRSu;=E)aJu4bO z-{kkFud=M@=3z@Zx6zdH7a7y8F+FLrRXZBo{fo*^-g`R|xbr;WSmxIOzxKoZ@P$d+ z7TZVmZugJPfjud2p)uawmu?*HPxmvd=~1o?J;}GFZwhS$JT0;T{(ur2;0-9Xp>Iz~ z@SQkH?fW`!0NqQqqKli&D1V_5#rbukKqIXmdbRmzC*H|pVGH;*K76me{dO~L<n1k) zX59fbJPoLHg&EcC?N4`7t?9uD8~Q5S7X1#$wWY^-5`2BqmKeBAPYTekg1UC8wT;_Y zWKEBA22$<eepJ53l#-_!P^eW08qnd>T;M(hz_F#js12>(d8ds_x7HWuIdr0|xh7N* zXF=BvTGQ=RJGz$+$grdPnSkSV^nl@n9X-sFfZO#Ix2v(Xal7QU@tAS@@~!D^+5oED z(wDO58PjaM)3M9v<qAG!hc^5yTKW`uQk}cDE{k;TOxfYR>Fh=;x_ZEdYE$g#)=>#= zAG1eW>@gOAG<&*>Hr-CMrOfrF6gR6Ut(?-G5|<gnr=b5!+xE3$?Cxg_q^ceLD0hJ| zg*$d8=25_%X^H2<tIp}yz^jwy+IL33gRjK(2hG^g)kJ%`e%L|4jU)%Uam1c(G9=rJ zcJ0A@*7~Ah-V?l+n!opFGSvBqCU~@?gG-I5_Lz+{ZWuFp+`h`T!`!yT*xJyg1WPdv z(``BuuNim-&uQjMFa7lI&}!6p3+;cW%`m1@Ypm$f4m+ye=Rh_49I0l%16?^F!PSEf zXp;k71MH9LgB-rEvk3J{@XlL*q5(#&X!G1&Xp1e~1ue?-crU{iyw`><ZbE(YS%x&m zOzU4AKY4F0p6v^G;asrn_(e;sEzkCkH=x3$mUMQrJzd=4K$mtp(xqLFba}S~ReKz% zYOf<zGoW3aH9mMHyc=uZXD!~QDPA3^D$$0RCYc`jcpcZLSNEkoWA(_hSKG&LzVUh( z{=XNX$&agU>wQbzx>5FgGdjKAj?QfvMCZ2xwhf{S+a<V|FbHiLM3p-nsS@ovw#@Pu z;|@fW|J(YLcPM?exkQhkNv6x{T?46LktszE?m`yrK1s*#M*%q2y}`##S3_%*nf6bq z(@dyv`9L}y=SUSBov0$-iOw)=a>95F62R>`$L#`S#tit?IY5DoXoq&|_o!;WJzYD5 zc?P(4z@E-*K;6|auEC)7_xIj>V>W)v@=}8jWP*fMp54K3EU9><1C_3GqEqXgsEi@j z3GI=fe1j8}bGziWp<O`^9hLov2mh+`o%4DNn&i1wvmaxeU_%AbX0*huD_OR0m4WB8 z%%{eS+<Lcp2lmnLk4`bByeJzgjB$eh%wPe<s|Qob8VO3*4o15M(<z2}ZSxt}{#W5G zw>vVphnQ=Z6mzX|hdq_8>yP^9Ml@Vs^BL9zUe{RmR(`w<G(G2m-)7E2p0Y)#VX+iG zgv*@a%O^oWj5BQxwIr85T4dCz6*(BUrI=ABq}sNk)lT%;N58&4?YdCpE0*UQ=Guh> zN6@b==%NpW*>obvy|F-?_YcaCr=G^aO##TMF&i~l5IdN?$OZYK7@U!B$(cf3deG~y zzt*6C?F`!@)^ad>oDg@raxnGR`(?EifQWc@^LO7wZYl>c*Ul*B+F8uCGx7G6zu1!2 z`|1+%5$ECeya!U!5c0-8JT%c5x#R5-6X62?ED265bfHCleO_4eW7-fs%3H!dpUyPI ztX+dP%DBDg$hAN_ec7BghMJ1Grkrm)*8r#E22oLrH65I2NbWtpyo+ZA$oTvAXw!OJ z-*%|4X^L8lgD7<_>Ma7&=egqCg+6}o?HA%?rt<}IWxI$m8*TUVYEZRl`7X`%>P4vw zZIyFPNs~O+6!VRFNzn>BN}tK~f3^Po)*G+${!Of9?2kCxt}`8-Vu@Pes1rT|{%tcQ zi1vDUO++X7SY^(4MICq-+C9aV{_@tV<<o85;$2FK;5<ML=UM^N;|kCvXp;FzF`zKU z0edAgnl_*#wf*RwrNEzc0?oV)H3Js8cBACU{V8dR3nfh*BH+lhA>`2O#q?m_r48aq zoG5LMV$3jZF##6z(Yxw7pzs>CG2}LGp4eYGZqi(H5p#{_8`GoUAu4UlTRI3iS}kb) zARW^Ey!j?P^JB)}S6_4Da<}f3GR20DOc{#(t2-T<>?Yvzj~cH>{FgWXOp_c8DLHHi z#tgt?rWm(lvxd-N)O(*j#Dx0lw4oO7tJ9y(mn|rM4A*T(o5P)Hv~724_gQl>$69{y z4tZI2LhabW4ceBo2y5jWOVknUMh4Ozhik?&{xep3^hAAmd*I=Pd>%e@WV$!)7&nwK z!PWW=KPLn2&na$%J=*CeAZhAQj2FWYI>LZ)<94aGZTCb+TIyp-TgKQTXAI^QYMQ2E z%zAffyk?WG<|m?Us(j?cLe#{VZAC2Kn6z!VAGkDU{87hj(u!d{kh30n^XCNsMu^(# zF#&@F?hg8$Nmt`@GVQEE3p@tWkuY!MG8|4x(-=H~w+GrWOgV0cF?I||XrEf!&@N@$ zFlGlP4{p#m+&?n-TXj|P5#}WssOgc0y!wk=x{-dHk9OgAoZGb-{Fwrm4n<zI+0Ll> zIvRE8$594q0PhX+BOfd1KO#n<FM`o7)CE_x?dY69IyyUmj?DC<O(8>Q=R|jmn>X6# zRoAwub!|JOpvn4e`{-SD+LCeSqxPpCD*4D!&}8bY!J-EBd?y|1(OT4=7X07WP<y~! z$FAW0PN-cv9`!4wx~3;k%k;$JNhIpqE}9_Kw2d5#wvC~*`61-j-v~9E>gsm1Y0;df z47Px8uGF@aS$>o<)0dKGNc1S$rW`Z0ZHfok>vd4}w<_*@{$+~~B${M;1f)y@Z=7O5 zVFNo;hnDY8!*2xsT>oUERY&x{J!*5J2KwsRQXMu$ea0NrWz1eajj~beSJZAr%~rpG zy_<Y*s7pW0ztL*rc5E(aa!!yKv!k>8iQATpwyhuO)a2Tzul<FRk4SS3_)j*YVAFR0 zZvNIA)-wKlL|npGDbuXz<SN7>%XJG;S4`AtM?J!mYq(Y=>dT>R$D$|GTi`$3M~#7g z2MyFFo`~_9Kp_r&N$b-V^xivfQRg<F(qyzPEkbHrs)8oXbhH|M&RAO$!AGQVV_I~k z{bNkXqo>A0yjKgL{P-Jc9@sXj59O^KkNR22o5M9xHlr>MYH3OJrlX`9<Z%mW*;t=O z--m~Z`yN)tsNXxCW)62~^mo?1y3lb!kEpYZxwc`PXM-_#8#USwPWKah<QUWAEMGbr z=0xj#^m$L8i+8E*VNg%kKhhPo=okBmdJbi%?I7x=%5_q?b}rY=MLn?v!A;k|<GRMl zin_-3COsR)pW7J~I7EdeCzG9_y4>Mpf?8e*J|b$&hl4MM+0bk|9Wv2qF$>QSw2!0s zxOMIN-U!q7-)GKs6ty4Ea-Bca`;%&0Aa5nt+d>^2t_Qkl?!+eXniuFU)y+fwXDfs5 zP5#c$wy&VcJk(6(nupn_gBla!K_9;N7vdV9yJrTg@R3QBxd6OwQeTlP@11v=wUO~w zJ;HZ;Yg|BYDq4qpbX>Da&fU+o%D7J1LDVHf{{2&^@ADVbe8B&zLAy4etJS(S@7Y!D zd)41haC0E8^~ZJF3dMZGTtiLJ=D^?9xQBAC<*l3ro-zgTqQhu~haPHpeSQefQR{oT z4IDQ;*0R$-vrvQa?7mf^W(wC&sZ`{ZmTKroImLVTXo%Zb4`-?V9>$Jq*Ko}wRh;GX z*U`U;OJ*wSQYmWNiMh6zf(G@cW6NiuZCsCxYtn^N?(zws{ecAi>rYs>y36=Bc7&hp zwzYn}Q8Q#J@ZX`#vn=u{BNwKq34)xs&I2seaQm!f3p$ywPSj!GnhOV3EKvJi#+{EI zw*90!aB^KHG1pLo7WH(bI$TnnYUUwDap9B|<wZ-}^~kCF7klvkjrP6V7WO&EJa~E( z`+#%%VyFgr{6rqmT*Ur!9(zUp`Vqri1b!=LPocx>m(jVDJ<7Im4V-yn8?SY3)=QTv zP+vx>ou{ai$2B3iZiuKkR5#a7ZI3~Y=t*>7k|pPncw*MJt=gVf#<~8{*RZYkG{}UU zXY|t1t;kE3DRME39L$P5brGRssWtK=Ds!7E+9vYbB5!Hg;T>ddsHZjtM!Gswfjsu2 zmJjmai#lM+yynP##P#14bL}j6M=5F>aQ)f|R-ON?$aPj9)h4f@i`X3PZiu-&4*7ME zFXU7%Vm3J*U&^~xeKLcx_ib0?5mw}v7HyOA0E_(dd8riO<}BLKQ>PQ1J-SciWEJ%U zGLf58QL{#(Nkv|F%(V;238;0k3^h21Q}|$A8m6bQrb$Gb_##+e|IhF}&s^o(hf3oo zqn{H+u1hKJp(1Y`=fC7UhMZ>%Z4-HN<y?fwKX|<`i&}s7DRoDF{Bs$H6nQ44T(vSy ziah8dFER4XqW(%1a!#Jzw_4<yjCSuuUIv<(99Po>nlHS~Yl6Q?yTTa6N0r7+L_W6z z$bnNLa&U?KD4eqmIdbk_KrSWDk0WyRNp0i2p69YsX+U37x|p3R<^EM^o5&H)`FlAR zIOkqP%>b?^bMC+z)E)DqXg34$Las?W<Qse;k}vs^^;(DpIJ#hn0s176YSOldd?SyL zQ;PG&$T@|i98-$iW1MG9*)~N!LeBl8$YUz!EEPEfk=K}W-f|xFv&i$AiP~+m2lXJt zlI;Ga{oY8EkbhTu#-#q;{+T!(I?&zo5!ZSYIciQLp1le=4ivc!IIjV6<jA>k9wDcN z$Za6<XQ{T0$Luun*%W~<WFT+ZHsmLrOuIs@X-wbFKVco62^?O?CmX@>rMJ8dHTod_ z=0c>qF{MR%A}%vp#OpppZ1&fP>F3z}r?+lMz_~s|?glB(i)a_}Lve1N`^eF7yYx8X z^mkDCwpny!t}}%@_d-lh`%-TstwwdTFGcPZf52mk{s}?6QEjA~F&%)ur4;e%myfJL zo{%IFm;UgQ6pzla=p2{M?Yn=TbI6=P{PAwYq%J|6aR}lM?T`-xcpJB?@iS`2wu#@o z&zou%^C#pk80K%%A$7b}x1XXtd(+kl)|7-jdCu|xIu$z(xj90S&ny&qt;S&Q<41>P zLWer0KSiP4V=TKM7OUN1#L+lK{7l<iQ#k$7`=VBblxt*}pK-f1)G@s>!d&O+Xp64@ z7;T~RBVtIOV!W!r2T~Bz7tQfQ%-eqH`~I8$4}My3P=Zhe2<Ic5nl$+Mj6H*KtOZf> zF%PZgBR4+0Su^20WuZ7~G;1b&p`eZVfxnlJ;M{nO$Z9Q)@XV;?YxxLj&*SU*N8A$k zaf|@)Cqn%FmB;$;k*{TZ<YRrj<@U(!M7u?M<@Sdw`h{==qN2ZQ75#^Xv-2SLs|6n^ z0`6&i<WI9^?5D-x4ERs^X^FTw#8~?nYPcf4b}aOL(*+o6jPW(naPcy3tMhxuJo@N< z`Y!UG`@q(k><@eEc#C#4&!IC#57D6*_yIBS`CQ!N@Ax<P?Gebq-u%DluK&o#Q@>+N z<S<(lV5IqcwtYw1JVF=yW<&VOn9!*ergSdOjLvWFOBXhq)42_1RJIB}Fi|FyHr-JC zmj5TfNb5QHVx+H8>lVKulCN>D%|K(#Zx*?9fi28{ikF&E<@SE?F|whDu;V|<wTIt` zJ$x(d;8S4--w^=L*+1r9`ar7QX-Or^`v@PAC|4cesrdx4lfl3dGvSwixX&;)X;TMu zpu~y#RJ_cLs`uEyr^1mQWDSB}ixcny<PV~+3kCtNLGaT76gkpUhGIwfu{h#;d-%iH z!S3B(^mXE-UNi;%QHaw#_DlQH$4Em9x}_`2+;oH=$hpk};Wyy~Uk?}ft^iIr14juS zW)G%^IfLQ10>~XqkAOGh{#f8H;r|3}czkk@@O!D+*`IRe8^f-u!}))1U~TE}b9#~Y z`-n|_5a-*2@*>TJJ^n_LE7cwyLbr|qQis4F#TESsNOy&g0{}SRV>mA1F5~|&+nE@D zCGI&+qVKQ4hoE4w8GM^`xt`?xKtqv_?xkpoJS~Qr*Vp^#QhuZ*T}*JMD+h<twZm?7 zEy<0pGaPZF8_7cj+)Notr3ntSaaM0y1{&BCWlm)~*`G$j{SM=vp@O>-|G{(vcJ`tr z<^o4QBaN#s8|w(j8_@ZuFByt4jpz#>2UohhcNkUe^MLK%gQ^b<qZ)>T!{|z)J5?QU zBOkl=&?_~xpT75(Ka**XRy55Q_NNtAbUnpY>UX*C(eJm@T<GdSNBA+Ai?If+b6vhB z=M3{$o(t)-`oK=^Lg%-8(uJMF=_13f;Z(WXQ^2J?p1|9KLR=f_a`1et4h`Y67VpqR zkFHd-#Zl^e^gH+c&Ez4%N96=;|5N&R;Fxxv*9|@*rrt>F>Hcwsl)n`I74cqlW{bCg zvs=CC>^5&Yx7|y?`2;WISs4Z${j1pn@iHaOfBMsF<nP##D)$YgtB2e{H}2@ep;R7g zO9v;Qwk>#1W8;i*T@nTUc4Dp-WMtG8i1kIj7hfs^oZbkC_oec9A1dGELlv8SD0{X0 zuk!sE+OJ(5-RvKRF+#2x(0e5KBlB?84rG}T`3RmLngIQBH0QJQ2aW+$wALTFq5$js zkyC~NI^bA8Iu$2@kM*mMuWBFi`5S+F9eNZuiEegz0zVJ<lMJFmuww^so<Wth5No>M zEZBiFP_r|8i7(|X4?=x8z=|N`Bo3tfm4Q@%oJuE=Z>?}O@}e;A0LI<P^q2de99=wE zqML1?8^D=O?vyppf@axv0v&32$$jXnuW>ZquNS3bzjk6#5M@Q9_TA!<z!k7~1dSPF zLLVyZQ5vv4&GE8S;2%gy^F5TbRlkp3bj^0&qV?WtbaNUspTER`;sbiJAMp{!kN0(` zfBZaYf(dfg`as`23U(?9G9yR9Z=`Wu_8d>t$zC#oa+Z#uw_r1P5ia$=`!mLNyo+I@ zbW^y}osPht0)7?GST^%9XxDALMSDsM>qi+2f+<7R?ZZwO<tN!W>p!2byTR_685vB+ zqk^eTOO{0I=4eyr`W?QY*~2a9SQK*RsL)LT@-NABQ?SAtwoG#x-?u%n{OD((F=UQi z7w|lLVFNq?8<}MP3x!Wg=O*84)>Rwr34y&VMA$l;!b!gGXawG{NI^G(cBH<~T{ePp zmjwx$&jZcpE%QY!-Ttt5bS7_oO=n-!G>wAYH4}E&oEX&HguQ`nM8{#PWZRbfeAT0+ zZYMe%9s*ktY+<k$WkgOO-~J}_{yUA^OnbF!L&w5L3c5*zZFX7UAc~*hCi<TH|HLBH z_*Kw+)}lc8`q<JU=dQ46KqfcPn6=bRmrl$d2HU|b*zI7mTRWGwOdr_*xA*Y=nO=j? zj_KH|hpE_sB>QAK@Qn&^AulT<>fJ?~j5}%3cqiEHKr870PfJ7al|Lz;YtW%B9h?z> zaTr}k^9zEI)7F8O4DHUc%}mB`nOhIkCm#;mKsXg`jKpyv&GB=^`tT;T`lva*4V$8~ zxdH4~Qz##}tJ)md#*@2hCgrNwRC8iv+^12N!nV4=uW_5YsSbQpBF8J~J{@x)b><*i zGDLwN#&?k`eD9(>VK<C|t!*i6ZA&O-VgStxLeANZu+_vzQ87Tqy%6{p#-SEp?0ncv zQHK|{j{J21wwDV0=Sa3-6?^cqQNxt|rb;V(eqhdMlIi|<)Hv9a9WlP$6yvKkbgsRQ zsP)M;pUZcyLjCuZqW*hXLJVvd%VE0!Y+FXBfO`&XO3oHW)Ux^e^bu-Sn(B6@t+U5S zxGVZz;2tOA9wBhgg1w1tQ1x-+>n&kH65X>sd)Xw+RXfa&F3?qKxcKX9cb{O{fli=S zMfr|ps8bjVd&GJ=vv(bANo!$mS%Vr@Yp8tJYQQS$-M!(Q=J8KjIz#mR@ipPJcIrq< zge|iK_T1ul2C47;?06@v&WHYt8o<X_%vAP$?ux14FZ~67;rw7Se@>a&7qvB_kaK+# zYz+!XMDBgq`_3IeKJEi?l(>9ugXhcdTQxP97KZqsW_TU_oA=bAoVZBQ_e?h#Yvwhu z4_fwu%*FjK`hEe{&`?Sa?E|}v#Gmne_;F|*+Z<$snlDpCtw*kvUYU}BIzZcT&9DtN z$*r_v+L%W1Ytj4zD%r7$I%&3P@cjVep31&wx(OOQQ0+Hsrv(Fl(EL`^8QT;E-=!eh z=&#S~TQcLuAGT4?1`Xy$4@17)EuuaZ+Y72t&-*g!lwD2*9NSG>qh_f+8~-Y<7l#G| z|1GGk+R)eeO{{&V_pDRW&FNihC}OlXy$fE|S@TOZ+)KAaqTcaf$OoO-p3Ctr;={RO zv3LG989o|kb}xb52Q{X%lb~DYI`fABiLh}Tq>3XugpC;gq{LIZ%@<UOI(rvVb`aN- zR{f68-+{gN+~F;#!3;RGS)v`#4cCEQJ$;Oz8$t7X*I@j@=+Gon$XA-rRpl#25`1uJ zG<Z&aOaO8N?}ObURRO4joX54WQ$!7*ZXMPA)<Oe^1NTJQy0~%Lbhm|{Jn+ACY$t4X zJ3%*~9oV6^Ma>X&a}jjIc%Rv`68^33#A_MoEsAkc{dgK{eHvh_^(-;e0``c-$bFfL zS_NE}^#tmaphhcdG;*y@8w*qV6V`yPz|+ps4E47U0QaLbe6U@EzH6`1hRP0Y7kyuy zc>pyJ_6s``)6V5I&<^OP3UpJMyahGx!cZ&NlzpYYVZDGVZt^*=#nY@iiCQt$Y1@(S zIR`ZZxvm@6jpVwJxfI~;N(0P#3)_mo9rYF%|M=(#_^!OE#Ib{x29+E}9#ECO=lWYR z-CPlLgPP&M_1xa&qBg_S0gy|fOOf%Ze~iHX67wc=nR`$8F9srS=waBsiWRk|N{}zO zn9_D_p+lS2!FI>FgX?g=4cw1xjTQKHZm&%hX^C}xFKQ%bz~04mN>MXg&<^M(_lT$y zcXF{OEp^xBIh)2f)W=Kpn)h+o&)<miXZbM%&rC)>+%ni2IFGedx8ecUt-w95y>_4L zX2EvHxQ`9+p&nhk(8b&|34iW;*s<ikza?t8W+Ojt7IN;Ui`tF_EBq0!rq6XsuQfHU zs`&9a>q7zzwI9X>07v9i<=o0#`{gm$>!{&;`-*xlsNEvfT##@-usN2ll;nx_a4j%V zJ5JR7l4}PRp@s<Tr6nh*8ujM#A?wBY88U7USf^4Sj~89DJ_J1H>hfWFqJCA?(T(V{ z64W6<ered!L>&sQIiajCp{P3o+#iYhMKbQF8Nq$eb!E`cwZ)mpLArvn;s3nUy$9=F zZonqk@kO{a^&O8j_5sIv&K#O*h5hg(<g-mgpOphY<iLiV?5W6&&w25Y@0@dA!xkv= zUMp~aRC7V}{oRU_RFj@SC7_wa$(A$~&tzQ+>wB7d|Et`O;5mbN&AcZXzKq9a48%To z4C*|@A)k7-$hZCV)eDM#MU986sQUt(9wF~K=lth-AeBk0;g3I(Qc!1l(GXqMqdsNX zn4kSBIK0Ylm_ITP=XGYTV>j9wY>IleHi#4OBF+hWW@oslOTe`W`25s{i6ZxG`fO{| zYwbg`@jv{(SoatI+CF;`zPuLlex79t<gEL4e7}w~&#^nPO+RM1fdD?|d;A^$hWFQ; z*Y5YWZLw^|@+0p7f$un_eF5iukN2+pn-{hBW#1uMKU^@?XTqGq_9(SQp6yZL0Jh6m z0N4(t0bsim%s0aB1hbCvn1^$idxV{dj}ZV?=+!^s)^GZWz|$IgPaf{8;!}%jRea@k z$nBEbDYskn4gN>jFLFOA`i*T&ngC0{Z((O*8p9kI2D{7#e-o`!fxWdJ1)6C6h$F)z zzK8F?mbKh}OL~MZ|1-QdHo!#d`x&+!DK-H1nR$kEYIPqvA8$_QH$m^brZ@7*8&a&l zF3mu^O@N8!_uz3GUsd;wE^6@Mx|fg{=8wR8SHd<`7}E#wq*nA8ww0&F_J}jKqvxmW z=-V>DDO&*BU<T67L;a{^Wgqzd=^$=H`$x7d@v|Dd-~~6B&tMOJGQz0~<t^%i*mA@? z=Q+Z*;{ZE~iVda20Wr@Gh<65@a-eTZ?FBpoZV%E2Af~Q2ML2cl{d_U^;|pG?{K~hN zmM-*+ch(^8=i(N`OJ@$E2MRmGqg<zYHWJu(U>{-o&J(up05@UdfqjRMY>TSiV?}Wo z7p#Z3nP(_p)W`|r=x3~Xd&d}k#DgJbH`Q6#5F{IjGi)Hv!hR#!KnBC!0sD<?1A+ZR z9X|=TN4bt7hIt3#-~CK9@A>v_)4GvYH<N1JYL=|^?Lil|Ar|n6EA;F`gl$H$&#;}q zm98Cerjafk=#!5mozNGEfi%}^MI&9?(e?!<bS2qQVPleT6E>zSC+NbhX)SCrSl5di zqX%B2!UuIm+#C7~_KPctZqTCx*#07HFhhaUP;wifshs*~M}zY>poi~o^cii7=uNlM z>iX|a`d~Vb@tlWuU_M2w_5sUnK_=S&$e7m``erw}yvIXfUtt@|Fsg>0y!wDUm2GwY zS-N-q&Ml~Liyh*1*}mlpJDf9O-OMP6`G$&J3TxQrrS9Dk$Hlgj;dDM>I9=GmHWb!> z1E2@5-0eXX+uZ2m4;t6qHP#n=ua9mk(SO&I(0_o+9fN4O2li6>8u8o*LdS2U{e1@P z4`mxYsbZ5i><nxlVLOQzo!tr$dhX${kqoCj5w_5Ky}I6)W1Qvwt2yY7euW?R0t@gc zt?&8W;8Px)3nLFY_0sh|up_|!5yv(V9|}htUB@p!rfym-X~K|Rz|TwI<~6WOgK=-F z{jEQFk17@YcL{xXY1c5&m_1Fm#$H@s%Z>YC_3$1-9x7bTb^^9>_|rtxW~l$n06SfQ z-(2`O*8i@t>o%?4qZQ-&O8tkv<No8ZJ+s-J)_TKd4r__8k=E&b<IJE>_lG_=5c=Fe zN{#eZ)7Qw)?$WlUz;9!y!wcWvP3sfdJ_9kN8+~9y0<irk-WxU~X>7|k4X1;Xd&7TB zs}!=`<K$_mK@k-QeeDS7Ye&#@cgqIP=eU&xzE%Rilh6ft_Un!qf`;=?^V8;(iJE{# zYyDtn0j%?bT<nXn1t`Y0biEg3(Ecn}K0;pWACDowJ?l3!6gCL!UY*q5uhX^_B_N(h z#xHAe5bP&Gv}eXJ3UM^1em%5EN23+FnCei<0xvogK8*V6Y7pBSn!~;_#jQ8&K+@Ps zW9$okj16c|`v=VZAJb<$K|eTIK-SVw>V&ut!;TuH)An<k?P&uYa)^jilJUz}I1;*9 z36A4<JZgl%O~%j8u%T^faS&>OVQeMvr_8yw;{9m<<5c)w7sk$2=ws*7nu$Kp!_GpS z0PByT^NyKDnTRcl4sxN$fWdSG@hB&v$3ss)4lyfZpd$`}9vixB(SO0@ZKbc)e@s8N z`W^8<()eaa2Vl=JkjMWKe8f&4oMMgmlW3(5xn%QVg$`5FU#7*(Ag#|^DWCZ+bW3X| z_yE5dO8jz`PnG&_$t2n}J)lACu5<71O8hbwV(iA63HtLj)Y!Dv*8uw9Wze~<Ma;z- z#9XX~u6Pw<6ISAW4EZ@&Dc>#61xHhT%3d2z<~_R5+t9}|e#^(g&b5y9@UzL<Ks`QW zNig`%(#ezs{EjaQq19e`ybs)n^~iI&jSloUGwFOH{L~XSL$?|a{n$q6_7D>SAN4*x zbQ-*W`J^CXkn!Q;pWk=`c^#%eA0JKoA`x@+XDQ~W%_kotRw5kZJCE{#TUJy6g@OOD zJjpc{0*%}MH9OjyE<$g5iS<fHcR?Sv6W6G<2|cRofWB()G1l!O@S8oNVO-5T#M+$R ziT*?1rLGFcI^CRR_zh9$df|r|ABh;f!I*pPzvn#{@P>|0bIWqnYd(JvwTIx7Tn!yk zHFS4XnFrxBet^QqjZl04hKMlu!tbPDHz&3G+y{G>%vS5a;dcF~6!=s?SIRol(v5SW zAJF5rb4<ASu%2K<@Af}sFY$*iF$uaN$>;l89%2xnquaZBv3Sq>@BT&j=vSsgCkOm= zpl`whlrYSGfU^HCLZ2x2--RUj=^x&TK3k8PCqp5Zw)?KWJP5gc`TW5>=p5?Z-GrWn zb#k@P3*AJ$^OEGf^ht}4C4ck++21~uhT08KzPqnsFRDbmhv>iK=)X+#AFiv?Q9m(l zAI5Yi>;W@qo)css$kmFbD-SGx`Ri+*TNhv|YOLNlQ$Tm2SGf!Q)gAaPr|sT~I;@i3 z?G|)~wF>+!Obp3bPnRxb9~E&A*9CrQbPc+;tBU@shCcZm>NKzOHDN!Aa`q8dzF6gi z<sd&3?MJ&O*`n6aUg#;{%M1TwsRlKCs9Dzo9h>a;UJD<7YYTI#hTs033dAY^zgy60 z*5Q{8pZ*NGm=ufH+Wv(2fIId3UFG%S6mp;*^4vdK8(;?Ad>r0?Mygo|y$;vRRrm4c zddT(h<C=EtyIul3a!{Xo1+DjoZre!f4$JoS-`Ci+=m*r}Sm2D@ORGkqO&Rbxua;^t zqW0c5sKY1K+C!a1)MDd$t&gFX;+l$F)BC~2GU)!25SuuL=DYM{T}6ra&#aLr{P;nW zJTF%HBED!-7<6F!g&ygf+o%P2Tf#%C4_QNxuUEl8z8v+(cfiIsiB@<Sqn=Frf3RH5 zwAENUU*#UpO^nBuK$8xCorzfgji~*Yy2yu$Vke=#(0o7y6|SE^sj!{L1)9<fyRO8) z^@mK${rD>Hc$MEUpJZN*zT5<#=hLV${RlNK{=oph?nitNd#retCuP3&D$k`n$sL&8 zRiSIpfZm~bEmRSCP){&KKz+c_>}P)D|I1T6rxwrTXY)IhuCaJG>nV6++7s|g^%Urv zHj+(UGYx$Nzi*B21mGOJJr!&082$}j@v}a>4~GqHb#Nc8pCTMP(Y{c9)Q&Qvvzz+S zIY2>-skj%8?~tS6U!b)euS)0G^Ek0yk^K};$0FZUmMc9k8%EDhyVJLT@5<fj`wBPu z{)`*^4+qnkjs0Mo7e1WNeS2whE{-NY;I9)#nQH$ld8P?`+1%;TNiTX_Fq|G24yUh+ zJn2cXCw)`mLEn^m(9=^M^o-vNc$n*om}O%cjsIocZeuS%8}0Dye`djF>Q<UB-96z; z_p*HHezp(YKH*K9=l7*K!Fse|o+)K-v8M+o@m{?9YsI@C=X+2t{OTA#A49D{s_)>v zIrquoX?^HQl0V%z7C<*s1E@9)knT@5ecCG58~mR*e;@fv?9JQK_Q-y8H`|LI=6OS3 zF&r`ECd~7mtB!}*XAbR(eWf2&Cyu0=LnG+QVL;LdIvzW;!L#Le$j9y-T2R>@H@bV= z7khCJS`42t-otPkIHr80iS|Epqa5hMuF-UH&uFULJBlvt1MD9~=l6^t-41m!r2NeK z#}TmeiFTvyoWEpbZ>^u0c3IEL`NdAfj=(;6ES=dtmbT1tp<x4%AJNMU*P|#GzG$z% z*3jos^&NiDXV)m&d}+5A^vTc<!8QiIk)ANxm`Y+NAU0?MtqXNglVN>q^r&L%IBNY# zLt8LEpZCPJsEwJ8J*?dB%lk&sjxk2uN2!3CBZx=NUp1Ku)`XHlC-t$kHR_BQ(uw45 z)uX{Uv}@ghvQXbkZui-QF?4PRLkJz4-JhRd1-<ik>2qBv7d|HG(UX++V$PxCJ-~>v zV<u7l>dDwwOrnKBj%01vncBB*NtSx;DScTmEeNtDldc-%Y5|{;)xn5`8BeD-jf4M- z1M6(y->vc8armSa!G9zHei^3SJJL`~BiaQY5V2QTiM_yzF!<-d*GTvqO+jq?6!;BH zrnKm>YI{X~chUMuh_ji9IN?EfzQ%W$S65T#4xuu{fR@28gkxFRpQQxx@Sz@#^jV7! zNw-~FiVqtp?URJR%51E$Gx44oXiFV!wQcpW^4&?(ths-xd3<+{Lk>s8p0aGua{ZaT z>*>(4Icl-+=6YQzYwdh;G&7*S@RNw0Hd6TgO!unu)3G(umHcGz-fggv@c1&%i5S!c zdx+RN88mqZt(`MTZ9Lb_98ZhJ*Tu@d4cl&3Y$O$LT}Hh+NjCJ&v&M*a7p$8}5ui`O z+fkPb@yP!ySUVnZiOI0{BK{GwdgivZ%I6z)?;_qkeT0|t9@FNDc*N^sPg92Y;ojIg zRB&uNWbrJl*_dDdl-CBdW9xF*W3OZ<N^zz|C+G@dO4<J0t6LY^y(*fn<fM>|d0j3D zd)U=4!`6H$74_kH@1oeZu-%@0sd#?}9K?s;13US5$?%7~Q;{!ZG?r~AjSPS-9I_+J zwUD_Z!zRKX<!v(1?M9Wyk4UmC_5oLN(C(}xAyX$!cVPbZZJ^$lYB~DY_ic0^?SJL1 z8ZBf;j&poibxt9}O7d$>aW3^S)TgR~<H8=ycKBN&RueI(uu~sj>`SBY3|^yT+*Oa* zyG&&Mox0FN)LCU2jO#87Sx_OT-a(A$SE%p)5Vl?+KSOT5S(HX8^IXLG!t|p0%=+it z4(_WM#L%DF6A8OvIeq)!E<L|@o1WdhMbB>EMBJcMAN(F_!=B#8`>LLzPnyEJ{yTWR z;XN?ZRfJ;~+8;KE3J}MBc5f7XDWd3P>}1*(<|y(yj^zG8&I*4MP0jkxdD%7Bl;iaj z{gwe4tr~lf?-}@<?=f%Z_2Wg)fmsJOE)CeXnm4P3r6>>9qX<Y$46pi;f6xER&){eA zGx^yphq3bpAAhbK#<7L0O9bp<UJPEw{0yL3CEe2+%D;IPJcaKjc;O7YPKfg~f-c?? zd0z+6&7}U&-S!3j7|<+y$NUn{AKcV4BZN<f#?o=-?U2vF0y2RMY!pN2n_^dbTH->_ zN}XXd84UZ46YLfa@RjaEV-a@*dRW5$TmQMxlP;Ty_g3y4O!rQBLEafo4{|&qGkMTg zdBY%Mxzpo(H^jRPr6)zwd#)wg(oDOKyw_i>`aF)y8e`s`&TfGp|FJ--g^Y76&7W$H zcu|mZ7t-t8lAJ6wXl77PN?VV-o!NEosX5?CoV!eJJNGO1Mf!YO$TT64X+}e)8AVr< zf+=KZ<M~9iT7N*XGy6i0@j+X>p-b$??Y<6u^qZhd_Zc>w=`7@n3%e&mj+ub<d^}{B zaWsEaKQ+FJHYu+;)}nZuhiK3BBp;dyUoX)0P~f>|*Tey+!5T*8Tc^>Q$*$DjpaV_! z=|^XGgo5Wcp1+Rem|W<<<@W5GV$Nf}6VIzjU*L^(cn);&qlCY_j7P#8Kk^vRQ1@rw zr!ytYb3=P3&_%SRa^E=UAzio+1+C_-7%%jFfsS?Uv%wmdy?Q1cT@p&xMqS}I_BQgB zzCk0M%qV%uD2hNntc;Z*s4qK(&Lm8RUfP?V_nhgzXnmN__qY!<Cr5K5S{pVBxi>iP z#$u7{hJ6YP;m4VSTquR>=TeL2QqHjE$OUtJ#U#Yw&VV0$0Jrx$*w(M)t$}|b>%xQ{ zXA5O-UQX@4{6gscCV9CCeIe@#*`JjC8F}57`((?EQB(jQ*NpiBziP-bd-u;mtPIwX zYUpXI;NLYp*jM>``@SZW5Y0ZN?~;zT2BpVF)8>d!@jdI(O14RUt2-y!vHf5dXnWXf zhpxgOi~Y4&-w{1sTJw3`%-tPNlLPD4<A`xVLKif1q&H3Wb4Ol`&G0u}L(^?_ggt_3 z5dC;9XYEwsf5kED`!=j5tuNcqoQWY=m$PWl084?-`|rL>C--j^>*ARdti8xH!uq<j z=pY`88~iMuckGie&Y}bS*>(#*q^gpWRFsy4wTsuoGKycmh~9-?;KBH{3f&Fsy*S52 zCY?RF2J@}G@G0kK$sf#<<ue_0v0f(8(>pgLoe1LczlI*-d_fK+ZrcQ12=p_T;O`>d za|(Wbn`ov3`v+?-k)I<UrI<~v<>M_nQt|eMSl_PE_m3XXw-4{p^ZR$O=G8*yf%nv2 z!<t%7C*x)SFXTlBUy5jiU&+tqw)5Pc?WjvfmyL(4vJJjBM~Q1?R|21u#iMDKJ>(Sh zIrAm?|6X(yV}x<X|Lg%&1HM-PpYLHFBKLj$=lt;6Gpsw$uzvLv5Ftti)c;lE*Zf=g z|M(gFEMBiZ1ZV<&&bq}u#^{$7*xO&8WYzAk43GscgJ;EXd;=cW=trVYjkU0=|Epx3 z89m4x2%9^@0D72d1v^h~3U}&+y)E<x_0~N8U&KM>97aw@-Vc9U?nU2Kc+!J>SM2kL z(qGTI(%;TvT`F;;!!r@@W2BL-`V7c5>kff$KQ10ePfEsO{~kiFiX5yhVUM;o|B_Z= z&;J$n_}`X!(&1Ug#CUK+*e55#sq4S)WKE|B1+(d4!A!cFJC%6<jIY(8chAr9?9ucn zKLBm%@-OZW@S#<?F<x{#2eS0xc{JX;AEj;xp#|gZ)&7^C^-=S8=&QmodQ>zO`D~m( zTUs%YC#x^+kD}|xH&X8&l6|Oa`!<xeF-&d$Vc)kKWo?;2ck?3Y-pK`6$0o3hetBZQ zc7LrtmOy8r$7VlPjW0f>#8va?YQ{GB%!ZPug9VNAa-bcHC(saUV@lsRoocgI(e?Cb znq=Aj`%sMUjoiajp1g}v;#Ug&`JNREY4>ux13t!A;Nw~Yzi3YfNvCbD-<__Z=6ubu z^~CRH{I6y0gCF8ax_dg8(spc6wmWCfW^%Pbp1-N%=uqr3ao^w75jx~Vs)S8}@#k?U zNmwZS-@byL^3c{e@jFxK&5tHX&w3O7^z0iO;xmlW6XH<QJ`;A6Fy<F4*mq))yUD+w zT&n~RsG;gp1=OueC(1auo8nh25zq2;aUy;;>&+jWM_!ilT+~4}gxuP;E7JgYe_RIk z-`_vF5597TYR;a9JY7o<p;vaaMSl943-Bj|4<vZl*VislM$C9#gE)`2_^{sse17t> z<7Uy{o<61@zI{r6fA)m__RV8@a{o4c|M&r(4Ssd+24$`er2u2i-1>ZgpMf<acB!A$ zzpJwL(sz&U&`&@9KtKKcZ}jc`Yji1NJ1zF^%X^P?_5WAKgOA)Fl5S4BYN~aYzcB>& j(XL`TWm;GLEuTMwQ&=7xt|A~VGra60|BnBM|M&j^&~%xO literal 0 HcmV?d00001 diff --git a/test/test_windows_theme/windowstheme.lpi b/test/test_windows_theme/windowstheme.lpi new file mode 100644 index 0000000..596df4a --- /dev/null +++ b/test/test_windows_theme/windowstheme.lpi @@ -0,0 +1,140 @@ +<?xml version="1.0" encoding="UTF-8"?> +<CONFIG> + <ProjectOptions> + <Version Value="12"/> + <PathDelim Value="\"/> + <General> + <SessionStorage Value="InProjectDir"/> + <Title Value="windowstheme"/> + <Scaled Value="True"/> + <ResourceType Value="res"/> + <UseXPManifest Value="True"/> + <XPManifest> + <DpiAware Value="True"/> + </XPManifest> + </General> + <BuildModes> + <Item Name="Debug" Default="True"/> + <Item Name="Release"> + <CompilerOptions> + <Version Value="11"/> + <PathDelim Value="\"/> + <Target> + <Filename Value="windowstheme"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <CodeGeneration> + <SmartLinkUnit Value="True"/> + <Optimizations> + <OptimizationLevel Value="3"/> + </Optimizations> + </CodeGeneration> + <Linking> + <Debugging> + <GenerateDebugInfo Value="False"/> + <RunWithoutDebug Value="True"/> + </Debugging> + <LinkSmart Value="True"/> + <Options> + <Win32> + <GraphicApplication Value="True"/> + </Win32> + </Options> + </Linking> + <Other> + <ConfigFile> + <WriteConfigFilePath Value=""/> + </ConfigFile> + </Other> + </CompilerOptions> + </Item> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + <UseFileFilters Value="True"/> + </PublishOptions> + <RunParams> + <FormatVersion Value="2"/> + </RunParams> + <RequiredPackages> + <Item> + <PackageName Value="bgracontrols"/> + </Item> + <Item> + <PackageName Value="LCL"/> + </Item> + </RequiredPackages> + <Units> + <Unit> + <Filename Value="windowstheme.lpr"/> + <IsPartOfProject Value="True"/> + </Unit> + <Unit> + <Filename Value="umain.pas"/> + <IsPartOfProject Value="True"/> + <ComponentName Value="frmWindowsTheme"/> + <HasResources Value="True"/> + <ResourceBaseClass Value="Form"/> + </Unit> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <PathDelim Value="\"/> + <Target> + <Filename Value="windowstheme"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Parsing> + <SyntaxOptions> + <IncludeAssertionCode Value="True"/> + </SyntaxOptions> + </Parsing> + <CodeGeneration> + <Checks> + <IOChecks Value="True"/> + <RangeChecks Value="True"/> + <OverflowChecks Value="True"/> + <StackChecks Value="True"/> + </Checks> + <VerifyObjMethodCallValidity Value="True"/> + </CodeGeneration> + <Linking> + <Debugging> + <DebugInfoType Value="dsDwarf3"/> + <UseHeaptrc Value="True"/> + <TrashVariables Value="True"/> + <UseExternalDbgSyms Value="True"/> + </Debugging> + <Options> + <Win32> + <GraphicApplication Value="True"/> + </Win32> + </Options> + </Linking> + <Other> + <ConfigFile> + <WriteConfigFilePath Value=""/> + </ConfigFile> + </Other> + </CompilerOptions> + <Debugging> + <Exceptions> + <Item> + <Name Value="EAbort"/> + </Item> + <Item> + <Name Value="ECodetoolError"/> + </Item> + <Item> + <Name Value="EFOpenError"/> + </Item> + </Exceptions> + </Debugging> +</CONFIG> diff --git a/test/test_windows_theme/windowstheme.lpr b/test/test_windows_theme/windowstheme.lpr new file mode 100644 index 0000000..a9f43ae --- /dev/null +++ b/test/test_windows_theme/windowstheme.lpr @@ -0,0 +1,25 @@ +program windowstheme; + +{$mode objfpc}{$H+} + +uses + {$IFDEF UNIX} + cthreads, + {$ENDIF} + {$IFDEF HASAMIGA} + athreads, + {$ENDIF} + Interfaces, // this includes the LCL widgetset + Forms, umain + { you can add units after this }; + +{$R *.res} + +begin + RequireDerivedFormResource:=True; + Application.Scaled:=True; + Application.Initialize; + Application.CreateForm(TfrmWindowsTheme, frmWindowsTheme); + Application.Run; +end. +