| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375 |
- unit Img32.Transform;
- (*******************************************************************************
- * Author : Angus Johnson *
- * Version : 4.7 *
- * Date : 6 January 2025 *
- * Website : http://www.angusj.com *
- * Copyright : Angus Johnson 2019-2025 *
- * Purpose : Affine and projective transformation routines for TImage32 *
- * License : http://www.boost.org/LICENSE_1_0.txt *
- *******************************************************************************)
- interface
- {$I Img32.inc}
- uses
- SysUtils, Classes, Math, Types, Img32, Img32.Vector;
- type
- TMatrixD = array [0..2, 0..2] of double;
- //Matrix functions
- function IsIdentityMatrix(const matrix: TMatrixD): Boolean;
- {$IFDEF INLINE} inline; {$ENDIF}
- function IsValidMatrix(const matrix: TMatrixD): Boolean;
- {$IFDEF INLINE} inline; {$ENDIF}
- function Matrix(const m00, m01, m02, m10, m11, m12, m20, m21, m22: double): TMatrixD;
- function MatrixDeterminant(const matrix: TMatrixD): double;
- function MatrixAdjugate(const matrix: TMatrixD): TMatrixD;
- function MatrixInvert(var matrix: TMatrixD): Boolean;
- // Note: Matrix multiplication IS NOT commutative hence ...
- procedure MatrixMultiply(var matrix1: TMatrixD; const matrix2: TMatrixD);
- procedure MatrixMultiply2(const matrix1: TMatrixD; var matrix2: TMatrixD);
- procedure MatrixApply(const matrix: TMatrixD;
- var x, y: double); overload; {$IFDEF INLINE} inline; {$ENDIF}
- procedure MatrixApply(const matrix: TMatrixD;
- var pt: TPointD); overload; {$IFDEF INLINE} inline; {$ENDIF}
- procedure MatrixApply(const matrix: TMatrixD; var rec: TRect); overload;
- procedure MatrixApply(const matrix: TMatrixD; var rec: TRectD); overload;
- procedure MatrixApply(const matrix: TMatrixD; var path: TPathD); overload;
- procedure MatrixApply(const matrix: TMatrixD; var paths: TPathsD); overload;
- procedure MatrixApply(const matrix: TMatrixD;
- img: TImage32; scaleAdjust: Boolean = false); overload; {$IFDEF INLINE} inline; {$ENDIF}
- procedure MatrixApply(const matrix: TMatrixD;
- img, targetImg: TImage32; scaleAdjust: Boolean = false); overload; {$IFDEF INLINE} inline; {$ENDIF}
- procedure MatrixSkew(var matrix: TMatrixD; angleX, angleY: double);
- procedure MatrixScale(var matrix: TMatrixD; scale: double); overload;
- procedure MatrixScale(var matrix: TMatrixD; scaleX, scaleY: double); overload;
- procedure MatrixRotate(var matrix: TMatrixD; angRad: double); overload;
- procedure MatrixRotate(var matrix: TMatrixD; const center: TPointD; angRad: double); overload;
- procedure MatrixTranslate(var matrix: TMatrixD; dx, dy: double);
- // The following MatrixExtract routines assume here is no skew
- procedure MatrixExtractScale(const mat: TMatrixD; out scale: double); overload;
- procedure MatrixExtractScale(const mat: TMatrixD; out X, Y: double); overload;
- procedure MatrixExtractTranslation(const mat: TMatrixD; out dx, dy: double);
- procedure MatrixExtractRotation(const mat: TMatrixD; out angle: double);
- // MatrixExtractAll - except skew :)
- function MatrixExtractAll(const mat: TMatrixD; out angle: double;
- out scale, trans: TPointD): Boolean;
- // AffineTransformImage: will automagically translate the image
- // Note: when the "scaleAdjust" parameter is enabled, it prevents antialiasing
- // from extending way outside of images when they are being enlarged
- // significantly (> 2 times) and rotated concurrently
- function AffineTransformImage(img: TImage32; matrix: TMatrixD;
- scaleAdjust: Boolean = false): TPoint; overload; {$IFDEF INLINE} inline; {$ENDIF}
- function AffineTransformImage(img, targetImg: TImage32; matrix: TMatrixD;
- scaleAdjust: Boolean = false): TPoint; overload;
- // ProjectiveTransform:
- // srcPts, dstPts => each path must contain 4 points
- // margins => the margins around dstPts (in the dest. projective).
- // Margins are only meaningful when srcPts are inside the image.
- function ProjectiveTransform(img: TImage32;
- const srcPts, dstPts: TPathD; const margins: TRect): Boolean; overload; {$IFDEF INLINE} inline; {$ENDIF}
- function ProjectiveTransform(img, targetImg: TImage32;
- const srcPts, dstPts: TPathD; const margins: TRect): Boolean; overload;
- function SplineVertTransform(img: TImage32; const topSpline: TPathD;
- splineType: TSplineType; backColor: TColor32; out offset: TPoint): Boolean; overload; {$IFDEF INLINE} inline; {$ENDIF}
- function SplineVertTransform(img, targetImg: TImage32; const topSpline: TPathD;
- splineType: TSplineType; backColor: TColor32; out offset: TPoint): Boolean; overload;
- function SplineHorzTransform(img: TImage32; const leftSpline: TPathD;
- splineType: TSplineType; backColor: TColor32; out offset: TPoint): Boolean; overload; {$IFDEF INLINE} inline; {$ENDIF}
- function SplineHorzTransform(img, targetImg: TImage32; const leftSpline: TPathD;
- splineType: TSplineType; backColor: TColor32; out offset: TPoint): Boolean; overload;
- type
- PWeightedColor = ^TWeightedColor;
- TWeightedColor = {$IFDEF RECORD_METHODS} record {$ELSE} object {$ENDIF}
- private
- fAddCount : Integer;
- fAlphaTot : Int64;
- fColorTotR: Int64;
- fColorTotG: Int64;
- fColorTotB: Int64;
- function GetColor: TColor32;
- public
- procedure Reset; overload; {$IFDEF INLINE} inline; {$ENDIF}
- procedure Reset(c: TColor32; w: Integer = 1); overload; {$IFDEF INLINE} inline; {$ENDIF}
- procedure Add(c: TColor32; w: Integer); overload; {$IFDEF INLINE_COMPATIBLE} inline; {$ENDIF}
- procedure Add(c: TColor32); overload; {$IFDEF INLINE} inline; {$ENDIF}
- procedure Add(const other: TWeightedColor); overload;
- {$IFDEF INLINE} inline; {$ENDIF}
- procedure Subtract(c: TColor32; w: Integer); overload;
- procedure Subtract(c: TColor32); overload; {$IFDEF INLINE} inline; {$ENDIF}
- procedure Subtract(const other: TWeightedColor); overload;
- {$IFDEF INLINE} inline; {$ENDIF}
- function AddSubtract(addC, subC: TColor32): Boolean; {$IFDEF INLINE_COMPATIBLE} inline; {$ENDIF}
- function AddNoneSubtract(c: TColor32): Boolean; {$IFDEF INLINE_COMPATIBLE} inline; {$ENDIF}
- procedure AddWeight(w: Integer); {$IFDEF INLINE} inline; {$ENDIF}
- property AddCount: Integer read fAddCount;
- property Color: TColor32 read GetColor;
- property Weight: integer read fAddCount;
- end;
- TArrayOfWeightedColor = array of TWeightedColor;
- const
- IdentityMatrix: TMatrixD = ((1, 0, 0),(0, 1, 0),(0, 0, 1));
- implementation
- uses Img32.Resamplers;
- resourcestring
- rsInvalidScale = 'Invalid matrix scaling factor (0)';
- const
- DivOneByXTableSize = 1024;
- {$IFDEF CPUX86}
- // Use faster Trunc for x86 code in this unit.
- Trunc: function(Value: Double): Integer = __Trunc;
- {$ENDIF CPUX86}
- var
- // DivOneByXTable[x] = 1/x
- DivOneByXTable: array[0 .. DivOneByXTableSize -1] of Double;
- //------------------------------------------------------------------------------
- // Matrix functions
- //------------------------------------------------------------------------------
- function IsIdentityMatrix(const matrix: TMatrixD): Boolean;
- begin
- result := (matrix[0,0] = 1) and (matrix[0,1] = 0) and (matrix[0,2] = 0) and
- (matrix[1,0] = 0) and (matrix[1,1] = 1) and (matrix[1,2] = 0) and
- (matrix[2,0] = 0) and (matrix[2,1] = 0) and (matrix[2,2] = 1);
- end;
- //------------------------------------------------------------------------------
- function IsValidMatrix(const matrix: TMatrixD): Boolean;
- begin
- result := matrix[2][2] = 1.0;
- end;
- //------------------------------------------------------------------------------
- function Matrix(const m00, m01, m02, m10, m11, m12, m20, m21, m22: double): TMatrixD;
- begin
- Result[0,0] := m00; Result[0,1] := m01; Result[0,2] := m02;
- Result[1,0] := m10; Result[1,1] := m11; Result[1,2] := m12;
- Result[2,0] := m20; Result[2,1] := m21; Result[2,2] := m22;
- end;
- //------------------------------------------------------------------------------
- function Det4(a1, a2, b1, b2: double): double; {$IFDEF INLINE} inline; {$ENDIF}
- begin
- Result := a1 * b2 - a2 * b1;
- end;
- //------------------------------------------------------------------------------
- function Det9(a1, a2, a3, b1, b2, b3, c1, c2, c3: double): double;
- {$IFDEF INLINE} inline; {$ENDIF}
- begin
- Result := a1 * Det4(b2, b3, c2, c3) -
- b1 * Det4(a2, a3, c2, c3) +
- c1 * Det4(a2, a3, b2, b3);
- end;
- //------------------------------------------------------------------------------
- function MatrixDeterminant(const matrix: TMatrixD): double;
- {$IFDEF INLINE} inline; {$ENDIF}
- begin
- Result := Det9(matrix[0,0], matrix[1,0], matrix[2,0],
- matrix[0,1], matrix[1,1], matrix[2,1],
- matrix[0,2], matrix[1,2], matrix[2,2]);
- end;
- //------------------------------------------------------------------------------
- function MatrixAdjugate(const matrix: TMatrixD): TMatrixD;
- begin
- //https://en.wikipedia.org/wiki/Adjugate_matrix
- Result[0,0] := Det4(matrix[1,1], matrix[1,2], matrix[2,1], matrix[2,2]);
- Result[0,1] := -Det4(matrix[0,1], matrix[0,2], matrix[2,1], matrix[2,2]);
- Result[0,2] := Det4(matrix[0,1], matrix[0,2], matrix[1,1], matrix[1,2]);
- Result[1,0] := -Det4(matrix[1,0], matrix[1,2], matrix[2,0], matrix[2,2]);
- Result[1,1] := Det4(matrix[0,0], matrix[0,2], matrix[2,0], matrix[2,2]);
- Result[1,2] := -Det4(matrix[0,0], matrix[0,2], matrix[1,0], matrix[1,2]);
- Result[2,0] := Det4(matrix[1,0], matrix[1,1], matrix[2,0], matrix[2,1]);
- Result[2,1] := -Det4(matrix[0,0], matrix[0,1], matrix[2,0], matrix[2,1]);
- Result[2,2] := Det4(matrix[0,0], matrix[0,1], matrix[1,0], matrix[1,1]);
- end;
- //------------------------------------------------------------------------------
- procedure MatrixApply(const matrix: TMatrixD; var x, y: double);
- var
- tmpX: double;
- begin
- tmpX := x;
- x := tmpX * matrix[0, 0] + y * matrix[1, 0] + matrix[2, 0];
- y := tmpX * matrix[0, 1] + y * matrix[1, 1] + matrix[2, 1];
- end;
- //------------------------------------------------------------------------------
- procedure MatrixApply(const matrix: TMatrixD; var pt: TPointD);
- var
- tmpX: double;
- begin
- tmpX := pt.x;
- pt.X := tmpX * matrix[0, 0] + pt.Y * matrix[1, 0] + matrix[2, 0];
- pt.Y := tmpX * matrix[0, 1] + pt.Y * matrix[1, 1] + matrix[2, 1];
- end;
- //------------------------------------------------------------------------------
- procedure MatrixApply(const matrix: TMatrixD; var rec: TRect);
- var
- path: TPathD;
- begin
- if not IsValidMatrix(matrix) then Exit;
- path := Rectangle(rec);
- MatrixApply(matrix, path);
- rec := GetBounds(path);
- end;
- //------------------------------------------------------------------------------
- procedure MatrixApply(const matrix: TMatrixD; var rec: TRectD);
- var
- path: TPathD;
- begin
- if not IsValidMatrix(matrix) then Exit;
- path := Rectangle(rec);
- MatrixApply(matrix, path);
- rec := GetBoundsD(path);
- end;
- //------------------------------------------------------------------------------
- procedure MatrixApply(const matrix: TMatrixD; var path: TPathD);
- var
- i, len: integer;
- tmpX: double;
- pp: PPointD;
- begin
- len := Length(path);
- if (len = 0) or IsIdentityMatrix(matrix) or
- not IsValidMatrix(matrix) then Exit;
- pp := @path[0];
- for i := 0 to len -1 do
- begin
- tmpX := pp.X;
- pp.X := tmpX * matrix[0, 0] + pp.Y * matrix[1, 0] + matrix[2, 0];
- pp.Y := tmpX * matrix[0, 1] + pp.Y * matrix[1, 1] + matrix[2, 1];
- inc(pp);
- end;
- end;
- //------------------------------------------------------------------------------
- procedure MatrixApply(const matrix: TMatrixD; var paths: TPathsD);
- var
- i,j,len: integer;
- tmpX: double;
- pp: PPointD;
- begin
- if not Assigned(paths) or IsIdentityMatrix(matrix) or
- not IsValidMatrix(matrix) then Exit;
- for i := 0 to High(paths) do
- begin
- len := Length(paths[i]);
- if len = 0 then Continue;
- pp := @paths[i][0];
- for j := 0 to High(paths[i]) do
- begin
- tmpX := pp.X;
- pp.X := tmpX * matrix[0, 0] + pp.Y * matrix[1, 0] + matrix[2, 0];
- pp.Y := tmpX * matrix[0, 1] + pp.Y * matrix[1, 1] + matrix[2, 1];
- inc(pp);
- end;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure MatrixApply(const matrix: TMatrixD;
- img: TImage32; scaleAdjust: Boolean);
- begin
- AffineTransformImage(img, matrix, scaleAdjust);
- end;
- //------------------------------------------------------------------------------
- procedure MatrixApply(const matrix: TMatrixD;
- img, targetImg: TImage32; scaleAdjust: Boolean);
- begin
- AffineTransformImage(img, targetImg, matrix, scaleAdjust);
- end;
- //------------------------------------------------------------------------------
- procedure MatrixMultiply(var matrix1: TMatrixD; const matrix2: TMatrixD);
- var
- i, j: Integer;
- m: TMatrixD;
- begin
- for i := 0 to 2 do
- for j := 0 to 2 do
- m[i, j] :=
- (matrix1[i, 0] * matrix2[0, j]) +
- (matrix1[i, 1] * matrix2[1, j]) +
- (matrix1[i, 2] * matrix2[2, j]);
- matrix1 := m;
- end;
- //------------------------------------------------------------------------------
- procedure MatrixMultiply2(const matrix1: TMatrixD; var matrix2: TMatrixD);
- var
- i, j: Integer;
- m: TMatrixD;
- begin
- for i := 0 to 2 do
- for j := 0 to 2 do
- m[i, j] :=
- (matrix1[i, 0] * matrix2[0, j]) +
- (matrix1[i, 1] * matrix2[1, j]) +
- (matrix1[i, 2] * matrix2[2, j]);
- matrix2 := m;
- end;
- //------------------------------------------------------------------------------
- procedure MatrixScale(var matrix: TMatrixD; scaleX, scaleY: double);
- var
- m: TMatrixD;
- begin
- m := IdentityMatrix;
- if (scaleX = 0) or (scaleY = 0) then
- raise Exception(rsInvalidScale);
- if ValueAlmostOne(scaleX) and ValueAlmostOne(scaleY) then Exit;
- m[0, 0] := scaleX;
- m[1, 1] := scaleY;
- MatrixMultiply(matrix, m);
- end;
- //------------------------------------------------------------------------------
- procedure MatrixScale(var matrix: TMatrixD; scale: double);
- begin
- if (scale = 0) or (scale = 1) then Exit;
- MatrixScale(matrix, scale, scale);
- end;
- //------------------------------------------------------------------------------
- procedure MatrixRotate(var matrix: TMatrixD;
- const center: TPointD; angRad: double);
- var
- m: TMatrixD;
- sinA, cosA: double;
- begin
- if not PointsEqual(center, NullPointD) then
- begin
- NormalizeAngle(angRad);
- if angRad = 0 then Exit;
- {$IFNDEF CLOCKWISE_ROTATION_WITH_NEGATIVE_ANGLES}
- angRad := -angRad; //negated angle because of inverted Y-axis.
- {$ENDIF}
- m := IdentityMatrix;
- MatrixTranslate(matrix, -center.X, -center.Y);
- GetSinCos(angRad, sinA, cosA);
- m := IdentityMatrix;
- m[0, 0] := cosA; m[1, 0] := sinA;
- m[0, 1] := -sinA; m[1, 1] := cosA;
- MatrixMultiply(matrix, m);
- MatrixTranslate(matrix, center.X, center.Y);
- end
- else
- MatrixRotate(matrix, angRad)
- end;
- //------------------------------------------------------------------------------
- procedure MatrixRotate(var matrix: TMatrixD; angRad: double);
- var
- m: TMatrixD;
- sinA, cosA: double;
- begin
- NormalizeAngle(angRad);
- if angRad = 0 then Exit;
- {$IFNDEF CLOCKWISE_ROTATION_WITH_NEGATIVE_ANGLES}
- angRad := -angRad; //negated angle because of inverted Y-axis.
- {$ENDIF}
- m := IdentityMatrix;
- GetSinCos(angRad, sinA, cosA);
- m := IdentityMatrix;
- m[0, 0] := cosA; m[1, 0] := sinA;
- m[0, 1] := -sinA; m[1, 1] := cosA;
- MatrixMultiply(matrix, m);
- end;
- //------------------------------------------------------------------------------
- procedure MatrixTranslate(var matrix: TMatrixD; dx, dy: double);
- var
- m: TMatrixD;
- begin
- if ValueAlmostZero(dx) and ValueAlmostZero(dy) then Exit;
- m := IdentityMatrix;
- m[2, 0] := dx;
- m[2, 1] := dy;
- MatrixMultiply(matrix, m);
- end;
- //------------------------------------------------------------------------------
- procedure ScaleInternal(var matrix: TMatrixD; s: double);
- var
- i, j: Integer;
- begin
- for i := 0 to 2 do
- for j := 0 to 2 do
- matrix[i,j] := matrix[i,j] * s;
- end;
- //------------------------------------------------------------------------------
- function MatrixInvert(var matrix: TMatrixD): Boolean;
- var
- d: double;
- const
- tolerance = 1.0E-5;
- begin
- d := MatrixDeterminant(matrix);
- Result := abs(d) > tolerance;
- if Result then
- begin
- matrix := MatrixAdjugate(matrix);
- ScaleInternal(matrix, 1/d);
- end;
- end;
- //------------------------------------------------------------------------------
- procedure MatrixSkew(var matrix: TMatrixD; angleX, angleY: double);
- var
- m: TMatrixD;
- begin
- if ValueAlmostZero(angleX) and ValueAlmostZero(angleY) then Exit;
- m := IdentityMatrix;
- m[1, 0] := tan(angleX);
- m[0, 1] := tan(angleY);
- MatrixMultiply(matrix, m);
- end;
- //------------------------------------------------------------------------------
- procedure MatrixExtractScale(const mat: TMatrixD; out X, Y: double);
- begin
- // https://stackoverflow.com/a/32125700/359538
- X := Sqrt(Sqr(mat[0,0]) + Sqr(mat[0,1]));
- //Y := Sqrt(Sqr(mat[1,0]) + Sqr(mat[1,1]));
- Y := Abs((mat[0,0] * mat[1,1] - mat[1,0] * mat[0,1]) / X);
- end;
- //------------------------------------------------------------------------------
- procedure MatrixExtractScale(const mat: TMatrixD; out scale: double);
- var
- x,y: double;
- begin
- MatrixExtractScale(mat, x, y);
- scale := Average(x,y);
- end;
- //------------------------------------------------------------------------------
- procedure MatrixExtractTranslation(const mat: TMatrixD; out dx, dy: double);
- begin
- dx := mat[2,0];
- dy := mat[2,1];
- end;
- //------------------------------------------------------------------------------
- procedure MatrixExtractRotation(const mat: TMatrixD; out angle: double);
- begin
- angle := ArcTan2(mat[0,1], mat[0,0]);
- end;
- //------------------------------------------------------------------------------
- function MatrixExtractAll(const mat: TMatrixD;
- out angle: double; out scale, trans: TPointD): Boolean;
- var
- m00, m01, m10, m11: double;
- begin
- m00 := mat[0][0]; m10 := mat[1][0];
- m01 := mat[0][1]; m11 := mat[1][1];
- trans.X := mat[2][0];
- trans.Y := mat[2][1];
- angle := 0;
- scale := PointD(1,1);
- Result := (m00 <> 0) or (m01 <> 0);
- if not Result then Exit;
- angle := ArcTan2(m01, m00);
- // https://stackoverflow.com/a/32125700/359538
- scale.X := Sqrt(Sqr(mat[0,0]) + Sqr(mat[0,1]));
- scale.Y := (m00 * m11 - m10 * m01) / scale.X;
- end;
- //------------------------------------------------------------------------------
- {$IFDEF USE_DOWNSAMPLER_AUTOMATICALLY}
- function CanUseBoxDownsampler(const mat: TMatrixD; sx, sy: double): Boolean;
- begin
- // If the matrix looks like this after removing the scale,
- // the box downsampler can be used. (only translation and scaling)
- // cos(0) -sin(0) tx 1 0 tx
- // sin(0) cos(0) ty => 0 1 ty
- // 0 0 1 0 0 1
- {
- Result := (mat[0,0]/sx = 1) and (mat[0,1]/sx = 0) and
- (mat[1,0]/sy = 0) and (mat[1,1]/sy = 1) and
- (mat[2,0] = 0) and (mat[2,1] = 0) and
- (mat[2,2] = 1);
- }
- // We can skip the divisions, because m/s is only zero if m is zero
- // and m/s=1 is the same as m=s
- Result := (SameValue(mat[0,0], sx)) and (mat[0,1] = 0) and
- (mat[1,0] = 0) and (SameValue(mat[1,1], sy)) and
- (mat[2,0] = 0) and (mat[2,1] = 0) and
- (mat[2,2] = 1);
- end;
- {$ENDIF USE_DOWNSAMPLER_AUTOMATICALLY}
- //------------------------------------------------------------------------------
- // Affine Transformation
- //------------------------------------------------------------------------------
- function GetTransformBounds(img: TImage32; const matrix: TMatrixD): TRect;
- var
- pts: TPathD;
- begin
- pts := Rectangle(img.Bounds);
- MatrixApply(matrix, pts);
- Result := GetBounds(pts);
- end;
- //------------------------------------------------------------------------------
- function AffineTransformImage(img: TImage32; matrix: TMatrixD;
- scaleAdjust: Boolean): TPoint;
- begin
- Result := AffineTransformImage(img, img, matrix, scaleAdjust);
- end;
- //------------------------------------------------------------------------------
- function AffineTransformImage(img, targetImg: TImage32; matrix: TMatrixD;
- scaleAdjust: Boolean): TPoint;
- var
- i, j: integer;
- newWidth, newHeight: integer;
- sx, sy, x,y: double;
- xLimLo, yLimLo, xLimHi, yLimHi: double;
- pc: PColor32;
- tmp: TArrayOfColor32;
- dstRec: TRect;
- resampler: TResamplerFunction;
- {$IFDEF USE_DOWNSAMPLER_AUTOMATICALLY}
- useBoxDownsampler: Boolean;
- {$ENDIF}
- begin
- Result := NullPoint;
- if IsIdentityMatrix(matrix) or
- img.IsEmpty or (targetImg.Resampler = 0) then
- begin
- if targetImg <> img then targetImg.Assign(img);
- Exit;
- end;
- resampler := GetResampler(targetImg.Resampler);
- if not Assigned(resampler) then
- begin
- if targetImg <> img then targetImg.Assign(img);
- Exit;
- end;
- //auto-resize the image so it'll fit transformed image
- dstRec := img.Bounds;
- MatrixApply(matrix, dstRec);
- RectWidthHeight(dstRec, newWidth, newHeight);
- MatrixExtractScale(matrix, sx, sy);
- {$IFDEF USE_DOWNSAMPLER_AUTOMATICALLY}
- if (sx < 1.0) and (sy < 1.0) then
- begin
- //only use box downsampling when downsizing
- useBoxDownsampler := CanUseBoxDownsampler(matrix, sx, sy);
- end else
- useBoxDownsampler := false;
- if useBoxDownsampler then
- begin
- BoxDownSampling(img, targetImg, sx, sy);
- Exit;
- end;
- {$ENDIF}
- if scaleAdjust then
- begin
- sx := Max(1, sx * 0.5);
- sy := Max(1, sy * 0.5);
- end;
- //auto-translate the image too
- Result := dstRec.TopLeft;
- //starting with the result pixel coords, reverse lookup
- //the fractional coordinates in the untransformed image
- if not MatrixInvert(matrix) then
- begin
- if targetImg <> img then targetImg.Assign(img);
- Exit;
- end;
- NewColor32Array(tmp, newWidth * newHeight, True);
- pc := @tmp[0];
- xLimLo := -0.5/sx;
- xLimHi := img.Width + 0.5/sx;
- yLimLo := -0.5/sy;
- yLimHi := img.Height + 0.5/sy;
- for i := dstRec.Top to dstRec.Bottom -1 do
- begin
- for j := dstRec.Left to dstRec.Right -1 do
- begin
- x := j; y := i;
- MatrixApply(matrix, x, y);
- if (x <= xLimLo) or (x >= xLimHi) or (y <= yLimLo) or (y >= yLimHi) then
- pc^ := clNone32
- else
- // nb: -0.5 below is needed to properly center the transformed image
- // (and this is most obviously needed when there is large scaling)
- pc^ := resampler(img, x - 0.5, y - 0.5);
- inc(pc);
- end;
- end;
- targetImg.AssignPixelArray(tmp, newWidth, newHeight);
- end;
- //------------------------------------------------------------------------------
- // Projective Transformation
- //------------------------------------------------------------------------------
- procedure MatrixMulCoord(const matrix: TMatrixD; var x,y,z: double);
- {$IFDEF INLINE} inline; {$ENDIF}
- var
- xx, yy: double;
- begin
- xx := x; yy := y;
- x := matrix[0,0] *xx + matrix[0,1] *yy + matrix[0,2] *z;
- y := matrix[1,0] *xx + matrix[1,1] *yy + matrix[1,2] *z;
- z := matrix[2,0] *xx + matrix[2,1] *yy + matrix[2,2] *z;
- end;
- //------------------------------------------------------------------------------
- function BasisToPoints(x1, y1, x2, y2, x3, y3, x4, y4: double): TMatrixD;
- var
- m2: TMatrixD;
- z4: double;
- begin
- Result := Matrix(x1, x2, x3, y1, y2, y3, 1, 1, 1);
- m2 := MatrixAdjugate(Result);
- z4 := 1;
- MatrixMulCoord(m2, x4, y4, z4);
- m2 := Matrix(x4, 0, 0, 0, y4, 0, 0, 0, z4);
- MatrixMultiply(Result, m2);
- end;
- //------------------------------------------------------------------------------
- procedure GetSrcCoords256(const matrix: TMatrixD; var x, y: integer);
- {$IFDEF INLINE} inline; {$ENDIF}
- var
- xx,yy,zz: double;
- const
- Q: integer = MaxInt div 256;
- begin
- //returns coords multiplied by 256 in anticipation of the following
- //GetWeightedPixel function call which in turn expects the lower 8bits
- //of the integer coord value to represent a fraction.
- xx := x; yy := y; zz := 1;
- MatrixMulCoord(matrix, xx, yy, zz);
- if zz = 0 then
- begin
- if xx >= 0 then x := Q else x := -MaxInt;
- if yy >= 0 then y := Q else y := -MaxInt;
- end else
- begin
- xx := xx/zz;
- if xx > Q then x := MaxInt
- else if xx < -Q then x := -MaxInt
- else x := Round(xx *256);
- yy := yy/zz;
- if yy > Q then y := MaxInt
- else if yy < -Q then y := -MaxInt
- else y := Round(yy *256);
- end;
- end;
- //------------------------------------------------------------------------------
- procedure GetSrcCoords(const matrix: TMatrixD; var x, y: double);
- {$IFDEF INLINE} inline; {$ENDIF}
- var
- zz: double;
- const
- Q: integer = MaxInt div 256;
- begin
- //returns coords multiplied by 256 in anticipation of the following
- //GetWeightedPixel function call which in turn expects the lower 8bits
- //of the integer coord value to represent a fraction.
- zz := 1;
- MatrixMulCoord(matrix, x, y, zz);
- if zz = 0 then
- begin
- if x >= 0 then x := Q else x := -MaxDouble;
- if y >= 0 then y := Q else y := -MaxDouble;
- end else
- begin
- x := x/zz;
- if x > Q then x := MaxDouble
- else if x < -Q then x := -MaxDouble;
- y := y/zz;
- if y > Q then y := MaxDouble
- else if y < -Q then y := -MaxDouble
- end;
- end;
- //------------------------------------------------------------------------------
- function GetProjectionMatrix(const srcPts, dstPts: TPathD): TMatrixD;
- var
- dstMat: TMatrixD;
- begin
- if (length(srcPts) <> 4) or (length(dstPts) <> 4) then
- begin
- Result := IdentityMatrix;
- Exit;
- end;
- Result := BasisToPoints(srcPts[0].X, srcPts[0].Y,
- srcPts[1].X, srcPts[1].Y, srcPts[2].X, srcPts[2].Y, srcPts[3].X, srcPts[3].Y);
- dstMat := BasisToPoints(dstPts[0].X, dstPts[0].Y,
- dstPts[1].X, dstPts[1].Y, dstPts[2].X, dstPts[2].Y, dstPts[3].X, dstPts[3].Y);
- MatrixMultiply(Result, MatrixAdjugate(dstMat));
- end;
- //------------------------------------------------------------------------------
- function ProjectiveTransform(img: TImage32;
- const srcPts, dstPts: TPathD; const margins: TRect): Boolean;
- begin
- Result := ProjectiveTransform(img, img, srcPts, dstPts, margins);
- end;
- //------------------------------------------------------------------------------
- function ProjectiveTransform(img, targetImg: TImage32;
- const srcPts, dstPts: TPathD; const margins: TRect): Boolean;
- var
- w,h,i,j: integer;
- x,y: double;
- xLimLo, yLimLo, xLimHi, yLimHi: double;
- rec: TRect;
- dstPts2: TPathD;
- mat: TMatrixD;
- tmp: TArrayOfColor32;
- pc: PColor32;
- resampler: TResamplerFunction;
- begin
- //https://math.stackexchange.com/a/339033/384709
- if targetImg.Resampler = 0 then
- resampler := nil else
- resampler := GetResampler(targetImg.Resampler);
- Result := Assigned(resampler) and not img.IsEmpty and
- (Length(dstPts) = 4) and IsPathConvex(dstPts);
- if not Result then
- begin
- if targetImg <> img then targetImg.Assign(img);
- Exit;
- end;
- rec := GetBounds(dstPts);
- dec(rec.Left, margins.Left);
- dec(rec.Top, margins.Top);
- inc(rec.Right, margins.Right);
- inc(rec.Bottom, margins.Bottom);
- dstPts2 := TranslatePath(dstPts, -rec.Left, -rec.Top);
- xLimLo := -0.5;
- xLimHi := img.Width + 0.5;
- yLimLo := -0.5;
- yLimHi := img.Height + 0.5;
- mat := GetProjectionMatrix(srcPts, dstPts2);
- RectWidthHeight(rec, w, h);
- NewColor32Array(tmp, w * h, True);
- pc := @tmp[0];
- for i := 0 to h -1 do
- for j := 0 to w -1 do
- begin
- x := j; y := i;
- GetSrcCoords(mat, x, y);
- if (x <= xLimLo) or (x >= xLimHi) or (y <= yLimLo) or (y >= yLimHi) then
- pc^ := clNone32
- else
- pc^ := resampler(img, x -0.5, y -0.5);
- inc(pc);
- end;
- targetImg.BlockNotify;
- targetImg.AssignPixelArray(tmp, w, h);
- targetImg.UnblockNotify;
- end;
- //------------------------------------------------------------------------------
- // Spline transformations
- //------------------------------------------------------------------------------
- function ReColor(color, newColor: TColor32): TColor32;
- {$IFDEF INLINE} inline; {$ENDIF}
- begin
- Result := (color and $FF000000) or newColor;
- end;
- //------------------------------------------------------------------------------
- function InterpolateSegX(const pt1, pt2: TPointD): TPathD;
- var
- i, x1, x2: integer;
- xo,dydx: double;
- begin
- Result := nil;
- if pt2.X > pt1.X then
- begin
- x1 := Ceil(pt1.X);
- x2 := Ceil(pt2.X);
- if x1 = x2 then Exit;
- dydx := (pt2.Y - pt1.Y)/(pt2.X - pt1.X);
- xo := x1 -pt1.X;
- NewPointDArray(Result, x2-x1, True);
- for i:= 0 to x2 - x1 -1 do
- begin
- Result[i].X := x1 +i;
- Result[i].Y := pt1.Y + dydx * (xo +i);
- end;
- end else
- begin
- x1 := Floor(pt1.X);
- x2 := Floor(pt2.X);
- if x1 = x2 then Exit;
- dydx := (pt2.Y - pt1.Y)/(pt2.X - pt1.X);
- xo := x1 -pt1.X;
- NewPointDArray(Result, x1-x2, True);
- for i:= 0 to x1 - x2 -1 do
- begin
- Result[i].X := x1 -i;
- Result[i].Y := pt1.Y + dydx * (xo -i);
- end;
- end;
- end;
- //------------------------------------------------------------------------------
- function InterpolateSegY(const pt1, pt2: TPointD): TPathD;
- var
- i, y1,y2: integer;
- yo,dxdy: double;
- begin
- Result := nil;
- if pt2.Y > pt1.Y then
- begin
- y1 := Ceil(pt1.Y);
- y2 := Ceil(pt2.Y);
- if y1 = y2 then Exit;
- dxdy := (pt2.X - pt1.X)/(pt2.Y - pt1.Y);
- yo := y1 -pt1.Y;
- NewPointDArray(Result, y2-y1, True);
- for i:= 0 to y2 - y1 -1 do
- begin
- Result[i].Y := y1 +i;
- Result[i].X := pt1.X + dxdy * (yo +i);
- end;
- end else
- begin
- y1 := Floor(pt1.Y);
- y2 := Floor(pt2.Y);
- if y1 = y2 then Exit;
- dxdy := (pt2.X - pt1.X)/(pt2.Y - pt1.Y);
- yo := y1 -pt1.Y;
- NewPointDArray(Result, y1-y2, True);
- for i:= 0 to y1 - y2 -1 do
- begin
- Result[i].Y := y1 -i;
- Result[i].X := pt1.X + dxdy * (yo -i);
- end;
- end;
- end;
- //------------------------------------------------------------------------------
- function InterpolatePathForX(const path: TPathD): TPathD;
- var
- i,len: integer;
- tmp: TPathD;
- begin
- Result := nil;
- len := length(path);
- if len < 2 then Exit;
- for i := 1 to len -1 do
- begin
- tmp := InterpolateSegX(path[i-1], path[i]);
- ConcatPaths(Result, tmp);
- end;
- end;
- //------------------------------------------------------------------------------
- function InterpolatePathForY(const path: TPathD): TPathD;
- var
- i, len: integer;
- tmp: TPathD;
- begin
- Result := nil;
- len := length(path);
- if len < 2 then Exit;
- for i := 1 to len -1 do
- begin
- tmp := InterpolateSegY(path[i-1], path[i]);
- ConcatPaths(Result, tmp);
- end;
- end;
- //------------------------------------------------------------------------------
- function SplineVertTransform(img: TImage32; const topSpline: TPathD;
- splineType: TSplineType; backColor: TColor32; out offset: TPoint): Boolean;
- begin
- Result := SplineVertTransform(img, img, topSpline, splineType, backColor, offset);
- end;
- //------------------------------------------------------------------------------
- function SplineVertTransform(img, targetImg: TImage32; const topSpline: TPathD;
- splineType: TSplineType; backColor: TColor32; out offset: TPoint): Boolean;
- var
- i,j, w,h, len: integer;
- x,y, yy, q: double;
- yLimLo, yLimHi: double;
- distances: TArrayOfDouble;
- pc: PColor32;
- rec: TRect;
- tmp: TArrayOfColor32;
- topPath: TPathD;
- prevX: double;
- resampler: TResamplerFunction;
- backColoring, allowBackColoring: Boolean;
- begin
- offset := NullPoint;
- if targetImg.Resampler = 0 then
- resampler := nil else
- resampler := GetResampler(targetImg.Resampler);
- //convert the top spline control points into a flattened path
- if splineType = stQuadratic then
- topPath := FlattenQSpline(topSpline) else
- topPath := FlattenCSpline(topSpline);
- rec := GetBounds(topPath);
- //return false if the spline is invalid or there's no vertical transformation
- Result := Assigned(resampler) and not IsEmptyRect(rec);
- if not Result then
- begin
- if targetImg <> img then targetImg.Assign(img);
- Exit;
- end;
- offset := rec.TopLeft;
- topPath := InterpolatePathForX(topPath);
- len := Length(topPath);
- inc(rec.Bottom, img.Height);
- RectWidthHeight(rec, w, h);
- NewColor32Array(tmp, (w+1) * h, False);
- prevX := topPath[0].X;
- allowBackColoring := GetAlpha(backColor) > 2;
- backColor := backColor and $00FFFFFF;
- distances := GetCumulativeDistances(topPath);
- q := img.Width / distances[High(distances)];
- yLimLo := -0.5;
- yLimHi := img.Height + 0.5;
- for i := 0 to len -1 do
- begin
- pc := @tmp[Round(topPath[i].X)-rec.Left];
- backColoring := allowBackColoring and (prevX >= topPath[i].X);
- prevX := topPath[i].X;
- yy := topPath[i].Y;
- for j := rec.top to rec.bottom -1 do
- begin
- x := Distances[i]*q;
- y := j - yy;
- if (y < yLimLo) or (y > yLimHi) then
- // do nothing !
- else if backColoring then
- pc^ := BlendToAlpha(pc^, ReColor(resampler(img, x -0.5, y -0.5), backColor))
- else
- pc^ := BlendToAlpha(pc^, resampler(img, x -0.5, y -0.5));
- inc(pc, w);
- end;
- end;
- // tmp was creates with "(w+1)*h". We take advantage of the
- // memory manager's inplace shrink.
- SetLength(tmp, w * h);
- targetImg.AssignPixelArray(tmp, w, h);
- end;
- //------------------------------------------------------------------------------
- function SplineHorzTransform(img: TImage32; const leftSpline: TPathD;
- splineType: TSplineType; backColor: TColor32; out offset: TPoint): Boolean;
- begin
- Result := SplineHorzTransform(img, img, leftSpline, splineType, backColor, offset);
- end;
- //------------------------------------------------------------------------------
- function SplineHorzTransform(img, targetImg: TImage32; const leftSpline: TPathD;
- splineType: TSplineType; backColor: TColor32; out offset: TPoint): Boolean;
- var
- i,j, len, w,h: integer;
- x,y, xx, q, prevY: double;
- xLimLo, xLimHi: double;
- leftPath: TPathD;
- distances: TArrayOfDouble;
- rec: TRect;
- pc: PColor32;
- tmp: TArrayOfColor32;
- backColoring, allowBackColoring: Boolean;
- resampler: TResamplerFunction;
- begin
- offset := NullPoint;
- if targetImg.Resampler = 0 then
- resampler := nil else
- resampler := GetResampler(targetImg.Resampler);
- //convert the left spline control points into a flattened path
- if splineType = stQuadratic then
- leftPath := FlattenQSpline(leftSpline) else
- leftPath := FlattenCSpline(leftSpline);
- rec := GetBounds(leftPath);
- //return false if the spline is invalid or there's no horizontal transformation
- Result := Assigned(resampler) and not IsEmptyRect(rec);
- if not Result then
- begin
- if targetImg <> img then targetImg.Assign(img);
- Exit;
- end;
- offset := rec.TopLeft;
- leftPath := InterpolatePathForY(leftPath);
- len := Length(leftPath);
- inc(rec.Right, img.Width);
- RectWidthHeight(rec, w, h);
- NewColor32Array(tmp, w * (h+1), False);
- prevY := leftPath[0].Y;
- allowBackColoring := GetAlpha(backColor) > 2;
- backColor := backColor and $00FFFFFF;
- distances := GetCumulativeDistances(leftPath);
- q := img.Height / distances[High(distances)];;
- xLimLo := -0.5;
- xLimHi := img.Width + 0.5;
- for i := 0 to len -1 do
- begin
- pc := @tmp[Round(leftPath[i].Y - rec.Top) * w];
- backColoring := allowBackColoring and (prevY >= leftPath[i].Y);
- prevY := leftPath[i].Y;
- xx := leftPath[i].X;
- y := Distances[i]*q;
- for j := rec.left to rec.right -1 do
- begin
- x := j - xx;
- if (x < xLimLo) or (x > xLimHi) then
- // do nothing !
- else if backColoring then
- pc^ := BlendToAlpha(pc^, ReColor(resampler(img, x -0.5, y -0.5), backColor))
- else
- pc^ := BlendToAlpha(pc^, resampler(img, x -0.5, y -0.5));
- inc(pc);
- end;
- end;
- // tmp was creates with "w*(h+1)". We take advantage of the
- // memory manager's inplace shrink.
- SetLength(tmp, w * h);
- targetImg.AssignPixelArray(tmp, w, h);
- end;
- //------------------------------------------------------------------------------
- // Miscellaneous WeightedColor function
- //------------------------------------------------------------------------------
- function LimitByte(val: Cardinal): byte; {$IFDEF INLINE} inline; {$ENDIF}
- begin
- if val > 255 then result := 255
- else result := val;
- end;
- //------------------------------------------------------------------------------
- // TWeightedColor
- //------------------------------------------------------------------------------
- procedure TWeightedColor.Reset;
- {$IFDEF CPUX64}
- var
- Zero: Int64;
- {$ENDIF CPUX64}
- begin
- {$IFDEF CPUX64}
- Zero := 0;
- fAddCount := Zero;
- fAlphaTot := Zero;
- fColorTotR := Zero;
- fColorTotG := Zero;
- fColorTotB := Zero;
- {$ELSE}
- fAddCount := 0;
- fAlphaTot := 0;
- fColorTotR := 0;
- fColorTotG := 0;
- fColorTotB := 0;
- {$ENDIF CPUX64}
- end;
- //------------------------------------------------------------------------------
- procedure TWeightedColor.Reset(c: TColor32; w: Integer);
- var
- a: Cardinal;
- begin
- fAddCount := w;
- a := w * Byte(c shr 24);
- if a = 0 then
- begin
- fAlphaTot := 0;
- fColorTotB := 0;
- fColorTotG := 0;
- fColorTotR := 0;
- end else
- begin
- fAlphaTot := a;
- fColorTotB := (a * Byte(c));
- fColorTotG := (a * Byte(c shr 8));
- fColorTotR := (a * Byte(c shr 16));
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TWeightedColor.AddWeight(w: Integer);
- begin
- inc(fAddCount, w);
- end;
- //------------------------------------------------------------------------------
- procedure TWeightedColor.Add(c: TColor32; w: Integer);
- var
- a: Int64;
- begin
- inc(fAddCount, w);
- a := Byte(c shr 24);
- if a <> 0 then
- begin
- a := a * Cardinal(w);
- inc(fAlphaTot, a);
- inc(fColorTotB, (a * Byte(c)));
- inc(fColorTotG, (a * Byte(c shr 8)));
- inc(fColorTotR, (a * Byte(c shr 16)));
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TWeightedColor.Add(c: TColor32);
- // Optimized for w=1
- var
- a: Int64;
- begin
- inc(fAddCount);
- a := Byte(c shr 24);
- if a = 0 then Exit;
- inc(fAlphaTot, a);
- inc(fColorTotB, (a * Byte(c)));
- inc(fColorTotG, (a * Byte(c shr 8)));
- inc(fColorTotR, (a * Byte(c shr 16)));
- end;
- //------------------------------------------------------------------------------
- procedure TWeightedColor.Add(const other: TWeightedColor);
- begin
- inc(fAddCount, other.fAddCount);
- inc(fAlphaTot, other.fAlphaTot);
- inc(fColorTotR, other.fColorTotR);
- inc(fColorTotG, other.fColorTotG);
- inc(fColorTotB, other.fColorTotB);
- end;
- //------------------------------------------------------------------------------
- procedure TWeightedColor.Subtract(c: TColor32; w: Integer);
- var
- a: Int64;
- begin
- dec(fAddCount, w);
- a := w * Byte(c shr 24);
- if a = 0 then Exit;
- dec(fAlphaTot, a);
- dec(fColorTotB, (a * Byte(c)));
- dec(fColorTotG, (a * Byte(c shr 8)));
- dec(fColorTotR, (a * Byte(c shr 16)));
- end;
- //------------------------------------------------------------------------------
- procedure TWeightedColor.Subtract(c: TColor32);
- // Optimized for w=1
- var
- a: Int64;
- begin
- dec(fAddCount);
- a := Byte(c shr 24);
- if a = 0 then Exit;
- dec(fAlphaTot, a);
- dec(fColorTotB, (a * Byte(c)));
- dec(fColorTotG, (a * Byte(c shr 8)));
- dec(fColorTotR, (a * Byte(c shr 16)));
- end;
- //------------------------------------------------------------------------------
- procedure TWeightedColor.Subtract(const other: TWeightedColor);
- begin
- dec(fAddCount, other.fAddCount);
- dec(fAlphaTot, other.fAlphaTot);
- dec(fColorTotR, other.fColorTotR);
- dec(fColorTotG, other.fColorTotG);
- dec(fColorTotB, other.fColorTotB);
- end;
- //------------------------------------------------------------------------------
- function TWeightedColor.AddSubtract(addC, subC: TColor32): Boolean;
- var
- a: Int64;
- begin
- // add+subtract => fAddCount stays the same
- // skip identical colors
- Result := False;
- if addC = subC then Exit;
- a := Byte(addC shr 24);
- if a > 0 then
- begin
- inc(fAlphaTot, a);
- inc(fColorTotB, (a * Byte(addC)));
- inc(fColorTotG, (a * Byte(addC shr 8)));
- inc(fColorTotR, (a * Byte(addC shr 16)));
- Result := True;
- end;
- a := Byte(subC shr 24);
- if a > 0 then
- begin
- dec(fAlphaTot, a);
- dec(fColorTotB, (a * Byte(subC)));
- dec(fColorTotG, (a * Byte(subC shr 8)));
- dec(fColorTotR, (a * Byte(subC shr 16)));
- Result := True;
- end;
- end;
- //------------------------------------------------------------------------------
- function TWeightedColor.AddNoneSubtract(c: TColor32): Boolean;
- var
- a: Int64;
- begin
- // add+subtract => fAddCount stays the same
- a := Byte(c shr 24);
- if a > 0 then
- begin
- dec(fAlphaTot, a);
- dec(fColorTotB, (a * Byte(c)));
- dec(fColorTotG, (a * Byte(c shr 8)));
- dec(fColorTotR, (a * Byte(c shr 16)));
- Result := True;
- end
- else
- Result := False;
- end;
- //------------------------------------------------------------------------------
- function TWeightedColor.GetColor: TColor32;
- var
- oneDivAlphaTot: double;
- alpha: Integer;
- begin
- result := clNone32;
- if (fAlphaTot <= 0) or (fAddCount <= 0) then
- Exit;
- {$IFDEF CPUX86}
- if fAlphaTot and $FFFFFFFF80000000 = 0 then // small, so can avoid _lldiv call
- alpha := (Cardinal(fAlphaTot) + (Cardinal(fAddCount) shr 1)) div
- Cardinal(fAddCount)
- else
- {$ENDIF CPUX86}
- alpha := (fAlphaTot + (Cardinal(fAddCount) shr 1)) div Cardinal(fAddCount);
- result := TColor32(Min(255, alpha)) shl 24;
- // alpha weighting has been applied to color channels, so div by fAlphaTot
- if fAlphaTot < DivOneByXTableSize then // use precalculated 1/X values
- oneDivAlphaTot := DivOneByXTable[fAlphaTot] else
- oneDivAlphaTot := 1/(fAlphaTot);
- // 1. Skip zero calculations.
- // 2. LimitByte(Integer): Values can't be less than 0, so don't use ClampByte.
- // 3. x86: Round expects the value in the st(0)/xmm1 FPU register.
- // Thus we need to do the calculation and Round call in one expression.
- // Otherwise the compiler will use a temporary double variable on
- // the stack that will cause unnecessary store and load operations.
- if fColorTotB > 0 then
- result := result or LimitByte(System.Round(fColorTotB * oneDivAlphaTot));
- if fColorTotG > 0 then
- result := result or LimitByte(System.Round(fColorTotG * oneDivAlphaTot)) shl 8;
- if fColorTotR > 0 then
- result := result or LimitByte(System.Round(fColorTotR * oneDivAlphaTot)) shl 16;
- end;
- //------------------------------------------------------------------------------
- // Initialization
- //------------------------------------------------------------------------------
- procedure MakeDivOneByXTable;
- var
- i: Integer;
- begin
- DivOneByXTable[0] := 0; // NaN
- for i := 1 to High(DivOneByXTable) do
- DivOneByXTable[i] := 1/i;
- end;
- initialization
- MakeDivOneByXTable;
- end.
|