| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643 |
- unit Img32.Extra;
- (*******************************************************************************
- * Author : Angus Johnson *
- * Version : 4.8 *
- * Date : 10 January 2025 *
- * Website : http://www.angusj.com *
- * Copyright : Angus Johnson 2019-2025 *
- * Purpose : Miscellaneous routines that don't belong in other modules. *
- * License : http://www.boost.org/LICENSE_1_0.txt *
- *******************************************************************************)
- interface
- {$I Img32.inc}
- uses
- SysUtils, Classes, Math, Types,
- Img32, Img32.Draw, Img32.Vector;
- type
- TButtonShape = (bsRound, bsSquare, bsDiamond);
- TButtonAttribute = (baShadow, ba3D, baEraseBeneath);
- TButtonAttributes = set of TButtonAttribute;
- procedure DrawEdge(img: TImage32; const rec: TRect;
- topLeftColor, bottomRightColor: TColor32; penWidth: double = 1.0); overload;
- procedure DrawEdge(img: TImage32; const rec: TRectD;
- topLeftColor, bottomRightColor: TColor32; penWidth: double = 1.0); overload;
- procedure DrawEdge(img: TImage32; const path: TPathD;
- topLeftColor, bottomRightColor: TColor32;
- penWidth: double = 1.0; closePath: Boolean = true); overload;
- //DrawShadowRect: is **much** faster than DrawShadow
- procedure DrawShadowRect(img: TImage32; const rec: TRect; depth: double;
- angle: double = angle45; color: TColor32 = $80000000);
- procedure DrawShadow(img: TImage32; const polygon: TPathD;
- fillRule: TFillRule; depth: double; angleRads: double = angle45;
- color: TColor32 = $80000000; cutoutInsideShadow: Boolean = false); overload;
- procedure DrawShadow(img: TImage32; const polygons: TPathsD;
- fillRule: TFillRule; depth: double; angleRads: double = angle45;
- color: TColor32 = $80000000; cutoutInsideShadow: Boolean = false); overload;
- procedure DrawGlow(img: TImage32; const polygon: TPathD;
- fillRule: TFillRule; color: TColor32; blurRadius: integer); overload;
- procedure DrawGlow(img: TImage32; const polygons: TPathsD;
- fillRule: TFillRule; color: TColor32; blurRadius: integer); overload;
- //FloodFill: If no CompareFunc is provided, FloodFill will fill whereever
- //adjoining pixels exactly match the starting pixel - Point(x,y).
- procedure FloodFill(img: TImage32; x, y: Integer; newColor: TColor32;
- tolerance: Byte = 0; compareFunc: TCompareFunctionEx = nil);
- procedure FastGaussianBlur(img: TImage32;
- const rec: TRect; stdDev: integer; repeats: integer = 2); overload;
- procedure FastGaussianBlur(img: TImage32;
- const rec: TRect; stdDevX, stdDevY: integer; repeats: integer); overload;
- procedure GaussianBlur(img: TImage32; rec: TRect; radius: Integer);
- //Emboss: A smaller radius is sharper. Increasing depth increases contrast.
- //Luminance changes grayscale balance (unless preserveColor = true)
- procedure Emboss(img: TImage32; radius: Integer = 1; depth: Integer = 10;
- luminance: Integer = 75; preserveColor: Boolean = false);
- //Sharpen: Radius range is 1 - 10; amount range is 1 - 50.<br>
- //see https://en.wikipedia.org/wiki/Unsharp_masking
- procedure Sharpen(img: TImage32; radius: Integer = 2; amount: Integer = 10);
- //HatchBackground: Assumes the current image is semi-transparent.
- procedure HatchBackground(img: TImage32; color1: TColor32 = clWhite32;
- color2: TColor32= $FFE8E8E8; hatchSize: Integer = 10); overload;
- procedure HatchBackground(img: TImage32; const rec: TRect;
- color1: TColor32 = clWhite32; color2: TColor32= $FFE8E8E8;
- hatchSize: Integer = 10); overload;
- procedure GridBackground(img: TImage32; majorInterval, minorInterval: integer;
- fillColor: TColor32 = clWhite32;
- majColor: TColor32 = $30000000; minColor: TColor32 = $20000000);
- procedure ReplaceExactColor(img: TImage32; oldColor, newColor: TColor32);
- //RemoveColor: Removes the specified color from the image, even from
- //pixels that are a blend of colors including the specified color.<br>
- //see https://stackoverflow.com/questions/9280902/
- procedure RemoveColor(img: TImage32; color: TColor32);
- //FilterOnColor: Removes everything not nearly matching 'color'
- //This uses an algorithm that's very similar to the one in RemoveColor.
- procedure FilterOnColor(img: TImage32; color: TColor32);
- procedure FilterOnExactColor(img: TImage32; color: TColor32);
- procedure FilterOnAlpha(img: TImage32; alpha: byte; tolerance: byte);
- //RedEyeRemove: Removes 'red eye' from flash photo images.
- procedure RedEyeRemove(img: TImage32; const rect: TRect);
- procedure PencilEffect(img: TImage32; intensity: integer = 0);
- procedure TraceContours(img: TImage32; intensity: integer);
- procedure EraseInsidePath(img: TImage32;
- const path: TPathD; fillRule: TFillRule);
- procedure EraseInsidePaths(img: TImage32;
- const paths: TPathsD; fillRule: TFillRule);
- procedure EraseOutsidePath(img: TImage32; const path: TPathD;
- fillRule: TFillRule; const outsideBounds: TRect);
- procedure EraseOutsidePaths(img: TImage32; const paths: TPathsD;
- fillRule: TFillRule; const outsideBounds: TRect;
- rendererCache: TCustomRendererCache = nil); overload;
- procedure Draw3D(img: TImage32; const polygon: TPathD;
- fillRule: TFillRule; height, blurRadius: double;
- colorLt: TColor32 = $DDFFFFFF; colorDk: TColor32 = $80000000;
- angleRads: double = angle225); overload;
- procedure Draw3D(img: TImage32; const polygons: TPathsD;
- fillRule: TFillRule; height, blurRadius: double;
- colorLt: TColor32 = $DDFFFFFF; colorDk: TColor32 = $80000000;
- angleRads: double = angle225); overload;
- function RainbowColor(fraction: double; luminance: byte = 128): TColor32;
- function GradientColor(color1, color2: TColor32; frac: single): TColor32;
- function MakeDarker(color: TColor32; percent: cardinal): TColor32;
- function MakeLighter(color: TColor32; percent: cardinal): TColor32;
- function DrawButton(img: TImage32; const pt: TPointD;
- size: double; color: TColor32 = clNone32;
- buttonShape: TButtonShape = bsRound;
- buttonAttributes: TButtonAttributes = [baShadow, ba3D, baEraseBeneath]): TPathD;
- // RamerDouglasPeucker: simplifies paths, recursively removing vertices where
- // they deviate no more than 'epsilon' from their adjacent vertices.
- function RamerDouglasPeucker(const path: TPathD;
- epsilon: double): TPathD; overload;
- function RamerDouglasPeucker(const paths: TPathsD;
- epsilon: double): TPathsD; overload;
- {$IFDEF USE_OLD_SIMPLIFYPATHS}
- // SimplifyPath: Better than RDP when simplifying closed paths
- function SimplifyPath(const path: TPathD;
- shapeTolerance: double = 0.1; isOpenPath: Boolean = false): TPathD;
- function SimplifyPaths(const paths: TPathsD;
- shapeTolerance: double = 0.1; isOpenPath: Boolean = false): TPathsD;
- {$ELSE}
- // SimplifyPath: Better than RDP when simplifying closed paths
- function SimplifyPath(const path: TPathD;
- shapeTolerance: double = 0.1; isClosedPath: Boolean = true): TPathD;
- function SimplifyPaths(const paths: TPathsD;
- shapeTolerance: double = 0.1; isClosedPath: Boolean = true): TPathsD;
- {$ENDIF}
- // SimplifyPathEx: this is particularly useful following Vectorize()
- // because it also removes very short zig-zag segments
- function SimplifyPathEx(const path: TPathD; shapeTolerance: double): TPathD;
- function SimplifyPathsEx(const paths: TPathsD; shapeTolerance: double): TPathsD;
- // SmoothToCubicBezier and SmoothToCubicBezier2 have been deprecated in
- // favour of SmoothPath that's much simpler
- function SmoothToCubicBezier(const path: TPathD;
- pathIsClosed: Boolean; maxOffset: integer = 0): TPathD; overload; deprecated;
- function SmoothToCubicBezier(const paths: TPathsD;
- pathIsClosed: Boolean; maxOffset: integer = 0): TPathsD; overload; deprecated;
- function SmoothToCubicBezier2(const path: TPathD;
- pathIsClosed: Boolean; maxOffset: integer = 0): TPathD; overload; deprecated;
- function SmoothToCubicBezier2(const paths: TPathsD;
- pathIsClosed: Boolean; maxOffset: integer = 0): TPathsD; overload; deprecated;
- // SmoothPath - smooths a path using bicubic interpolation
- // tension (range -1 to 1): from least to most curve constraint
- function SmoothPath(const path: TPathD; isClosedPath: Boolean;
- tension: double = 0; shapeTolerance: double = 0.1): TPathD;
- function SmoothPaths(const paths: TPathsD; isClosedPath: Boolean;
- tension: double = 0; shapeTolerance: double = 0.1): TPathsD;
- function GetFloodFillMask(imgIn, imgMaskOut: TImage32; x, y: Integer;
- tolerance: Byte; compareFunc: TCompareFunctionEx): Boolean;
- procedure SymmetricCropTransparent(img: TImage32);
- //3 additional blend functions (see TImage32.CopyBlend)
- function BlendAverage(bgColor, fgColor: TColor32): TColor32;
- function BlendLinearBurn(bgColor, fgColor: TColor32): TColor32;
- function BlendColorDodge(bgColor, fgColor: TColor32): TColor32;
- implementation
- uses
- {$IFDEF USING_FMX}
- Img32.FMX,
- {$ENDIF}
- Img32.Transform;
- const
- FloodFillDefaultRGBTolerance: byte = 64;
- MaxBlur = 100;
- type
- PColor32Array = ^TColor32Array;
- TColor32Array = array [0.. maxint div SizeOf(TColor32) -1] of TColor32;
- PWeightedColorArray = ^TWeightedColorArray;
- TWeightedColorArray = array [0.. $FFFFFF] of TWeightedColor;
- // SimplifyPathsEx structures
- PVertex = ^TVertex;
- TVertex = record
- pt : TPointD;
- uvec : TPointD;
- dist : double;
- perpD : double;
- next : PVertex;
- prev: PVertex;
- end;
- TArrayOfVertices = array of TVertex;
- //------------------------------------------------------------------------------
- // Miscellaneous functions
- //------------------------------------------------------------------------------
- function Clamp(val, endVal: integer): integer;
- {$IFDEF INLINE} inline; {$ENDIF}
- begin
- if val < 0 then Result := 0
- else if val >= endVal then Result := endVal -1
- else Result := val;
- end;
- //------------------------------------------------------------------------------
- function ModEx(val, endVal: integer): integer;
- {$IFDEF INLINE} inline; {$ENDIF}
- begin
- Result := val mod endVal;
- if Result < 0 then Result := endVal + Result;
- end;
- //------------------------------------------------------------------------------
- function GetSymmetricCropTransparentRect(img: TImage32): TRect;
- var
- w,h, x,y, x1,y1: Integer;
- p1,p2: PARGB;
- opaquePxlFound: Boolean;
- begin
- Result := img.Bounds;
- w := img.Width;
- y1 := 0;
- opaquePxlFound := false;
- for y := 0 to (img.Height div 2) -1 do
- begin
- p1 := PARGB(img.PixelRow[y]);
- p2 := PARGB(img.PixelRow[img.Height - y -1]);
- for x := 0 to w -1 do
- begin
- if (p1.A > 0) or (p2.A > 0) then
- begin
- y1 := y;
- opaquePxlFound := true;
- break;
- end;
- inc(p1); inc(p2);
- end;
- if opaquePxlFound then break;
- end;
- // probably safeset not to resize empty images
- if not opaquePxlFound then Exit;
- if y1 > 0 then
- begin
- inc(Result.Top, y1);
- dec(Result.Bottom, y1);
- end;
- x1 := 0;
- h := RectHeight(Result);
- opaquePxlFound := false;
- for x := 0 to (w div 2) -1 do
- begin
- p1 := PARGB(@img.Pixels[Result.Top * w + x]);
- p2 := PARGB(@img.Pixels[Result.Top * w + (w -1) - x]);
- for y := 0 to h -1 do
- begin
- if (p1.A > 0) or (p2.A > 0) then
- begin
- x1 := x;
- opaquePxlFound := true;
- break;
- end;
- inc(p1, w); inc(p2, w);
- end;
- if opaquePxlFound then break;
- end;
- if not opaquePxlFound then Exit;
- inc(Result.Left, x1);
- dec(Result.Right, x1);
- end;
- //------------------------------------------------------------------------------
- //SymmetricCropTransparent: after cropping, the image's midpoint
- //will be the same pixel as before cropping. (Important for rotating.)
- procedure SymmetricCropTransparent(img: TImage32);
- var
- rec: TRect;
- begin
- rec := GetSymmetricCropTransparentRect(img);
- if (rec.Top > 0) or (rec.Left > 0) then img.Crop(rec);
- end;
- //------------------------------------------------------------------------------
- procedure DrawEdge(img: TImage32; const rec: TRect;
- topLeftColor, bottomRightColor: TColor32; penWidth: double = 1.0);
- begin
- DrawEdge(img, RectD(rec), topLeftColor, bottomRightColor, penWidth);
- end;
- //------------------------------------------------------------------------------
- procedure DrawEdge(img: TImage32; const rec: TRectD;
- topLeftColor, bottomRightColor: TColor32; penWidth: double = 1.0);
- var
- p: TPathD;
- c: TColor32;
- begin
- if penWidth = 0 then Exit
- else if penWidth < 0 then
- begin
- c := topLeftColor;
- topLeftColor := bottomRightColor;
- bottomRightColor := c;
- penWidth := -penWidth;
- end;
- if topLeftColor <> bottomRightColor then
- begin
- with rec do
- begin
- p := Img32.Vector.MakePath([left, bottom, left, top, right, top]);
- DrawLine(img, p, penWidth, topLeftColor, esButt);
- p := Img32.Vector.MakePath([right, top, right, bottom, left, bottom]);
- DrawLine(img, p, penWidth, bottomRightColor, esButt);
- end;
- end else
- DrawLine(img, Rectangle(rec), penWidth, topLeftColor, esPolygon);
- end;
- //------------------------------------------------------------------------------
- procedure DrawEdge(img: TImage32; const path: TPathD;
- topLeftColor, bottomRightColor: TColor32;
- penWidth: double = 1.0; closePath: Boolean = true);
- var
- i, highI, deg: integer;
- frac: double;
- c: TColor32;
- p: TPathD;
- const
- RadToDeg = 180/PI;
- begin
- if penWidth = 0 then Exit
- else if penWidth < 0 then
- begin
- c := topLeftColor;
- topLeftColor := bottomRightColor;
- bottomRightColor := c;
- penWidth := -penWidth;
- end;
- highI := high(path);
- if highI < 2 then Exit;
- p := path;
- if closePath and not PointsNearEqual(p[0], p[highI], 0.01) then
- begin
- AppendPoint(p, p[0]);
- inc(highI);
- end;
- for i := 1 to highI do
- begin
- deg := Round(GetAngle(p[i-1], p[i]) * RadToDeg);
- case deg of
- -180..-136: frac := (-deg-135)/45;
- -135..0 : frac := 0;
- 1..44 : frac := deg/45;
- else frac := 1;
- end;
- c := GradientColor(topLeftColor, bottomRightColor, frac);
- DrawLine(img, p[i-1], p[i], penWidth, c);
- end;
- end;
- //------------------------------------------------------------------------------
- procedure FillColorHorz(img: TImage32; x, endX, y: integer; color: TColor32);
- var
- i,dx: integer;
- p: PColor32;
- begin
- if (x < 0) or (x >= img.Width) then Exit;
- if (y < 0) or (y >= img.Height) then Exit;
- p := img.PixelRow[y]; inc(p, x);
- if endX >= img.Width then endX := img.Width -1
- else if endX < 0 then endX := 0;
- if endX < x then dx := -1 else dx := 1;
- for i := 0 to Abs(x-endX) do
- begin
- p^ := color;
- inc(p, dx);
- end;
- end;
- //------------------------------------------------------------------------------
- procedure FillColorVert(img: TImage32; x, y, endY: integer; color: TColor32);
- var
- i, dy: integer;
- p: PColor32;
- begin
- if (x < 0) or (x >= img.Width) then Exit;
- if (y < 0) or (y >= img.Height) then Exit;
- p := img.PixelRow[y]; inc(p, x);
- if endY >= img.Height then
- endY := img.Height -1 else if endY < 0 then endY := 0;
- dy := img.Width;
- if endY < y then dy := -dy;
- for i := 0 to Abs(y - endY) do
- begin
- p^ := color;
- inc(p, dy);
- end;
- end;
- //------------------------------------------------------------------------------
- procedure DrawShadowRect(img: TImage32; const rec: TRect; depth: double;
- angle: double = angle45; color: TColor32 = $80000000);
- var
- i,j, sX,sY: integer;
- l,t,r,b: integer;
- tmpImg: TImage32;
- tmpRec: TRect;
- xx,yy: double;
- ss: TPointD;
- c: TColor32;
- begin
- GetSinCos(angle, yy, xx);
- ss.X := depth * xx;
- ss.Y := depth * yy;
- sX := Abs(Round(ss.X));
- sY := Abs(Round(ss.Y));
- if rec.Left + ss.X < 0 then ss.X := -rec.Left
- else if rec.Right + ss.X > img.Width then ss.X := img.Width - rec.Right -1;
- if rec.Top + ss.Y < 0 then ss.Y := -rec.Top
- else if rec.Bottom + ss.Y > img.Height then ss.Y := img.Height -rec.Bottom -1;
- tmpImg := TImage32.Create(sX*3 +1, sY*3 +1);
- try
- i := sX div 2; j := sY div 2;
- DrawPolygon(tmpImg, Rectangle(i,j,i+sX*2,j+sY*2), frNonZero, color);
- FastGaussianBlur(tmpImg, tmpImg.Bounds, Round(sX/4),Round(sY/4), 1);
- // t-l corner
- if (ss.X < 0) or (ss.Y < 0) then
- begin
- tmpRec := Rect(0, 0, sX, sY);
- l := rec.Left; t := rec.Top;
- if ss.X < 0 then dec(l, sX);
- if ss.Y < 0 then dec(t, sY);
- img.Copy(tmpImg, tmpRec, Rect(l,t,l+sX,t+sY));
- end;
- // t-r corner
- if (ss.X > 0) or (ss.Y < 0) then
- begin
- tmpRec := Rect(sX*2+1, 0, sX*3+1, sY);
- l := rec.Right; t := rec.Top;
- if ss.X < 0 then dec(l, sX);
- if ss.Y < 0 then dec(t, sY);
- img.Copy(tmpImg, tmpRec, Rect(l,t,l+sX,t+sY));
- end;
- // b-l corner
- if (ss.X < 0) or (ss.Y > 0) then
- begin
- tmpRec := Rect(0, sY*2+1, sX, sY*3+1);
- l := rec.Left; t := rec.Bottom;
- if ss.X < 0 then dec(l, sX);
- if ss.Y < 0 then dec(t, sY);
- img.Copy(tmpImg, tmpRec, Rect(l,t,l+sX,t+sY));
- end;
- // b-r corner
- if (ss.X > 0) or (ss.Y > 0) then
- begin
- tmpRec := Rect(sX*2+1, sY*2+1, sX*3+1, sY*3+1);
- l := rec.Right; t := rec.Bottom;
- if ss.X < 0 then dec(l, sX);
- if ss.Y < 0 then dec(t, sY);
- img.Copy(tmpImg, tmpRec, Rect(l,t,l+sX,t+sY));
- end;
- // l-edge
- if (ss.X < 0) then
- begin
- l := rec.Left; t := rec.Top+sY; b := rec.Bottom-1;
- if ss.Y < 0 then begin dec(t, sY); dec(b,sY); end;
- for i := 1 to sX do
- begin
- c := tmpImg.Pixel[sX-i, sY+1];
- FillColorVert(img, l-i, t, b, c);
- end;
- end;
- // t-edge
- if (ss.Y < 0) then
- begin
- l := rec.Left+sX; r := rec.Right-1; t := rec.Top;
- if ss.X < 0 then begin dec(l, sX); dec(r,sX); end;
- for i := 1 to sY do
- begin
- c := tmpImg.Pixel[sX+1, sY-i];
- FillColorHorz(img, l, r, t-i, c);
- end;
- end;
- // r-edge
- if (ss.X > 0) then
- begin
- r := rec.Right-1; t := rec.Top+sY; b := rec.Bottom-1;
- if ss.Y < 0 then begin dec(t, sY); dec(b,sY); end;
- for i := 1 to sX do
- begin
- c := tmpImg.Pixel[sX*2+i, sY+1];
- FillColorVert(img, r+i, t, b, c);
- end;
- end;
- // b-edge
- if (ss.Y > 0) then
- begin
- l := rec.Left+sX; r := rec.Right-1; b := rec.Bottom-1;
- if ss.X < 0 then begin dec(l, sX); dec(r,sX); end;
- for i := 1 to sY do
- begin
- c := tmpImg.Pixel[sX+1, sY*2+i];
- FillColorHorz(img, l, r, b+i, c);
- end;
- end;
- finally
- tmpImg.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure DrawShadow(img: TImage32; const polygon: TPathD;
- fillRule: TFillRule; depth: double; angleRads: double;
- color: TColor32; cutoutInsideShadow: Boolean);
- var
- polygons: TPathsD;
- begin
- setlength(polygons, 1);
- polygons[0] := polygon;
- DrawShadow(img, polygons, fillRule, depth,
- angleRads, color, cutoutInsideShadow);
- end;
- //------------------------------------------------------------------------------
- procedure DrawShadow(img: TImage32; const polygons: TPathsD;
- fillRule: TFillRule; depth: double; angleRads: double;
- color: TColor32; cutoutInsideShadow: Boolean);
- var
- x, y: double;
- blurSize, w,h: integer;
- rec: TRect;
- polys, shadowPolys: TPathsD;
- shadowImg: TImage32;
- begin
- rec := GetBounds(polygons);
- if IsEmptyRect(rec) or (depth < 1) then Exit;
- {$IFDEF CLOCKWISE_ROTATION_WITH_NEGATIVE_ANGLES}
- angleRads := -angleRads;
- {$ENDIF}
- NormalizeAngle(angleRads);
- GetSinCos(angleRads, y, x);
- depth := depth * 0.5;
- x := depth * x;
- y := depth * y;
- blurSize := Max(1,Round(depth / 2));
- Img32.Vector.InflateRect(rec, Ceil(depth*2), Ceil(depth*2));
- polys := TranslatePath(polygons, -rec.Left, -rec.Top);
- shadowPolys := TranslatePath(polys, x, y);
- RectWidthHeight(rec, w, h);
- shadowImg := TImage32.Create(w, h);
- try
- DrawPolygon(shadowImg, shadowPolys, fillRule, color);
- FastGaussianBlur(shadowImg, shadowImg.Bounds, blurSize, 1);
- if cutoutInsideShadow then EraseInsidePaths(shadowImg, polys, fillRule);
- img.CopyBlend(shadowImg, shadowImg.Bounds, rec, BlendToAlphaLine);
- finally
- shadowImg.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure DrawGlow(img: TImage32; const polygon: TPathD;
- fillRule: TFillRule; color: TColor32; blurRadius: integer);
- var
- polygons: TPathsD;
- begin
- setlength(polygons, 1);
- polygons[0] := polygon;
- DrawGlow(img, polygons, fillRule, color, blurRadius);
- end;
- //------------------------------------------------------------------------------
- procedure DrawGlow(img: TImage32; const polygons: TPathsD;
- fillRule: TFillRule; color: TColor32; blurRadius: integer);
- var
- w,h: integer;
- rec: TRect;
- glowPolys: TPathsD;
- glowImg: TImage32;
- begin
- rec := GetBounds(polygons);
- glowPolys := TranslatePath(polygons,
- blurRadius -rec.Left +1, blurRadius -rec.Top +1);
- Img32.Vector.InflateRect(rec, blurRadius +1, blurRadius +1);
- RectWidthHeight(rec, w, h);
- glowImg := TImage32.Create(w, h);
- try
- DrawPolygon(glowImg, glowPolys, fillRule, color);
- FastGaussianBlur(glowImg, glowImg.Bounds, blurRadius, 2);
- glowImg.ScaleAlpha(4);
- img.CopyBlend(glowImg, glowImg.Bounds, rec, BlendToAlphaLine);
- finally
- glowImg.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure Sharpen(img: TImage32; radius: Integer; amount: Integer);
- var
- i: Integer;
- amt: double;
- weightAmount: array [-255 .. 255] of Integer;
- bmpBlur: TImage32;
- pColor, pBlur: PARGB;
- begin
- if radius = 0 then Exit;
- amt := ClampRange(amount/10, 0.1, 5);
- radius := ClampRange(radius, 1, 10);
- for i := -255 to 255 do
- weightAmount[i] := Round(amt * i);
- bmpBlur := TImage32.Create(img); // clone self
- try
- pColor := PARGB(img.pixelBase);
- FastGaussianBlur(bmpBlur, bmpBlur.Bounds, radius, 2);
- pBlur := PARGB(bmpBlur.pixelBase);
- for i := 1 to img.Width * img.Height do
- begin
- if (pColor.A > 0) then
- begin
- pColor.R := ClampByte(pColor.R + weightAmount[pColor.R - pBlur.R]);
- pColor.G := ClampByte(pColor.G + weightAmount[pColor.G - pBlur.G]);
- pColor.B := ClampByte(pColor.B + weightAmount[pColor.B - pBlur.B]);
- end;
- Inc(pColor); Inc(pBlur);
- end;
- finally
- bmpBlur.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure InternalHatchBackground(img: TImage32; const rec: TRect;
- color1, color2: TColor32; hatchSize: Integer = 10);
- var
- i, j, imgWidth: Integer;
- pc: PColor32;
- colors: array[boolean] of TColor32;
- hatch: Boolean;
- x: integer;
- begin
- colors[false] := color1;
- colors[true] := color2;
- imgWidth := img.Width;
- for i := rec.Top to rec.Bottom -1 do
- begin
- pc := @img.Pixels[i * imgWidth + rec.Left];
- hatch := Odd(i div hatchSize);
- x := (rec.Left + 1) mod hatchSize;
- if x = 0 then hatch := not hatch;
- for j := rec.Left to rec.Right -1 do
- begin
- if pc^ = 0 then
- pc^ := colors[hatch]
- else if GetAlpha(pc^) < 255 then
- pc^ := BlendToOpaque(colors[hatch], pc^);
- inc(pc);
- inc(x);
- if x >= hatchSize then
- begin
- x := 0;
- hatch := not hatch;
- end;
- end;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure HatchBackground(img: TImage32; const rec: TRect;
- color1: TColor32 = clWhite32; color2: TColor32= $FFE8E8E8;
- hatchSize: Integer = 10); overload;
- begin
- if (rec.Right <= rec.Left) or (rec.Bottom - rec.Top <= 0) then Exit;
- img.BeginUpdate;
- try
- InternalHatchBackground(img, rec, color1, color2, hatchSize);
- finally
- img.EndUpdate;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure HatchBackground(img: TImage32;
- color1: TColor32; color2: TColor32; hatchSize: Integer);
- begin
- HatchBackground(img, img.Bounds, color1, color2, hatchSize);
- end;
- //------------------------------------------------------------------------------
- procedure GridBackground(img: TImage32; majorInterval, minorInterval: integer;
- fillColor: TColor32; majColor: TColor32; minColor: TColor32);
- var
- i, x,y, w,h: integer;
- path: TPathD;
- cr: TCustomColorRenderer;
- begin
- img.Clear(fillColor);
- w := img.Width; h := img.Height;
- NewPointDArray(path, 2, True);
- if img.AntiAliased then
- cr := TColorRenderer.Create(minColor) else
- cr := TAliasedColorRenderer.Create(minColor);
- try
- if minorInterval > 0 then
- begin
- //cr.SetColor(minColor);
- x := minorInterval;
- path[0] := PointD(x, 0); path[1] := PointD(x, h);;
- for i := 1 to (w div minorInterval) do
- begin
- Img32.Draw.DrawLine(img, path, 1, cr, esSquare);
- path[0].X := path[0].X + minorInterval;
- path[1].X := path[1].X + minorInterval;
- end;
- y := minorInterval;
- path[0] := PointD(0, y); path[1] := PointD(w, y);
- for i := 1 to (h div minorInterval) do
- begin
- Img32.Draw.DrawLine(img, path, 1, cr, esSquare);
- path[0].Y := path[0].Y + minorInterval;
- path[1].Y := path[1].Y + minorInterval;
- end;
- end;
- if majorInterval > minorInterval then
- begin
- cr.SetColor(majColor);
- x := majorInterval;
- path[0] := PointD(x, 0); path[1] := PointD(x, h);;
- for i := 1 to (w div majorInterval) do
- begin
- Img32.Draw.DrawLine(img, path, 1, cr, esSquare);
- path[0].X := path[0].X + majorInterval;
- path[1].X := path[1].X + majorInterval;
- end;
- y := majorInterval;
- path[0] := PointD(0, y); path[1] := PointD(w, y);
- for i := 1 to (h div majorInterval) do
- begin
- Img32.Draw.DrawLine(img, path, 1, cr, esSquare);
- path[0].Y := path[0].Y + majorInterval;
- path[1].Y := path[1].Y + majorInterval;
- end;
- end;
- finally
- cr.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- function ColorDifference(color1, color2: TColor32): cardinal;
- {$IFDEF INLINE} inline; {$ENDIF}
- var
- c1: TARGB absolute color1;
- c2: TARGB absolute color2;
- begin
- result := Abs(c1.R - c2.R) + Abs(c1.G - c2.G) + Abs(c1.B - c2.B);
- result := (result * 341) shr 10; // divide by 3
- end;
- //------------------------------------------------------------------------------
- procedure ReplaceExactColor(img: TImage32; oldColor, newColor: TColor32);
- var
- color: PColor32;
- i: Integer;
- begin
- color := img.PixelBase;
- for i := 0 to img.Width * img.Height -1 do
- begin
- if color^ = oldColor then color^ := newColor;
- inc(color);
- end;
- end;
- //------------------------------------------------------------------------------
- procedure RemoveColor(img: TImage32; color: TColor32);
- var
- fg: TARGB absolute color;
- bg: PARGB;
- i: Integer;
- Q: byte;
- begin
- if fg.A = 0 then Exit;
- bg := PARGB(img.PixelBase);
- for i := 0 to img.Width * img.Height -1 do
- begin
- if bg.A > 0 then
- begin
- // red
- if (bg.R > fg.R) then Q := bg.R - fg.R
- else if (bg.R < fg.R) then Q := DivTable[fg.R - bg.R, fg.R]
- else Q := 0;
- // green
- if (bg.G > fg.G) then Q := Max(Q, bg.G - fg.G)
- else if (bg.G < fg.G) then Q := Max(Q, DivTable[fg.G - bg.G, fg.G]);
- // blue
- if (bg.B > fg.B) then Q := Max(Q, bg.B - fg.B)
- else if (bg.B < fg.B) then Q := Max(Q, DivTable[fg.B - bg.B, fg.B]);
- // weight Q toward either fully opaque or fully translucent
- Q := Sigmoid[Q];
- if (Q = 0) then
- bg.Color := clNone32
- else if (Q < 255) then
- begin
- bg.A := MulTable[bg.A, Q];
- bg.R := DivTable[bg.R - MulTable[not Q, fg.R], Q];
- bg.G := DivTable[bg.G - MulTable[not Q, fg.G], Q];
- bg.B := DivTable[bg.B - MulTable[not Q, fg.B], Q];
- end;
- end;
- inc(bg);
- end;
- end;
- //------------------------------------------------------------------------------
- procedure FilterOnColor(img: TImage32; color: TColor32);
- var
- fg: TARGB absolute color;
- bg: PARGB;
- i: Integer;
- Q: byte;
- begin
- if fg.A = 0 then Exit;
- bg := PARGB(img.PixelBase);
- for i := 0 to img.Width * img.Height -1 do
- begin
- if bg.A > 0 then
- begin
- // red
- if (bg.R > fg.R) then
- Q := bg.R - fg.R
- else if (bg.R < fg.R) then
- Q := DivTable[fg.R - bg.R, fg.R]
- else
- Q := 0;
- // green
- if (bg.G > fg.G) then
- Q := Max(Q, bg.G - fg.G)
- else if (bg.G < fg.G) then
- Q := Max(Q, DivTable[fg.G - bg.G, fg.G]);
- // blue
- if (bg.B > fg.B) then
- Q := Max(Q, bg.B - fg.B)
- else if (bg.B < fg.B) then
- Q := Max(Q, DivTable[fg.B - bg.B, fg.B]);
- // weight Q toward either fully opaque or fully translucent
- Q := Sigmoid[Q];
- Q := MulTable[bg.A, not Q];
- bg.Color := color;
- bg.A := Q; // note: fg.A is ignored
- end;
- inc(bg);
- end;
- end;
- //------------------------------------------------------------------------------
- procedure FilterOnExactColor(img: TImage32; color: TColor32);
- var
- pc: PColor32;
- i: Integer;
- mask: TColor32;
- begin
- // alpha channel is ignored
- mask := $FFFFFF;
- color := color and mask;
- pc := img.PixelBase;
- for i := 0 to img.Width * img.Height -1 do
- begin
- if (pc^ and mask) <> color then pc^ := clNone32;
- inc(pc);
- end;
- end;
- //------------------------------------------------------------------------------
- procedure FilterOnAlpha(img: TImage32; alpha: byte; tolerance: byte);
- var
- bg: PARGB;
- i: Integer;
- begin
- bg := PARGB(img.PixelBase);
- for i := 0 to img.Width * img.Height -1 do
- begin
- if abs(bg.A - alpha) > tolerance then bg.A := 0;
- inc(bg);
- end;
- end;
- //------------------------------------------------------------------------------
- procedure RedEyeRemove(img: TImage32; const rect: TRect);
- var
- k: integer;
- cutout, mask: TImage32;
- path: TPathD;
- cutoutRec, rect3: TRect;
- radGrad: TRadialGradientRenderer;
- begin
- k := RectWidth(rect) * RectHeight(rect);
- if k < 120 then k := 2
- else if k < 230 then k := 3
- else k := 4;
- cutoutRec := rect;
- Img32.Vector.InflateRect(cutoutRec, k, k);
- cutout := TImage32.Create(img, cutoutRec);
- mask := TImage32.Create(cutout.Width, cutout.Height);
- radGrad := TRadialGradientRenderer.Create;
- try
- // fill behind the cutout with black also
- // blurring the fill to soften its edges
- rect3 := cutout.Bounds;
- Img32.Vector.InflateRect(rect3, -k, -k);
- path := Ellipse(rect3);
- DrawPolygon(mask, path, frNonZero, clBlack32);
- // given the very small area and small radius of the blur, the
- // speed improvement of BoxBlur over GaussianBlur is inconsequential.
- GaussianBlur(mask, mask.Bounds, k);
- img.CopyBlend(mask, mask.Bounds, cutoutRec, BlendToOpaque);
- // gradient fill to clNone32 a mask to soften cutout's edges
- path := Ellipse(cutoutRec);
- radGrad.SetParameters(rect3, clBlack32, clNone32);
- DrawPolygon(mask, path, frNonZero, radGrad);
- cutout.CopyBlend(mask, mask.Bounds, cutout.Bounds, BlendMaskLine);
- // now remove red from the cutout
- RemoveColor(cutout, clRed32);
- // finally replace the cutout ...
- img.CopyBlend(cutout, cutout.Bounds, cutoutRec, BlendToOpaque);
- finally
- mask.Free;
- cutout.Free;
- radGrad.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure EraseInsidePath(img: TImage32; const path: TPathD; fillRule: TFillRule);
- begin
- if assigned(path) then
- ErasePolygon(img, path, fillRule);
- end;
- //------------------------------------------------------------------------------
- procedure EraseInsidePaths(img: TImage32; const paths: TPathsD; fillRule: TFillRule);
- begin
- if assigned(paths) then
- ErasePolygon(img, paths, fillRule);
- end;
- //------------------------------------------------------------------------------
- procedure EraseOutsideRect(img: TImage32; const r, outsideBounds: TRect);
- begin
- // Fill the parts, that are in outsideBounds but not in r with zeros
- // whole top block
- if r.Top > outsideBounds.Top then
- img.FillRect(Rect(outsideBounds.Left, outsideBounds.Top, outsideBounds.Right, r.Top - 1), 0);
- // whole bottom block
- if r.Bottom < outsideBounds.Bottom then
- img.FillRect(Rect(outsideBounds.Left, r.Bottom + 1, outsideBounds.Right, outsideBounds.Bottom), 0);
- // remaining left block
- if r.Left > outsideBounds.Left then
- img.FillRect(Rect(outsideBounds.Left, r.Top, r.Left - 1, r.Bottom), 0);
- // remaining right block
- if r.Right < outsideBounds.Right then
- img.FillRect(Rect(r.Right + 1, r.Top, outsideBounds.Right, r.Bottom), 0);
- end;
- //------------------------------------------------------------------------------
- procedure EraseOutsidePath(img: TImage32; const path: TPathD;
- fillRule: TFillRule; const outsideBounds: TRect);
- var
- w, h: integer;
- renderer: TMaskRenderer;
- r: TRect;
- polygons: TPathsD;
- begin
- if not assigned(path) then Exit;
- RectWidthHeight(outsideBounds, w, h);
- if (w <= 0) or (h <= 0) then Exit;
- // We can skip the costly polygon rasterization if the path is
- // a rectangle
- if (fillRule in [frEvenOdd, frNonZero]) and IsSimpleRectanglePath(path, r) then
- begin
- EraseOutsideRect(img, r, outsideBounds);
- Exit;
- end;
- renderer := TMaskRenderer.Create;
- try
- SetLength(polygons, 1);
- polygons[0] := path;
- Rasterize(img, polygons, outsideBounds, fillRule, renderer);
- finally
- renderer.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure EraseOutsidePaths(img: TImage32; const paths: TPathsD;
- fillRule: TFillRule; const outsideBounds: TRect;
- rendererCache: TCustomRendererCache);
- var
- w, h: integer;
- renderer: TMaskRenderer;
- r: TRect;
- begin
- if not assigned(paths) then Exit;
- RectWidthHeight(outsideBounds, w, h);
- if (w <= 0) or (h <= 0) then Exit;
- // We can skip the costly polygon rasterization if the path is
- // a rectangle.
- if (fillRule in [frEvenOdd, frNonZero]) and IsSimpleRectanglePath(paths, r) then
- begin
- EraseOutsideRect(img, r, outsideBounds);
- Exit;
- end;
- if rendererCache = nil then
- renderer := TMaskRenderer.Create
- else
- renderer := rendererCache.MaskRenderer;
- try
- Rasterize(img, paths, outsideBounds, fillRule, renderer);
- finally
- if rendererCache = nil then
- renderer.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure Draw3D(img: TImage32; const polygon: TPathD;
- fillRule: TFillRule; height, blurRadius: double;
- colorLt: TColor32; colorDk: TColor32; angleRads: double);
- var
- polygons: TPathsD;
- begin
- setLength(polygons, 1);
- polygons[0] := polygon;
- Draw3D(img, polygons, fillRule, height, blurRadius, colorLt, colorDk, angleRads);
- end;
- //------------------------------------------------------------------------------
- procedure Draw3D(img: TImage32; const polygons: TPathsD;
- fillRule: TFillRule; height, blurRadius: double;
- colorLt: TColor32; colorDk: TColor32; angleRads: double);
- var
- tmp: TImage32;
- rec: TRect;
- paths, paths2: TPathsD;
- w,h: integer;
- x,y: double;
- begin
- rec := GetBounds(polygons);
- if IsEmptyRect(rec) then Exit;
- {$IFDEF CLOCKWISE_ROTATION_WITH_NEGATIVE_ANGLES}
- angleRads := -angleRads;
- {$ENDIF}
- GetSinCos(angleRads, y, x);
- paths := TranslatePath(polygons, -rec.Left, -rec.Top);
- RectWidthHeight(rec, w, h);
- tmp := TImage32.Create(w, h);
- try
- if GetAlpha(colorLt) > 0 then
- begin
- tmp.Clear(colorLt);
- paths2 := TranslatePath(paths, -height*x, -height*y);
- EraseInsidePaths(tmp, paths2, fillRule);
- FastGaussianBlur(tmp, tmp.Bounds, Round(blurRadius), 0);
- EraseOutsidePaths(tmp, paths, fillRule, tmp.Bounds);
- img.CopyBlend(tmp, tmp.Bounds, rec, BlendToAlphaLine);
- end;
- if GetAlpha(colorDk) > 0 then
- begin
- tmp.Clear(colorDk);
- paths2 := TranslatePath(paths, height*x, height*y);
- EraseInsidePaths(tmp, paths2, fillRule);
- FastGaussianBlur(tmp, tmp.Bounds, Round(blurRadius), 0);
- EraseOutsidePaths(tmp, paths, fillRule, tmp.Bounds);
- img.CopyBlend(tmp, tmp.Bounds, rec, BlendToAlphaLine);
- end;
- finally
- tmp.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- function RainbowColor(fraction: double; luminance: byte = 128): TColor32;
- var
- hsl: THsl;
- begin
- if (fraction < 0) or (fraction > 1) then
- fraction := frac(fraction);
- hsl.hue := Round(fraction * 255);
- hsl.sat := 255;
- hsl.lum := luminance;
- hsl.alpha := 255;
- Result := HslToRgb(hsl);
- end;
- //------------------------------------------------------------------------------
- function GradientColor(color1, color2: TColor32; frac: single): TColor32;
- var
- hsl1, hsl2: THsl;
- begin
- if (frac <= 0) then result := color1
- else if (frac >= 1) then result := color2
- else
- begin
- hsl1 := RgbToHsl(color1); hsl2 := RgbToHsl(color2);
- hsl1.hue := ClampByte(hsl1.hue*(1-frac) + hsl2.hue*frac);
- hsl1.sat := ClampByte(hsl1.sat*(1-frac) + hsl2.sat*frac);
- hsl1.lum := ClampByte(hsl1.lum*(1-frac) + hsl2.lum*frac);
- hsl1.alpha := ClampByte(hsl1.alpha*(1-frac) + hsl2.alpha*frac);
- Result := HslToRgb(hsl1);
- end;
- end;
- //------------------------------------------------------------------------------
- function MakeDarker(color: TColor32; percent: cardinal): TColor32;
- var
- hsl: THsl;
- begin
- hsl := RgbToHsl(color);
- hsl.lum := ClampByte(hsl.lum - (percent/100 * hsl.lum));
- Result := HslToRgb(hsl);
- end;
- //------------------------------------------------------------------------------
- function MakeLighter(color: TColor32; percent: cardinal): TColor32;
- var
- hsl: THsl;
- begin
- hsl := RgbToHsl(color);
- hsl.lum := ClampByte(hsl.lum + percent/100 * (255 - hsl.lum));
- Result := HslToRgb(hsl);
- end;
- //------------------------------------------------------------------------------
- function DrawButton(img: TImage32; const pt: TPointD;
- size: double; color: TColor32; buttonShape: TButtonShape;
- buttonAttributes: TButtonAttributes): TPathD;
- var
- i: integer;
- radius: double;
- rec: TRectD;
- lightSize, lightAngle: double;
- begin
- if (size < 5) then Exit;
- radius := size * 0.5;
- lightSize := radius * 0.25;
- rec := RectD(pt.X -radius, pt.Y -radius, pt.X +radius, pt.Y +radius);
- if baEraseBeneath in buttonAttributes then
- img.Clear(Rect(rec));
- case buttonShape of
- bsDiamond:
- begin
- NewPointDArray(Result, 4, True);
- for i := 0 to 3 do Result[i] := pt;
- Result[0].X := Result[0].X -radius;
- Result[1].Y := Result[1].Y -radius;
- Result[2].X := Result[2].X +radius;
- Result[3].Y := Result[3].Y +radius;
- end;
- bsSquare:
- begin
- Img32.Vector.InflateRect(rec, -1,-1);
- Result := Rectangle(rec);
- end;
- else
- Result := Ellipse(rec);
- end;
- lightAngle := angle225;
- img.BeginUpdate;
- try
- // nb: only need to cutout the inside shadow if
- // the pending color fill is semi-transparent
- if baShadow in buttonAttributes then
- DrawShadow(img, Result, frNonZero, lightSize *2,
- (lightAngle + angle180), $AA000000, GetAlpha(color) < $FE);
- if GetAlpha(color) > 2 then
- DrawPolygon(img, Result, frNonZero, color);
- if ba3D in buttonAttributes then
- Draw3D(img, Result, frNonZero, lightSize*2,
- Ceil(lightSize), $CCFFFFFF, $AA000000, lightAngle);
- DrawLine(img, Result, dpiAware1, clBlack32, esPolygon);
- finally
- img.EndUpdate;
- end;
- end;
- //------------------------------------------------------------------------------
- function AlphaAverage(color1, color2: TColor32): cardinal;
- {$IFDEF INLINE} inline; {$ENDIF}
- var
- c1: TARGB absolute color1;
- c2: TARGB absolute color2;
- begin
- result := (c1.A + c2.A) shr 1;
- end;
- //------------------------------------------------------------------------------
- function BlendAverage(bgColor, fgColor: TColor32): TColor32;
- var
- res: TARGB absolute Result;
- bg: TARGB absolute bgColor;
- fg: TARGB absolute fgColor;
- begin
- res.A := (fg.A + bg.A) shr 1;
- res.R := (fg.R + bg.R) shr 1;
- res.G := (fg.G + bg.G) shr 1;
- res.B := (fg.B + bg.B) shr 1;
- end;
- //------------------------------------------------------------------------------
- function BlendLinearBurn(bgColor, fgColor: TColor32): TColor32;
- var
- res: TARGB absolute Result;
- bg: TARGB absolute bgColor;
- fg: TARGB absolute fgColor;
- begin
- res.A := 255;
- res.R := Max(0, bg.R + fg.R - 255);
- res.G := Max(0, bg.G + fg.G - 255);
- res.B := Max(0, bg.B + fg.B - 255);
- end;
- //------------------------------------------------------------------------------
- function BlendColorDodge(bgColor, fgColor: TColor32): TColor32;
- var
- res: TARGB absolute Result;
- bg: TARGB absolute bgColor;
- fg: TARGB absolute fgColor;
- begin
- res.A := 255;
- res.R := DivTable[bg.R, not fg.R];
- res.G := DivTable[bg.G, not fg.G];
- res.B := DivTable[bg.B, not fg.B];
- end;
- //------------------------------------------------------------------------------
- procedure PencilEffect(img: TImage32; intensity: integer);
- var
- img2: TImage32;
- begin
- if img.IsEmpty then Exit;
- intensity := max(1, min(10, intensity));
- img.Grayscale;
- img2 := TImage32.Create(img);
- try
- img2.InvertColors;
- FastGaussianBlur(img2, img2.Bounds, intensity, 2);
- img.CopyBlend(img2, img2.Bounds, img.Bounds, BlendColorDodge);
- finally
- img2.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TraceContours(img: TImage32; intensity: integer);
- var
- i,j, w,h: integer;
- tmp, tmp2: TArrayOfColor32;
- s, s2: PColor32;
- d: PARGB;
- begin
- w := img.Width; h := img.Height;
- if w * h = 0 then Exit;
- NewColor32Array(tmp, w * h);
- NewColor32Array(tmp2, w * h);
- s := img.PixelRow[0]; d := @tmp[0];
- for j := 0 to h-1 do
- begin
- s2 := IncPColor32(s, 1);
- for i := 0 to w-2 do
- begin
- d.A := ColorDifference(s^, s2^);
- inc(s); inc(s2); inc(d);
- end;
- inc(s); inc(d);
- end;
- for j := 0 to w-1 do
- begin
- s := @tmp[j]; d := @tmp2[j];
- s2 := IncPColor32(s, w);
- for i := 0 to h-2 do
- begin
- d.A := AlphaAverage(s^, s2^);
- inc(s, w); inc(s2, w); inc(d, w);
- end;
- end;
- img.BlockNotify;
- img.AssignPixelArray(tmp2, w, h);
- img.UnblockNotify;
- if intensity < 1 then Exit;
- if intensity > 10 then
- intensity := 10; // range = 1-10
- img.ScaleAlpha(intensity);
- end;
- //------------------------------------------------------------------------------
- // FLOODFILL - AND SUPPORT FUNCTIONS
- //------------------------------------------------------------------------------
- type
- PFloodFillRec = ^TFloodFillRec;
- TFloodFillRec = record
- xLeft : Integer;
- xRight : Integer;
- y : Integer;
- dirY : Integer;
- next : PFloodFillRec;
- end;
- TFloodFillStack = class
- first : PFloodFillRec;
- maxY : integer;
- constructor Create(maxY: integer);
- destructor Destroy; override;
- procedure Push(xLeft, xRight,y, direction: Integer);
- procedure Pop(out xLeft, xRight,y, direction: Integer);
- function IsEmpty: Boolean;
- end;
- TFloodFillMask = class
- private
- img : TImage32;
- mask : TImage32;
- colorsRow : PColor32Array;
- maskRow : PColor32Array;
- initialColor : TColor32;
- compareFunc : TCompareFunctionEx;
- tolerance : Integer;
- public
- function Execute(imgIn, imgMaskOut: TImage32; x,y: integer;
- aTolerance: Byte = 0; compFunc: TCompareFunctionEx = nil): Boolean;
- procedure SetCurrentY(y: Integer);
- function IsMatch(x: Integer): Boolean;
- end;
- //------------------------------------------------------------------------------
- // TFloodFillStack methods
- //------------------------------------------------------------------------------
- constructor TFloodFillStack.Create(maxY: integer);
- begin
- self.maxY := maxY;
- end;
- //------------------------------------------------------------------------------
- destructor TFloodFillStack.Destroy;
- var
- ffr: PFloodFillRec;
- begin
- while assigned(first) do
- begin
- ffr := first;
- first := first.next;
- dispose(ffr);
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TFloodFillStack.Push(xLeft, xRight, y, direction: Integer);
- var
- ffr: PFloodFillRec;
- begin
- if ((y <= 0) and (direction = -1)) or
- ((y >= maxY) and (direction = 1)) then Exit;
- new(ffr);
- ffr.xLeft := xLeft;
- ffr.xRight := xRight;
- ffr.y := y;
- ffr.dirY := direction;
- ffr.next := first;
- first := ffr;
- end;
- //------------------------------------------------------------------------------
- procedure TFloodFillStack.Pop(out xLeft, xRight, y, direction: Integer);
- var
- ffr: PFloodFillRec;
- begin
- xLeft := first.xLeft;
- xRight := first.xRight;
- direction := first.dirY;
- y := first.y + direction;
- ffr := first;
- first := first.next;
- dispose(ffr);
- end;
- //------------------------------------------------------------------------------
- function TFloodFillStack.IsEmpty: Boolean;
- begin
- result := not assigned(first);
- end;
- //------------------------------------------------------------------------------
- // TFloodFillMask methods
- //------------------------------------------------------------------------------
- function TFloodFillMask.Execute(imgIn, imgMaskOut: TImage32; x,y: integer;
- aTolerance: Byte; compFunc: TCompareFunctionEx): Boolean;
- var
- ffs : TFloodFillStack;
- w,h : integer;
- xl, xr, xr2 : Integer;
- maxX : Integer;
- dirY : Integer;
- begin
- Result := Assigned(imgIn) and Assigned(imgMaskOut) and
- InRange(x,0,imgIn.Width -1) and InRange(y,0,imgIn.Height -1);
- if not Result then Exit;
- w := imgIn.Width; h := imgIn.Height;
- // make sure the mask is the size of the image
- imgMaskOut.SetSize(w,h);
- img := imgIn;
- mask := imgMaskOut;
- compareFunc := compFunc;
- tolerance := aTolerance;
- maxX := w -1;
- ffs := TFloodFillStack.create(h -1);
- try
- initialColor := imgIn.Pixel[x, y];
- xl := x; xr := x;
- SetCurrentY(y);
- IsMatch(x);
- while (xl > 0) and IsMatch(xl -1) do dec(xl);
- while (xr < maxX) and IsMatch(xr +1) do inc(xr);
- ffs.Push(xl, xr, y, -1); // down
- ffs.Push(xl, xr, y, 1); // up
- while not ffs.IsEmpty do
- begin
- ffs.Pop(xl, xr, y, dirY);
- SetCurrentY(y);
- xr2 := xl;
- // check left ...
- if IsMatch(xl) then
- begin
- while (xl > 0) and IsMatch(xl-1) do dec(xl);
- if xl <= xr2 -2 then
- ffs.Push(xl, xr2-2, y, -dirY);
- while (xr2 < maxX) and IsMatch(xr2+1) do inc(xr2);
- ffs.Push(xl, xr2, y, dirY);
- if xr2 >= xr +2 then
- ffs.Push(xr+2, xr2, y, -dirY);
- xl := xr2 +2;
- end;
- // check right ...
- while (xl <= xr) and not IsMatch(xl) do inc(xl);
- while (xl <= xr) do
- begin
- xr2 := xl;
- while (xr2 < maxX) and IsMatch(xr2+1) do inc(xr2);
- ffs.Push(xl, xr2, y, dirY);
- if xr2 >= xr +2 then
- begin
- ffs.Push(xr+2, xr2, y, -dirY);
- break;
- end;
- inc(xl, 2);
- while (xl <= xr) and not IsMatch(xl) do inc(xl);
- end;
- end;
- finally
- ffs.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TFloodFillMask.SetCurrentY(y: Integer);
- begin
- colorsRow := PColor32Array(img.PixelRow[y]);
- maskRow := PColor32Array(mask.PixelRow[y]);
- end;
- //------------------------------------------------------------------------------
- function TFloodFillMask.IsMatch(x: Integer): Boolean;
- var
- b: Byte;
- begin
- if (maskRow[x] > 0) then
- result := false
- else
- begin
- b := compareFunc(initialColor, colorsRow[x]);
- result := b < tolerance;
- if Result then
- maskRow[x] := tolerance - b else
- maskRow[x] := 1;
- end;
- end;
- //------------------------------------------------------------------------------
- function GetFloodFillMask(imgIn, imgMaskOut: TImage32; x, y: Integer;
- tolerance: Byte; compareFunc: TCompareFunctionEx): Boolean;
- var
- ffm: TFloodFillMask;
- begin
- if not Assigned(compareFunc) then compareFunc := CompareRGBEx;
- ffm := TFloodFillMask.Create;
- try
- Result := ffm.Execute(imgIn, imgMaskOut, x, y, tolerance, compareFunc);
- finally
- ffm.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure FloodFill(img: TImage32; x, y: Integer; newColor: TColor32;
- tolerance: Byte; compareFunc: TCompareFunctionEx);
- var
- i: Integer;
- pc, pm: PColor32;
- mask: TImage32;
- begin
- if not assigned(compareFunc) then
- begin
- compareFunc := CompareRGBEx;
- if tolerance = 0 then
- tolerance := FloodFillDefaultRGBTolerance;
- end;
- mask := TImage32.Create;
- try
- if not GetFloodFillMask(img, mask, x, y, tolerance, compareFunc) then
- Exit;
- pc := img.PixelBase;
- pm := mask.PixelBase;
- for i := 0 to img.Width * img.Height -1 do
- begin
- if (pm^ > 1) then pc^ := newColor;
- inc(pm); inc(pc);
- end;
- finally
- mask.free;
- end;
- end;
- //------------------------------------------------------------------------------
- // EMBOSS - AND SUPPORT FUNCTIONS
- //------------------------------------------------------------------------------
- function IncPWeightColor(pwc: PWeightedColor; cnt: Integer): PWeightedColor;
- begin
- result := PWeightedColor(PByte(pwc) + cnt * SizeOf(TWeightedColor));
- end;
- //------------------------------------------------------------------------------
- function Intensity(color: TColor32): byte;
- var
- c: TARGB absolute color;
- begin
- Result := (c.R * 61 + c.G * 174 + c.B * 21) shr 8;
- end;
- //------------------------------------------------------------------------------
- function Gray(color: TColor32): TColor32;
- var
- c: TARGB absolute color;
- res: TARGB absolute Result;
- begin
- res.A := c.A;
- res.R := Intensity(color);
- res.G := res.R;
- res.B := res.R;
- end;
- //------------------------------------------------------------------------------
- procedure Emboss(img: TImage32; radius: Integer;
- depth: Integer; luminance: Integer; preserveColor: Boolean);
- var
- yy,xx, x,y, w,h: Integer;
- b: byte;
- kernel: array [0 .. MaxBlur, 0 .. MaxBlur] of Integer;
- wca: TArrayOfWeightedColor;
- pc0, pcf, pcb: PColor32; // pointers to pixels (forward & backward in kernel)
- pw0, pw: PWeightedColor; // pointers to weight
- customGray: TColor32;
- pc: PColor32;
- const
- maxDepth = 50;
- begin
- // grayscale luminance as percent where 0% is black and 100% is white
- //(luminance is ignored when preserveColor = true)
- luminance := ClampRange(luminance, 0, 100);
- b := luminance *255 div 100;
- customGray := $FF000000 + b shl 16 + b shl 8 + b;
- ClampRange(radius, 1, 5);
- inc(depth);
- ClampRange(depth, 2, maxDepth);
- kernel[0][0] := 1;
- for y := 1 to radius do
- for x := 1 to radius do
- kernel[y][x] := depth;
- w := img.Width; h := img.Height;
- // nb: dynamic arrays are zero-initialized (unless they're a function result)
- SetLength(wca, w * h);
- pc0 := IncPColor32(img.PixelBase, radius * w);
- pw0 := @wca[radius * w];
- for y := radius to h -1 - radius do
- begin
- for x := radius to w -1 - radius do
- begin
- pw := IncPWeightColor(pw0, x);
- pcb := IncPColor32(pc0, x - 1);
- if preserveColor then
- begin
- pcf := IncPColor32(pc0, x);
- pw^.Add(pcf^, kernel[0,0]);
- inc(pcf);
- end else
- begin
- pw^.Add(customGray, kernel[0,0]);
- pcf := IncPColor32(pc0, x + 1);
- end;
- // parse the kernel ...
- for yy := 1 to radius do
- begin
- for xx := 1 to radius do
- begin
- pw^.Subtract(Gray(pcf^), kernel[yy,xx]);
- pw^.Add(Gray(pcb^), kernel[yy,xx]);
- dec(pcb); inc(pcf);
- end;
- dec(pcb, img.Width - radius);
- inc(pcf, img.Width - radius);
- end;
- end;
- inc(pc0, img.Width);
- inc(pw0, img.Width);
- end;
- pc := @img.Pixels[0]; pw := @wca[0];
- for x := 0 to img.width * img.Height - 1 do
- begin
- pc^ := pw.Color or $FF000000;
- inc(pc); inc(pw);
- end;
- end;
- //------------------------------------------------------------------------------
- // RamerDouglasPeucker - and support functions
- //------------------------------------------------------------------------------
- procedure RDP(const path: TPathD; startIdx, endIdx: integer;
- epsilonSqrd: double; var flags: TArrayOfInteger);
- var
- i, idx: integer;
- d, maxD: double;
- begin
- idx := 0;
- maxD := 0;
- for i := startIdx +1 to endIdx -1 do
- begin
- // PerpendicularDistSqrd - avoids expensive Sqrt()
- d := PerpendicularDistSqrd(path[i], path[startIdx], path[endIdx]);
- if d <= maxD then Continue;
- maxD := d;
- idx := i;
- end;
- if maxD < epsilonSqrd then Exit;
- flags[idx] := 1;
- if idx > startIdx + 1 then RDP(path, startIdx, idx, epsilonSqrd, flags);
- if endIdx > idx + 1 then RDP(path, idx, endIdx, epsilonSqrd, flags);
- end;
- //------------------------------------------------------------------------------
- function RamerDouglasPeucker(const path: TPathD;
- epsilon: double): TPathD;
- var
- i,j, len: integer;
- buffer: TArrayOfInteger;
- begin
- len := length(path);
- if len < 5 then
- begin
- result := Copy(path, 0, len);
- Exit;
- end;
- SetLength(buffer, len); // buffer is zero initialized
- buffer[0] := 1;
- buffer[len -1] := 1;
- RDP(path, 0, len -1, Sqr(epsilon), buffer);
- j := 0;
- SetLength(Result, len);
- for i := 0 to len -1 do
- if buffer[i] = 1 then
- begin
- Result[j] := path[i];
- inc(j);
- end;
- SetLength(Result, j);
- end;
- //------------------------------------------------------------------------------
- function RamerDouglasPeucker(const paths: TPathsD;
- epsilon: double): TPathsD;
- var
- i,j, len: integer;
- begin
- j := 0;
- len := length(paths);
- setLength(Result, len);
- for i := 0 to len -1 do
- begin
- Result[j] := RamerDouglasPeucker(paths[i], epsilon);
- if Result[j] <> nil then inc(j);
- end;
- setLength(Result, j);
- end;
- //------------------------------------------------------------------------------
- function GetNext(current, high: integer; var flags: array of Boolean): integer;
- begin
- Result := current +1;
- while (Result <= high) and flags[Result] do inc(Result);
- if (Result <= high) then Exit;
- Result := 0;
- while (flags[Result]) do inc(Result);
- end;
- //---------------------------------------------------------------------------
- function GetPrior(current, high: integer; var flags: array of Boolean): integer;
- begin
- Result := current;
- if (Result = 0) then Result := high
- else dec(Result);
- while (Result > 0) and flags[Result] do dec(Result);
- if not flags[Result] then Exit;
- Result := high;
- while flags[Result] do dec(Result);
- end;
- //---------------------------------------------------------------------------
- type
- PSimplifyRec = ^TSimplifyRec;
- TSimplifyRec = record
- pt : TPointD;
- pdSqrd : double;
- prev : PSimplifyRec;
- next : PSimplifyRec;
- isEndPt : Boolean;
- end;
- function SimplifyPath(const path: TPathD;
- shapeTolerance: double; isClosedPath: Boolean): TPathD;
- var
- i, iPrev, iNext, len, minLen: integer;
- tolSqrd: double;
- srArray: array of TSimplifyRec;
- current, last: PSimplifyRec;
- begin
- Result := nil;
- len := Length(path);
- if not isClosedPath then minLen := 2 else minLen := 3;
- if len < minLen then Exit;
- SetLength(srArray, len);
- for i := 0 to len -1 do
- with srArray[i] do
- begin
- iPrev := ModEx(i-1, len);
- iNext := ModEx(i+1, len);
- pt := path[i];
- prev := @srArray[iPrev];
- next := @srArray[iNext];
- pdSqrd := PerpendicularDistSqrd(path[i], path[iPrev], path[iNext]);
- isEndPt := not isClosedPath and ((i = 0) or (i = len -1));
- end;
- current := @srArray[0];
- last := current.prev;
- tolSqrd := Sqr(shapeTolerance);
- while current <> last do
- begin
- if not current.isEndPt and
- ((current.pdSqrd < tolSqrd) and (current.next.pdSqrd > current.pdSqrd)) then
- begin
- current.prev.next := current.next;
- current.next.prev := current.prev;
- last := current.prev;
- dec(len);
- if last.next = last.prev then break;
- last.pdSqrd := PerpendicularDistSqrd(last.pt, last.prev.pt, last.next.pt);
- current := last.next;
- current.pdSqrd := PerpendicularDistSqrd(current.pt, current.prev.pt, current.next.pt);
- end
- else
- current := current.next;
- end;
- if len < minLen then Exit;
- if not isClosedPath then current := @srArray[0];
- NewPointDArray(Result, len, True);
- for i := 0 to len -1 do
- begin
- Result[i] := current.pt;
- current := current.next;
- end;
- end;
- //------------------------------------------------------------------------------
- function SimplifyPaths(const paths: TPathsD;
- shapeTolerance: double; isClosedPath: Boolean): TPathsD;
- var
- i,j, len: integer;
- begin
- len := Length(paths);
- SetLength(Result, len);
- j := 0;
- for i := 0 to len -1 do
- begin
- result[j] := SimplifyPath(paths[i], shapeTolerance, isClosedPath);
- if Length(result[j]) > 0 then inc(j);
- end;
- SetLength(Result, j);
- end;
- //---------------------------------------------------------------------------
- //---------------------------------------------------------------------------
- type
- PSimplifyExRec = ^TSimplifyExRec;
- TSimplifyExRec = record
- pt : TPointD;
- pdSqrd : double;
- segLenSq : double;
- prev : PSimplifyExRec;
- next : PSimplifyExRec;
- end;
- function DeleteCurrent(var current: PSimplifyExRec): Boolean;
- var
- next: PSimplifyExRec;
- begin
- current.prev.next := current.next;
- current.next.prev := current.prev;
- current := current.prev;
- next := current.next;
- Result := next <> current.prev;
- if not Result then Exit;
- next.pdSqrd := PerpendicularDistSqrd(next.pt, next.prev.pt, next.next.pt);
- current.segLenSq := DistanceSqrd(current.pt, current.next.pt);
- current.pdSqrd := PerpendicularDistSqrd(current.pt, current.prev.pt, current.next.pt);
- end;
- //---------------------------------------------------------------------------
- function SimplifyPathEx(const path: TPathD; shapeTolerance: double): TPathD;
- var
- i, prevI, nextI, len: integer;
- shapeTolSqr: double;
- srArray: array of TSimplifyExRec;
- current, start: PSimplifyExRec;
- begin
- Result := nil;
- len := Length(path);
- if len < 3 then Exit;
- shapeTolSqr := Sqr(shapeTolerance);
- SetLength(srArray, len);
- for i := 0 to len -1 do
- begin
- prevI := i -1;
- nextI := i +1;
- if i = 0 then prevI := len -1
- else if i = len -1 then nextI := 0;
- with srArray[i] do
- begin
- pt := path[i];
- segLenSq:= DistanceSqrd(path[i], path[nextI]);
- pdSqrd := PerpendicularDistSqrd(path[i], path[prevI], path[nextI]);
- prev := @srArray[prevI];
- next := @srArray[nextI];
- end;
- end;
- current := @srArray[0];
- start := current.prev;
- while current <> start do
- begin
- // Irrespective of segment length, remove vertices that deviate very little
- // from imaginary lines that pass through their adjacent vertices.
- // However, if the following vertex has an even sorter distance from its
- // respective imaginary line, its important to remove that vertex first.
- if ((current.pdSqrd < shapeTolSqr) and
- (current.pdSqrd < current.next.pdSqrd)) then
- begin
- dec(len);
- if not DeleteCurrent(current) then Break;
- start := current.prev;
- end
- // also remove insignificant path zig-zags
- else if (current.prev.segLenSq < shapeTolSqr) and
- (current.segLenSq < shapeTolSqr) and
- ((CrossProduct(current.prev.pt, current.pt, current.next.pt) > 0) <>
- (CrossProduct(current.pt, current.next.pt, current.next.next.pt) > 0)) then
- begin
- dec(len);
- if not DeleteCurrent(current) then Break;
- start := current.prev;
- end else
- current := current.next;
- end;
- if len < 3 then Exit;
- NewPointDArray(Result, len, True);
- for i := 0 to len -1 do
- begin
- Result[i] := current.pt;
- current := current.next;
- end;
- end;
- //------------------------------------------------------------------------------
- function SimplifyPathsEx(const paths: TPathsD; shapeTolerance: double): TPathsD;
- var
- i,j, len: integer;
- begin
- len := Length(paths);
- SetLength(Result, len);
- j := 0;
- for i := 0 to len -1 do
- begin
- Result[j] := SimplifyPathEx(paths[i], shapeTolerance);
- if Length(Result[j]) > 0 then inc(j);
- end;
- SetLength(Result, len);
- end;
- //---------------------------------------------------------------------------
- //---------------------------------------------------------------------------
- function DotProdVecs(const vec1, vec2: TPointD): double;
- {$IFDEF INLINE} inline; {$ENDIF}
- begin
- result := (vec1.X * vec2.X + vec1.Y * vec2.Y);
- end;
- //---------------------------------------------------------------------------
- function SmoothToCubicBezier(const path: TPathD;
- pathIsClosed: Boolean; maxOffset: integer): TPathD;
- var
- i, j, len, prev: integer;
- vec: TPointD;
- pl: TArrayOfDouble;
- unitVecs: TPathD;
- d, angle, d1,d2: double;
- begin
- // SmoothToCubicBezier - returns cubic bezier control points
- Result := nil;
- len := Length(path);
- if len < 3 then Exit;
- NewPointDArray(Result, len *3 +1, True);
- prev := len-1;
- SetLength(pl, len);
- SetLength(unitVecs, len);
- pl[0] := Distance(path[prev], path[0]);
- unitVecs[0] := GetUnitVector(path[prev], path[0]);
- for i := 0 to len -1 do
- begin
- if i = prev then
- begin
- j := 0;
- end else
- begin
- j := i +1;
- pl[j] := Distance(path[i], path[j]);
- unitVecs[j] := GetUnitVector(path[i], path[j]);
- end;
- vec := GetAvgUnitVector(unitVecs[i], unitVecs[j]);
- angle := arccos(Max(-1,Min(1,(DotProdVecs(unitVecs[i], unitVecs[j])))));
- d := abs(Pi-angle)/TwoPi;
- d1 := pl[i] * d;
- d2 := pl[j] * d;
- if maxOffset > 0 then
- begin
- d1 := Min(maxOffset, d1);
- d2 := Min(maxOffset, d2);
- end;
- if i = 0 then
- Result[len*3-1] := TranslatePoint(path[0], -vec.X * d1, -vec.Y * d1)
- else
- Result[i*3-1] := TranslatePoint(path[i], -vec.X * d1, -vec.Y * d1);
- Result[i*3] := path[i];
- Result[i*3+1] := TranslatePoint(path[i], vec.X * d2, vec.Y * d2);
- end;
- Result[len*3] := path[0];
- if pathIsClosed then Exit;
- Result[1] := Result[0];
- dec(len);
- Result[len*3-1] := Result[len*3];
- SetLength(Result, Len*3 +1);
- end;
- //------------------------------------------------------------------------------
- function SmoothToCubicBezier(const paths: TPathsD;
- pathIsClosed: Boolean; maxOffset: integer = 0): TPathsD;
- var
- i, len: integer;
- begin
- len := Length(paths);
- SetLength(Result, len);
- for i := 0 to len -1 do
- Result[i] := SmoothToCubicBezier(paths[i], pathIsClosed, maxOffset);
- end;
- //------------------------------------------------------------------------------
- function SmoothToCubicBezier2(const path: TPathD;
- pathIsClosed: Boolean; maxOffset: integer): TPathD;
- var
- i, j, len, prev: integer;
- vec: TPointD;
- pl: TArrayOfDouble;
- unitVecs: TPathD;
- d1,d2: double;
- begin
- // SmoothToCubicBezier2 - returns cubic bezier control points
- Result := nil;
- len := Length(path);
- if len < 3 then Exit;
- NewPointDArray(Result, len *3 +1);
- prev := len-1;
- SetLength(pl, len);
- SetLength(unitVecs, len);
- pl[0] := Distance(path[prev], path[0]);
- unitVecs[0] := GetUnitVector(path[prev], path[0]);
- for i := 0 to len -1 do
- begin
- if i = prev then
- begin
- j := 0;
- end else
- begin
- j := i +1;
- pl[j] := Distance(path[i], path[j]);
- unitVecs[j] := GetUnitVector(path[i], path[j]);
- end;
- vec := GetAvgUnitVector(unitVecs[i], unitVecs[j]);
- d1 := pl[i]/2;
- d2 := pl[j]/2;
- if maxOffset > 0 then
- begin
- d1 := Min(maxOffset, d1);
- d2 := Min(maxOffset, d2);
- end;
- if i = 0 then
- Result[len*3-1] := TranslatePoint(path[0], -vec.X * d1, -vec.Y * d1)
- else
- Result[i*3-1] := TranslatePoint(path[i], -vec.X * d1, -vec.Y * d1);
- Result[i*3] := path[i];
- Result[i*3+1] := TranslatePoint(path[i], vec.X * d2, vec.Y * d2);
- end;
- Result[len*3] := path[0];
- if pathIsClosed then Exit;
- Result[1] := Result[0];
- dec(len);
- Result[len*3-1] := Result[len*3];
- SetLength(Result, Len*3 +1);
- end;
- //------------------------------------------------------------------------------
- function SmoothToCubicBezier2(const paths: TPathsD;
- pathIsClosed: Boolean; maxOffset: integer = 0): TPathsD;
- var
- i, len: integer;
- begin
- len := Length(paths);
- SetLength(Result, len);
- for i := 0 to len -1 do
- Result[i] := SmoothToCubicBezier2(paths[i], pathIsClosed, maxOffset);
- end;
- //------------------------------------------------------------------------------
- function CubicInterpolate(v1, v2, v3, v4: double;
- t: double; tension: double = 0): double;
- var
- m0, m1, tt, ttt, tensionEx: double;
- a, b: double;
- begin
- tt := t * t;
- ttt := tt * t;
- tensionEx := (1-tension) * 0.5;
- m0 := (v3 - v1)*tensionEx;
- m1 := (v4 - v2)*tensionEx;
- a := 2*v2 - 2*v3 + m0 + m1;
- b := 3*v3 -3*v2 -2*m0 - m1;
- Result := a*ttt + b*tt + m0*t + v2;
- end;
- //------------------------------------------------------------------------------
- procedure Append(var path: TPathD; const pt: TPointD);
- {$IFDEF INLINE} inline; {$ENDIF}
- var
- len: integer;
- begin
- len := Length(path);
- SetLengthUninit(path, len +1);
- path[len] := pt;
- end;
- //------------------------------------------------------------------------------
- function SmoothPath(const path: TPathD; isClosedPath: Boolean;
- tension: double; shapeTolerance: double): TPathD;
- var
- i, j, highI, len, cnt: integer;
- pt: TPointD;
- dists: TArrayOfDouble;
- const
- maxInterval = 1.5;
- begin
- Result := nil;
- len := Length(path);
- if len < 3 then Exit;
- SetLength(dists, len);
- highI := len -1;
- dists[highI] := Distance(path[highI], path[0]);
- for i := 0 to highI-1 do
- dists[i] := Distance(path[i], path[i+1]);
- if tension > 1 then tension := 1
- else if tension < -1 then tension := -1;
- if tension > 0.9 then
- begin
- Result := path;
- Exit;
- end;
- if isClosedPath then
- for i := 0 to highI do
- begin
- cnt := Ceil(dists[i]/maxInterval);
- Append(Result, path[i]);
- for j := 1 to cnt -1 do
- begin
- pt.X := CubicInterpolate(
- path[ModEx(i-1, len)].X,
- path[i].X,
- path[ModEx(i+1, len)].X,
- path[ModEx(i+2, len)].X, j/cnt, tension);
- pt.Y := CubicInterpolate(
- path[ModEx(i-1, len)].Y,
- path[i].Y,
- path[ModEx(i+1, len)].Y,
- path[ModEx(i+2, len)].Y, j/cnt, tension);
- Append(Result, pt);
- end;
- end
- else
- begin
- for i := 0 to highI -1 do
- begin
- cnt := Ceil(dists[i]/maxInterval);
- Append(Result, path[i]);
- for j := 1 to cnt -1 do
- begin
- pt.X := CubicInterpolate(
- path[Clamp(i-1, len)].X,
- path[Clamp(i, len)].X,
- path[Clamp(i+1, len)].X,
- path[Clamp(i+2, len)].X, j/cnt, tension);
- pt.Y := CubicInterpolate(
- path[Clamp(i-1, len)].Y,
- path[Clamp(i, len)].Y,
- path[Clamp(i+1, len)].Y,
- path[Clamp(i+2, len)].Y, j/cnt, tension);
- Append(Result, pt);
- end;
- end;
- Append(Result, path[highi]);
- end;
- Result := SimplifyPath(Result, shapeTolerance, false);
- end;
- //------------------------------------------------------------------------------
- function SmoothPaths(const paths: TPathsD; isClosedPath: Boolean;
- tension: double = 0; shapeTolerance: double = 0.1): TPathsD;
- var
- i, len: integer;
- begin
- len := Length(paths);
- SetLength(Result, len);
- for i := 0 to len -1 do
- Result[i] := SmoothPath(paths[i], isClosedPath, tension, shapeTolerance);
- end;
- //------------------------------------------------------------------------------
- // GaussianBlur
- //------------------------------------------------------------------------------
- procedure GaussianBlur(img: TImage32; rec: TRect; radius: Integer);
- var
- i, w,h, highX, x,y,yy,z,startz: Integer;
- expConst: double;
- gaussTable: array [-MaxBlur .. MaxBlur] of integer;
- wc: TWeightedColor;
- wca: TArrayOfWeightedColor;
- wcaColor: TArrayOfColor32;
- row: PColor32Array;
- wcRow: PWeightedColorArray;
- imgWidth: Integer;
- dst, pc: PColor32;
- const
- tableConst = 1024;
- sigma = 3;
- begin
- Types.IntersectRect(rec, rec, img.Bounds);
- if IsEmptyRect(rec) or (radius < 1) then Exit
- else if radius > MaxBlur then radius := MaxBlur;
- expConst := - 1 / (Sqr(radius) * 2 * Sqr(sigma));
- gaussTable[0] := Round(tableConst * Exp(expConst));
- for i := 1 to radius do
- begin
- gaussTable[i] := Round(tableConst * Exp(expConst * Sqr(i)));
- gaussTable[-i] := gaussTable[i];
- end;
- RectWidthHeight(rec, w, h);
- setLength(wca, w * h);
- NewColor32Array(wcaColor, w * h, True);
- imgWidth := img.Width;
- highX := imgWidth -1;
- for y := 0 to h -1 do
- begin
- row := PColor32Array(@img.Pixels[(y + rec.Top) * imgWidth + rec.Left]);
- wcRow := PWeightedColorArray(@wca[y * w]);
- for x := 0 to w -1 do
- for z := max(0, x - radius) to min(highX, x + radius) do
- wcRow[x].Add(row[z], gaussTable[x-z]);
- end;
- // calculate colors
- for x := 0 to w * h - 1 do
- wcaColor[x] := wca[x].Color;
- dst := @img.Pixels[rec.Left + rec.Top * imgWidth];
- imgWidth := imgWidth * SizeOf(TColor32); // convert to byte size
- for x := 0 to w -1 do
- begin
- pc := dst;
- inc(pc, x);
- for y := 0 to h -1 do
- begin
- wc.Reset;
- startz := max(0, y - radius);
- yy := startz * w;
- for z := startz to min(h -1, y + radius) do
- begin
- wc.Add(wcaColor[x + yy], gaussTable[y-z]);
- inc(yy, w);
- end;
- pc^ := wc.Color;
- inc(PByte(pc), imgWidth); // increment by byte size
- end;
- end;
- end;
- //------------------------------------------------------------------------------
- // FastGaussian blur - and support functions
- //------------------------------------------------------------------------------
- //http://blog.ivank.net/fastest-gaussian-blur.html
- //https://www.peterkovesi.com/papers/FastGaussianSmoothing.pdf
- function BoxesForGauss(stdDev, boxCnt: integer): TArrayOfInteger;
- var
- i, wl, wu, m: integer;
- wIdeal, mIdeal: double;
- begin
- NewIntegerArray(Result, boxCnt, True);
- wIdeal := Sqrt((12*stdDev*stdDev/boxCnt)+1); // Ideal averaging filter width
- wl := Floor(wIdeal); if not Odd(wl) then dec(wl);
- mIdeal :=
- (-3*stdDev*stdDev +0.25*boxCnt*wl*wl +boxCnt*wl +0.75*boxCnt)/(wl+1);
- m := Floor(mIdeal) div 2; // nb: variation on Ivan Kutskir's code.
- wl := (wl -1) div 2; // It's better to do this here
- wu := wl+1; // than later in both BoxBlurH & BoxBlurV
- for i := 0 to boxCnt -1 do
- if i < m then
- Result[i] := wl else
- Result[i] := wu;
- end;
- //------------------------------------------------------------------------------
- procedure FastGaussianBlur(img: TImage32;
- const rec: TRect; stdDev: integer; repeats: integer);
- begin
- FastGaussianBlur(img, rec, stdDev, stdDev, repeats);
- end;
- //------------------------------------------------------------------------------
- procedure BoxBlurHLine(src, dst: PColor32; srcRiOffset: nativeint;
- count, w: integer; dstLast: PColor32; var v: TWeightedColor);
- var
- lastColor: TColor32;
- val: PWeightedColor;
- s, d: PColor32;
- begin
- lastColor := v.Color;
- if count > w then
- count := w;
- w := w - count;
- // The Delphi compiler sometimes is really stupid with
- // the CPU register allocation. With this, even if no actual
- // code is produced, the compiler happens to make better
- // decisions.
- val := @v;
- s := src;
- d := dst;
- if count > 0 then
- begin
- while count > 0 do
- begin
- if val.AddSubtract(PColor32Array(s)[srcRiOffset], s^) then
- lastColor := val.Color;
- inc(s);
- d^ := lastColor;
- inc(d);
- dec(count);
- end;
- count := w;
- while count > 0 do
- begin
- d^ := lastColor;
- inc(d);
- dec(count);
- end;
- end;
- while PByte(d) <= PByte(dstLast) do
- begin
- if val.AddNoneSubtract(s^) then
- lastColor := val.Color;
- inc(s);
- d^ := lastColor;
- inc(d);
- end;
- end;
- //------------------------------------------------------------------------------
- procedure BoxBlurH(const src, dst: TArrayOfColor32; w,h, stdDev: integer);
- var
- i,j, ti, li, ri, re, ovr: integer;
- fv, val: TWeightedColor;
- lastColor: TColor32;
- stdDevW: integer;
- begin
- ovr := Max(0, stdDev - w);
- for i := 0 to h -1 do
- begin
- ti := i * w;
- li := ti;
- ri := ti +stdDev;
- re := ti +w -1; // idx of last pixel in row
- fv.Reset(src[ti]);
- val.Reset(src[ti], stdDev +1);
- for j := 0 to stdDev -1 - ovr do
- val.Add(src[ti + j]);
- if ovr > 0 then val.Add(clNone32, ovr);
- for j := 0 to stdDev do
- begin
- if ri <= re then
- val.Add(src[ri]) else
- val.Add(src[re]); // color of last pixel in row
- inc(ri);
- val.Subtract(fv);
- if ti <= re then
- dst[ti] := val.Color;
- inc(ti);
- end;
- // Skip "val.Color" calculation if both for-loops are skipped anyway
- stdDevW := w - stdDev*2 - 1;
- if (ti <= re) or (stdDevW > 0) then
- begin
- if w > 4 then // prevent the call-overhead if it would be slower than the inline version
- BoxBlurHLine(@src[li], @dst[ti], ri - li, re - ri + 1, stdDevW, @dst[re], val)
- else
- begin
- lastColor := val.Color;
- for j := stdDevW downto 1 do
- begin
- if ri <= re then
- begin
- if val.AddSubtract(src[ri], src[li]) then
- lastColor := val.Color;
- inc(ri);
- inc(li);
- end;
- dst[ti] := lastColor;
- inc(ti);
- end;
- while ti <= re do
- begin
- if val.AddNoneSubtract(src[li]) then
- lastColor := val.Color;
- inc(li);
- dst[ti] := lastColor;
- inc(ti);
- end;
- end;
- end;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure BoxBlurVLine(src, dst: PColor32; srcRiOffset: nativeint;
- widthBytes, count, h: integer; dstLast: PColor32; var v: TWeightedColor);
- var
- lastColor: TColor32;
- val: PWeightedColor;
- s, d: PColor32;
- begin
- lastColor := v.Color;
- if count > h then
- count := h;
- h := h - count;
- // The Delphi compiler sometimes is really stupid with
- // the CPU register allocation. With this, even if no actual
- // code is produced, the compiler happens to make better
- // decisions.
- val := @v;
- s := src;
- d := dst;
- if count > 0 then
- begin
- while count > 0 do
- begin
- if val.AddSubtract(PColor32Array(s)[srcRiOffset], s^) then
- lastColor := val.Color;
- inc(PByte(s), widthBytes);
- d^ := lastColor;
- inc(PByte(d), widthBytes);
- dec(count);
- end;
- count := h;
- while count > 0 do
- begin
- d^ := lastColor;
- inc(PByte(d), widthBytes);
- dec(count);
- end;
- end;
- while PByte(d) <= PByte(dstLast) do
- begin
- if val.AddNoneSubtract(s^) then
- lastColor := val.Color;
- inc(PByte(s), widthBytes);
- d^ := lastColor;
- inc(PByte(d), widthBytes);
- end;
- end;
- //------------------------------------------------------------------------------
- procedure BoxBlurV(const src, dst: TArrayOfColor32; w, h, stdDev: integer);
- var
- i,j, ti, li, ri, re, ovr: integer;
- fv, val: TWeightedColor;
- lastColor: TColor32;
- stdDevH: integer;
- begin
- ovr := Max(0, stdDev - h);
- for i := 0 to w -1 do
- begin
- ti := i;
- li := ti;
- ri := ti + stdDev * w;
- re := ti +w *(h-1); // idx of last pixel in column
- fv.Reset(src[ti]);
- val.Reset(src[ti], stdDev +1);
- for j := 0 to stdDev -1 -ovr do
- val.Add(src[ti + j *w]);
- if ovr > 0 then val.Add(clNone32, ovr);
- for j := 0 to stdDev do
- begin
- if ri <= re then
- val.Add(src[ri]) else
- val.Add(src[re]); // color of last pixel in column
- inc(ri, w);
- val.Subtract(fv);
- if ti <= re then
- dst[ti] := val.Color;
- inc(ti, w);
- end;
- // Skip "val.Color" calculation if both for-loops are skipped anyway
- stdDevH := h - stdDev*2 - 1;
- if (ti <= re) or (stdDevH > 0) then
- begin
- if stdDevH > 4 then // prevent the call-overhead if it would be slower than the inline version
- BoxBlurVLine(@src[li], @dst[ti], ri - li, w * SizeOf(TColor32), re - ri + 1, stdDevH, @dst[re], val)
- else
- begin
- lastColor := val.Color;
- for j := stdDevH downto 1 do
- begin
- if ri <= re then
- begin
- if val.AddSubtract(src[ri], src[li]) then
- lastColor := val.Color;
- inc(ri, w);
- inc(li, w);
- end;
- dst[ti] := lastColor;
- inc(ti, w);
- end;
- while ti <= re do
- begin
- if val.AddNoneSubtract(src[li]) then
- lastColor := val.Color;
- inc(li, w);
- dst[ti] := lastColor;
- inc(ti, w);
- end;
- end;
- end;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure FastGaussianBlur(img: TImage32;
- const rec: TRect; stdDevX, stdDevY: integer; repeats: integer);
- var
- i,j,len, w,h: integer;
- rec2: TRect;
- boxesH: TArrayOfInteger;
- boxesV: TArrayOfInteger;
- src, dst: TArrayOfColor32;
- blurFullImage: Boolean;
- pSrc, pDst: PColor32;
- begin
- if not Assigned(img) then Exit;
- Types.IntersectRect(rec2, rec, img.Bounds);
- if IsEmptyRect(rec2) then Exit;
- blurFullImage := RectsEqual(rec2, img.Bounds);
- RectWidthHeight(rec2, w, h);
- if (Min(w, h) < 2) or ((stdDevX < 1) and (stdDevY < 1)) then Exit;
- len := w * h;
- NewColor32Array(src, len, True); // content is overwritten in BoxBlurH
- if blurFullImage then
- begin
- // Use the img.Pixels directly instead of copying the entire image into 'dst'.
- // The first thing the code does is BoxBlurH({source:=}dst, {dest:=}src, ...).
- dst := img.Pixels;
- end
- else
- begin
- // copy a rectangular region into 'dst'
- NewColor32Array(dst, len, True);
- pSrc := img.PixelRow[rec2.Top];
- inc(pSrc, rec2.Left);
- pDst := @dst[0];
- for i := 0 to h -1 do
- begin
- Move(pSrc^, pDst^, w * SizeOf(TColor32));
- inc(pSrc, img.Width);
- inc(pDst, w);
- end;
- end;
- // do the blur
- inc(repeats); // now represents total iterations
- boxesH := BoxesForGauss(stdDevX, repeats);
- if stdDevY = stdDevX then
- boxesV := boxesH else
- boxesV := BoxesForGauss(stdDevY, repeats);
- img.BeginUpdate;
- try
- for j := 0 to repeats -1 do
- begin
- BoxBlurH(dst, src, w, h, boxesH[j]);
- BoxBlurV(src, dst, w, h, boxesV[j]);
- end;
- if not blurFullImage then
- begin
- // copy dst array back to image rect
- pDst := img.PixelRow[rec2.Top];
- inc(pDst, rec2.Left);
- pSrc := @dst[0];
- for i := 0 to h -1 do
- begin
- Move(pSrc^, pDst^, w * SizeOf(TColor32));
- inc(pSrc, w);
- inc(pDst, img.Width);
- end;
- end;
- finally
- img.EndUpdate;
- end;
- end;
- //------------------------------------------------------------------------------
- end.
|