| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569 |
- unit Img32;
- (*******************************************************************************
- * Author : Angus Johnson *
- * Version : 4.8 *
- * Date : 10 January 2025 *
- * Website : http://www.angusj.com *
- * Copyright : Angus Johnson 2019-2025 *
- * Purpose : The core module of the Image32 library *
- * License : http://www.boost.org/LICENSE_1_0.txt *
- *******************************************************************************)
- interface
- {$I Img32.inc}
- uses
- Types, SysUtils, Classes,
- {$IFDEF MSWINDOWS} Windows,{$ENDIF}
- {$IFDEF USING_VCL_LCL}
- {$IFDEF USES_NAMESPACES} Vcl.Graphics, Vcl.Forms,
- {$ELSE}Graphics, Forms,
- {$ENDIF}
- {$ENDIF}
- {$IFDEF XPLAT_GENERICS}
- Generics.Collections, Generics.Defaults, Character,
- {$ENDIF}
- {$IFDEF UITYPES} UITypes,{$ENDIF} Math;
- type
- {$IF not declared(SizeInt)} // FPC has SizeInt
- {$IF CompilerVersion < 20.0}
- SizeInt = Integer; // Delphi 7-2007 can't use NativeInt with "FOR"
- SizeUInt = Cardinal; // Delphi 7-2007 can't use NativeUInt with "FOR"
- {$ELSE}
- SizeInt = NativeInt;
- SizeUInt = NativeUInt;
- {$IFEND}
- {$IFEND}
- TRect = Types.TRect;
- TColor32 = type Cardinal;
- TPointD = record
- X, Y: double;
- end;
- PARGB = ^TARGB;
- TARGB = packed record
- case boolean of
- false: (B: Byte; G: Byte; R: Byte; A: Byte);
- true : (Color: TColor32);
- end;
- TArrayOfARGB = array of TARGB;
- const
- clNone32 = TColor32($00000000);
- clAqua32 = TColor32($FF00FFFF);
- clBlack32 = TColor32($FF000000);
- clBlue32 = TColor32($FF0000FF);
- clFuchsia32 = TColor32($FFFF00FF);
- clGray32 = TColor32($FF808080);
- clGreen32 = TColor32($FF008000);
- clGrey32 = TColor32($FF808080);
- clLime32 = TColor32($FF00FF00);
- clMaroon32 = TColor32($FF800000);
- clNavy32 = TColor32($FF000080);
- clOlive32 = TColor32($FF7F7F00);
- clOrange32 = TColor32($FFFF7F00);
- clPurple32 = TColor32($FF7F00FF);
- clRed32 = TColor32($FFFF0000);
- clSilver32 = TColor32($FFC0C0C0);
- clTeal32 = TColor32($FF007F7F);
- clWhite32 = TColor32($FFFFFFFF);
- clYellow32 = TColor32($FFFFFF00);
- //custom gray colors
- clDarkGray32 = TColor32($FF505050);
- clDarkGrey32 = TColor32($FF505050);
- //clGray32 = TColor32($FF808080);
- //clSilver32 = TColor32($FFC0C0C0);
- clLiteGray32 = TColor32($FFD3D3D3);
- clLiteGrey32 = TColor32($FFD3D3D3);
- clPaleGray32 = TColor32($FFE0E0E0);
- clPaleGrey32 = TColor32($FFE0E0E0);
- clDarkBtn32 = TColor32($FFE8E8E8);
- clBtnFace32 = TColor32($FFF0F0F0);
- clLiteBtn32 = TColor32($FFF8F8F8);
- defaultCompression = -1;
- {$IFDEF ZEROBASEDSTR}
- {$ZEROBASEDSTRINGS OFF}
- {$ENDIF}
- RT_BITMAP = PChar(2);
- type
- {$IFDEF SUPPORTS_POINTERMATH}
- // Works for Delphi 2009 and newer. For FPC, POINTERMATH is
- // a requirement for negative indices. Otherwise 32bit and 64bit
- // code would behave differently since FPC doesn't otherwise
- // sign-extend the index variable of type Integer when it's used
- // as an array-index into an array with an unsigned index range.
- // i32:=-1; i64:=-1 => i32=i64 but @arr[i32] <> @arr[i64]
- PByteArray = PByte; // PByte already has PointerMath
- {$POINTERMATH ON}
- PDoubleArray = ^Double;
- PInt64Array = ^Int64;
- PColor32Array = ^TColor32;
- PARGBArray = ^TARGB;
- {$POINTERMATH OFF}
- {$ELSE} // Delphi 7-2007
- PByteArray = ^TStaticByteArray;
- TStaticByteArray = array[0..MaxInt div SizeOf(byte) - 1] of byte;
- PDoubleArray = ^TStaticDoubleArray;
- TStaticDoubleArray = array[0..MaxInt div SizeOf(double) - 1] of double;
- PInt64Array = ^TStaticInt64Array;
- TStaticInt64Array = array[0..MaxInt div SizeOf(int64) - 1] of int64;
- PColor32Array = ^TStaticColor32Array;
- TStaticColor32Array = array[0..MaxInt div SizeOf(TColor32) - 1] of TColor32;
- PARGBArray = ^TStaticARGBArray;
- TStaticARGBArray = array[0..MaxInt div SizeOf(TARGB) - 1] of TARGB;
- {$ENDIF}
- TArrayOfByte = array of Byte;
- TArrayOfWord = array of WORD;
- TArrayOfInteger = array of Integer;
- TArrayOfDouble = array of double;
- PColor32 = ^TColor32;
- TArrayOfColor32 = array of TColor32;
- TArrayOfArrayOfColor32 = array of TArrayOfColor32;
- TArrayOfString = array of string;
- TClipboardPriority = (cpLow, cpMedium, cpHigh);
- TImg32Notification = (inStateChange, inDestroy);
- //A INotifyRecipient receives change notifications though a property
- //interface from a single NotifySender (eg a Font property).
- //A NotifySender can send change notificatons to multiple NotifyRecipients
- //(eg where multiple object use the same font property). NotifyRecipients can
- //still receive change notificatons from mulitple NotifySenders, but it
- //must use a separate property for each NotifySender. (Also there's little
- //benefit in using INotifySender and INotifyRecipient interfaces where there
- //will only be one receiver - eg scroll - scrolling window.)
- INotifyRecipient = interface
- ['{95F50C62-D321-46A4-A42C-8E9D0E3149B5}']
- procedure ReceiveNotification(Sender: TObject; notify: TImg32Notification);
- end;
- TRecipients = array of INotifyRecipient;
- INotifySender = interface
- ['{52072382-8B2F-481D-BE0A-E1C0A216B03E}']
- procedure AddRecipient(recipient: INotifyRecipient);
- procedure DeleteRecipient(recipient: INotifyRecipient);
- end;
- TInterfacedObj = class(TObject, IInterface)
- public
- {$IFDEF FPC}
- function _AddRef: Integer;
- {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
- function _Release: Integer;
- {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
- function QueryInterface(
- {$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} iid : tguid;
- out obj) : longint;
- {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
- {$ELSE}
- function _AddRef: Integer; stdcall;
- function _Release: Integer; stdcall;
- function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
- {$ENDIF}
- end;
- TImage32 = class;
- TImageFormatClass = class of TImageFormat;
- //TImageFormat: Abstract base class for loading and saving images in TImage32.<br>
- //This class is overridden to provide support for separate
- //file storage formats (eg BMP, PNG, GIF & JPG).<br>
- //Derived classes register with TImage32 using TImage32.RegisterImageFormatClass.
- TImageFormat = class
- public
- class function IsValidImageStream(stream: TStream): Boolean; virtual; abstract;
- procedure SaveToStream(stream: TStream; img32: TImage32; quality: integer = 0); virtual; abstract;
- function SaveToFile(const filename: string; img32: TImage32; quality: integer = 0): Boolean; virtual;
- function LoadFromStream(stream: TStream;
- img32: TImage32; imgIndex: integer = 0): Boolean; virtual; abstract;
- function LoadFromFile(const filename: string; img32: TImage32): Boolean; virtual;
- class function GetImageCount(stream: TStream): integer; virtual;
- class function CanCopyToClipboard: Boolean; virtual;
- class function CopyToClipboard(img32: TImage32): Boolean; virtual; abstract;
- class function CanPasteFromClipboard: Boolean; virtual; abstract;
- class function PasteFromClipboard(img32: TImage32): Boolean; virtual; abstract;
- end;
- TBlendFunction = function(bgColor, fgColor: TColor32): TColor32;
- TBlendLineFunction = procedure(bgColor, fgColor: PColor32; width: nativeint);
- TCompareFunction = function(master, current: TColor32; data: integer): Boolean;
- TCompareFunctionEx = function(master, current: TColor32): Byte;
- TTileFillStyle = (tfsRepeat, tfsMirrorHorz, tfsMirrorVert, tfsRotate180);
- TResamplerFunction = function(img: TImage32; x, y: double): TColor32;
- TGrayscaleMode = (gsmSaturation, gsmLinear, gsmColorimetric);
- TImage32 = class(TObject)
- private
- fWidth: integer;
- fHeight: Integer;
- fResampler: integer;
- fIsPremultiplied: Boolean;
- fColorCount: integer;
- fPixels: TArrayOfColor32;
- fOnChange: TNotifyEvent;
- fOnResize: TNotifyEvent;
- fUpdateCnt: integer;
- fAntiAliased: Boolean;
- fNotifyBlockCnt: integer;
- function GetPixel(x,y: Integer): TColor32;
- procedure SetPixel(x,y: Integer; color: TColor32);
- function GetIsBlank: Boolean;
- function GetIsEmpty: Boolean;
- function GetPixelBase: PColor32;
- function GetPixelRow(row: Integer): PColor32;
- procedure RotateLeft90;
- procedure RotateRight90;
- procedure Rotate180;
- function GetColorCount: Integer;
- function GetHasTransparency: Boolean;
- function GetBounds: TRect;
- function GetMidPoint: TPointD;
- protected
- procedure ResetColorCount;
- function RectHasTransparency(const rec: TRect): Boolean;
- function CopyPixels(const rec: TRect): TArrayOfColor32;
- //CopyInternal: Internal routine (has no scaling or bounds checking)
- procedure CopyInternal(src: TImage32;
- const srcRec, dstRec: TRect; blendFunc: TBlendFunction);
- procedure CopyInternalLine(src: TImage32;
- const srcRec, dstRec: TRect; blendLineFunc: TBlendLineFunction);
- function CopyBlendInternal(src: TImage32; const srcRec: TRect; dstRec: TRect;
- blendFunc: TBlendFunction = nil; blendLineFunc: TBlendLineFunction = nil): Boolean; overload;
- procedure Changed; virtual;
- procedure Resized; virtual;
- function SetPixels(const newPixels: TArrayOfColor32): Boolean;
- property UpdateCount: integer read fUpdateCnt;
- public
- constructor Create(width: Integer = 0; height: Integer = 0); overload;
- //Create(src:array, width, height): Uses the specified array for the pixels.
- // Uses src for the pixels without copying it.
- constructor Create(const src: TArrayOfColor32; width: Integer; height: Integer); overload;
- constructor Create(src: TImage32); overload;
- constructor Create(src: TImage32; const srcRec: TRect); overload;
- destructor Destroy; override;
- //BeginUpdate/EndUpdate: postpones calls to OnChange event (can be nested)
- procedure BeginUpdate;
- procedure EndUpdate;
- //BlockUpdate/UnBlockUpdate: blocks calls to OnChange event (can be nested)
- procedure BlockNotify;
- procedure UnblockNotify;
- procedure Assign(src: TImage32);
- procedure AssignTo(dst: TImage32);
- procedure AssignSettings(src: TImage32);
- //AssignPixelArray: Replaces the content and takes ownership of src.
- // Uses src for the pixels without copying it.
- procedure AssignPixelArray(const src: TArrayOfColor32; width: Integer; height: Integer);
- //SetSize: Erases any current image, and fills with the specified color.
- procedure SetSize(newWidth, newHeight: Integer; color: TColor32 = 0);
- //Resize: is very similar to Scale()
- procedure Resize(newWidth, newHeight: Integer);
- procedure ResizeTo(targetImg: TImage32; newWidth, newHeight: Integer);
- //ScaleToFit: The image will be scaled proportionally
- procedure ScaleToFit(width, height: integer);
- //ScaleToFitCentered: The new image will be scaled and also centred
- procedure ScaleToFitCentered(width, height: integer); overload;
- procedure ScaleToFitCentered(const rect: TRect); overload;
- procedure Scale(s: double); overload;
- procedure Scale(sx, sy: double); overload;
- procedure ScaleTo(targetImg: TImage32; s: double); overload;
- procedure ScaleTo(targetImg: TImage32; sx, sy: double); overload;
- function Copy(src: TImage32; srcRec, dstRec: TRect): Boolean;
- //CopyBlend: Copies part or all of another image (src) on top of the
- //existing image. If no blend function is provided, then the function
- //will behave exactly as the Copy function above. However, when a blend
- //function is specified, that function will determine how the images will
- //be blended. If srcRec and dstRec have different widths or heights,
- //then the image in srcRec will also be stretched to fit dstRec.
- function CopyBlend(src: TImage32; const srcRec, dstRec: TRect;
- blendFunc: TBlendFunction = nil): Boolean; overload; {$IFDEF INLINE} inline; {$ENDIF}
- function CopyBlend(src: TImage32; const srcRec, dstRec: TRect;
- blendLineFunc: TBlendLineFunction): Boolean; overload; {$IFDEF INLINE} inline; {$ENDIF}
- {$IFDEF MSWINDOWS}
- //CopyFromDC: Copies an image from a Windows device context, erasing
- //any current image in TImage32. (eg copying from TBitmap.canvas.handle)
- procedure CopyFromDC(srcDc: HDC; const srcRect: TRect);
- //CopyToDc: Copies the image into a Windows device context
- procedure CopyToDc(dstDc: HDC; x: Integer = 0; y: Integer = 0;
- transparent: Boolean = true); overload;
- procedure CopyToDc(const srcRect: TRect; dstDc: HDC;
- x: Integer = 0; y: Integer = 0; transparent: Boolean = true); overload;
- procedure CopyToDc(const srcRect, dstRect: TRect; dstDc: HDC;
- transparent: Boolean = true); overload;
- {$ENDIF}
- {$IF DEFINED(USING_VCL_LCL)}
- procedure CopyFromBitmap(bmp: TBitmap);
- procedure CopyToBitmap(bmp: TBitmap);
- {$IFEND}
- function CopyToClipBoard: Boolean;
- class function CanPasteFromClipBoard: Boolean;
- function PasteFromClipBoard: Boolean;
- procedure Crop(const rec: TRect);
- //SetBackgroundColor: Assumes the current image is semi-transparent.
- procedure SetBackgroundColor(bgColor: TColor32);
- procedure Clear(color: TColor32 = 0); overload;
- procedure Clear(const rec: TRect; color: TColor32 = 0); overload;
- procedure FillRect(const rec: TRect; color: TColor32);
- procedure ConvertToBoolMask(reference: TColor32;
- tolerance: integer; colorFunc: TCompareFunction;
- maskBg: TColor32 = clWhite32; maskFg: TColor32 = clBlack32);
- procedure ConvertToAlphaMask(reference: TColor32;
- colorFunc: TCompareFunctionEx);
- procedure FlipVertical;
- procedure FlipHorizontal;
- procedure PreMultiply;
- //SetAlpha: Sets 'alpha' to the alpha byte of every pixel in the image
- procedure SetAlpha(alpha: Byte);
- procedure ReduceOpacity(opacity: Byte); overload;
- procedure ReduceOpacity(opacity: Byte; rec: TRect); overload;
- //SetRGB: Sets the RGB channels leaving the alpha channel unchanged
- procedure SetRGB(rgbColor: TColor32); overload;
- procedure SetRGB(rgbColor: TColor32; rec: TRect); overload;
- //Grayscale: Only changes color channels. The alpha channel is untouched.
- procedure Grayscale(mode: TGrayscaleMode = gsmSaturation;
- linearAmountPercentage: double = 1.0);
- procedure InvertColors;
- procedure InvertAlphas;
- procedure AdjustHue(percent: Integer); //ie +/- 100%
- procedure AdjustLuminance(percent: Integer); //ie +/- 100%
- procedure AdjustSaturation(percent: Integer); //ie +/- 100%
- function GetOpaqueBounds: TRect;
- //CropTransparentPixels: Trims transparent edges until each edge contains
- //at least one opaque or semi-opaque pixel.
- function CropTransparentPixels: TRect;
- procedure Rotate(angleRads: double);
- //RotateRect: Rotates part of an image, but also clips those parts of the
- //rotated image that fall outside rec. The eraseColor parameter indicates
- //the color to fill those uncovered pixels in rec following rotation.
- procedure RotateRect(const rec: TRect;
- angleRads: double; eraseColor: TColor32 = 0);
- procedure Skew(dx,dy: double);
- //ScaleAlpha: Scales the alpha byte of every pixel by the specified amount.
- procedure ScaleAlpha(scale: double);
- class procedure RegisterImageFormatClass(ext: string;
- bm32ExClass: TImageFormatClass; clipPriority: TClipboardPriority);
- class function GetImageFormatClass(const ext: string): TImageFormatClass; overload;
- class function GetImageFormatClass(stream: TStream): TImageFormatClass; overload;
- class function IsRegisteredFormat(const ext: string): Boolean;
- function SaveToFile(filename: string;
- compressionQuality: integer = defaultCompression): Boolean;
- function SaveToStream(stream: TStream; const FmtExt: string;
- compressionQuality: integer = defaultCompression): Boolean;
- function LoadFromFile(const filename: string): Boolean;
- function LoadFromStream(stream: TStream; imgIdx: integer = 0): Boolean;
- function LoadFromResource(const resName: string; resType: PChar): Boolean;
- //properties ...
- property AntiAliased: Boolean read fAntiAliased write fAntiAliased;
- property Width: Integer read fWidth;
- property Height: Integer read fHeight;
- property Bounds: TRect read GetBounds;
- property IsBlank: Boolean read GetIsBlank;
- property IsEmpty: Boolean read GetIsEmpty;
- property IsPreMultiplied: Boolean read fIsPremultiplied;
- property MidPoint: TPointD read GetMidPoint;
- property Pixel[x,y: Integer]: TColor32 read GetPixel write SetPixel;
- property Pixels: TArrayOfColor32 read fPixels;
- property PixelBase: PColor32 read GetPixelBase;
- property PixelRow[row: Integer]: PColor32 read GetPixelRow;
- property ColorCount: Integer read GetColorCount;
- //HasTransparency: Returns true if any pixel's alpha byte < 255.
- property HasTransparency: Boolean read GetHasTransparency;
- //Resampler: is used in scaling and rotation transforms
- property Resampler: integer read fResampler write fResampler;
- property OnChange: TNotifyEvent read fOnChange write fOnChange;
- property OnResize: TNotifyEvent read fOnResize write fOnResize;
- end;
- TImageList32 = class
- private
- {$IFDEF XPLAT_GENERICS}
- fList: TList<TImage32>;
- {$ELSE}
- fList: TList;
- {$ENDIF}
- fIsImageOwner: Boolean;
- function GetImage(index: integer): TImage32;
- procedure SetImage(index: integer; img: TIMage32);
- function GetLast: TImage32;
- public
- constructor Create;
- destructor Destroy; override;
- procedure Clear;
- function Count: integer;
- procedure Add(image: TImage32); overload;
- function Add(width, height: integer): TImage32; overload;
- procedure Insert(index: integer; image: TImage32);
- procedure Move(currentIndex, newIndex: integer);
- procedure Delete(index: integer);
- property Image[index: integer]: TImage32 read GetImage write SetImage; default;
- property IsImageOwner: Boolean read fIsImageOwner write fIsImageOwner;
- property Last: TImage32 read GetLast;
- end;
- THsl = packed record
- hue : byte;
- sat : byte;
- lum : byte;
- alpha: byte;
- end;
- PHsl = ^THsl;
- TArrayofHSL = array of THsl;
- TTriState = (tsUnknown = 0, tsYes = 1, tsChecked = 1, tsNo = 2, tsUnchecked = 2);
- PPointD = ^TPointD;
- TPathD = array of TPointD; //nb: watch for ambiguity with Clipper.pas
- TPathsD = array of TPathD; //nb: watch for ambiguity with Clipper.pas
- TArrayOfPathsD = array of TPathsD;
- TRectD = {$IFDEF RECORD_METHODS} record {$ELSE} object {$ENDIF}
- {$IFNDEF RECORD_METHODS}
- Left, Top, Right, Bottom: Double;
- function TopLeft: TPointD;
- function BottomRight: TPointD;
- {$ENDIF}
- function IsEmpty: Boolean;
- function Width: double;
- function Height: double;
- //Normalize: Returns True if swapping top & bottom or left & right
- function Normalize: Boolean;
- function Contains(const Pt: TPoint): Boolean; overload;
- function Contains(const Pt: TPointD): Boolean; overload;
- function MidPoint: TPointD;
- {$IFDEF RECORD_METHODS}
- case Integer of
- 0: (Left, Top, Right, Bottom: Double);
- 1: (TopLeft, BottomRight: TPointD);
- {$ENDIF}
- end;
- {$IFNDEF PBYTE}
- PByte = type PChar;
- {$ENDIF}
- //BLEND FUNCTIONS ( see TImage32.CopyBlend() )
- //BlendToOpaque: Blends a semi-transparent image onto an opaque background
- function BlendToOpaque(bgColor, fgColor: TColor32): TColor32;
- //BlendToAlpha: Blends two semi-transparent images (slower than BlendToOpaque)
- function BlendToAlpha(bgColor, fgColor: TColor32): TColor32;
- function BlendToAlpha3(bgColor, fgColor: TColor32; blendOpacity: Byte): TColor32;
- procedure BlendToAlphaLine(bgColor, fgColor: PColor32; width: nativeint);
- //BlendMask: Whereever the mask is, preserves the background
- function BlendMask(bgColor, alphaMask: TColor32): TColor32;
- procedure BlendMaskLine(bgColor, alphaMask: PColor32; width: nativeint);
- function BlendAltMask(bgColor, alphaMask: TColor32): TColor32;
- function BlendDifference(color1, color2: TColor32): TColor32;
- function BlendSubtract(bgColor, fgColor: TColor32): TColor32;
- function BlendLighten(bgColor, fgColor: TColor32): TColor32;
- function BlendDarken(bgColor, fgColor: TColor32): TColor32;
- function BlendInvertedMask(bgColor, alphaMask: TColor32): TColor32;
- procedure BlendInvertedMaskLine(bgColor, alphaMask: PColor32; width: nativeint);
- //BlendBlueChannel: typically useful for white color masks
- function BlendBlueChannel(bgColor, blueMask: TColor32): TColor32;
- procedure BlendBlueChannelLine(bgColor, blueMask: PColor32; width: nativeint);
- //COMPARE COLOR FUNCTIONS (ConvertToBoolMask, FloodFill, Vectorize etc.)
- function CompareRGB(master, current: TColor32; tolerance: Integer): Boolean;
- function CompareHue(master, current: TColor32; tolerance: Integer): Boolean;
- function CompareAlpha(master, current: TColor32; tolerance: Integer): Boolean;
- //CompareEx COLOR FUNCTIONS (see ConvertToAlphaMask)
- function CompareRgbEx(master, current: TColor32): Byte;
- function CompareAlphaEx(master, current: TColor32): Byte;
- //MISCELLANEOUS FUNCTIONS ...
- function GetBoolMask(img: TImage32; reference: TColor32;
- compareFunc: TCompareFunction; tolerance: Integer): TArrayOfByte;
- function GetByteMask(img: TImage32; reference: TColor32;
- compareFunc: TCompareFunctionEx): TArrayOfByte;
- function GetColorMask(img: TImage32; reference: TColor32;
- compareFunc: TCompareFunction; tolerance: Integer): TArrayOfColor32;
- {$IFDEF MSWINDOWS}
- //Color32: Converts a Graphics.TColor value into a TColor32 value.
- function Color32(rgbColor: Integer): TColor32; overload; {$IFDEF INLINE} inline; {$ENDIF}
- procedure FixPalette(p: PARGB; count: integer);
- {$ENDIF}
- function Color32(a, r, g, b: Byte): TColor32; overload; {$IFDEF INLINE} inline; {$ENDIF}
- //RGBColor: Converts a TColor32 value into a COLORREF value
- function RGBColor(color: TColor32): Cardinal; {$IFDEF INLINE} inline; {$ENDIF}
- function InvertColor(color: TColor32): TColor32; {$IFDEF INLINE} inline; {$ENDIF}
- //RgbToHsl: See https://en.wikipedia.org/wiki/HSL_and_HSV
- function RgbToHsl(color: TColor32): THsl;
- //HslToRgb: See https://en.wikipedia.org/wiki/HSL_and_HSV
- function HslToRgb(hslColor: THsl): TColor32;
- function AdjustHue(color: TColor32; percent: Integer): TColor32;
- function ArrayOfColor32ToArrayHSL(const clr32Arr: TArrayOfColor32): TArrayofHSL;
- function ArrayOfHSLToArrayColor32(const hslArr: TArrayofHSL): TArrayOfColor32;
- function GetAlpha(color: TColor32): Byte; {$IFDEF INLINE} inline; {$ENDIF}
- function PointD(const X, Y: Double): TPointD; overload; {$IFDEF INLINE} inline; {$ENDIF}
- function PointD(const pt: TPoint): TPointD; overload; {$IFDEF INLINE} inline; {$ENDIF}
- function RectD(left, top, right, bottom: double): TRectD; overload;
- function RectD(const rec: TRect): TRectD; overload;
- function ClampByte(val: Integer): byte; overload; {$IFDEF INLINE} inline; {$ENDIF}
- function ClampByte(val: double): byte; overload; {$IFDEF INLINE} inline; {$ENDIF}
- function ClampRange(val, min, max: Integer): Integer; overload;
- {$IFDEF INLINE} inline; {$ENDIF}
- function ClampRange(val, min, max: double): double; overload;
- {$IFDEF INLINE} inline; {$ENDIF}
- function IncPColor32(pc: Pointer; cnt: Integer): PColor32; {$IFDEF INLINE} inline; {$ENDIF}
- procedure NormalizeAngle(var angle: double; tolerance: double = Pi/360);
- function GrayScale(color: TColor32): TColor32; {$IFDEF INLINE} inline; {$ENDIF}
- //DPIAware: Useful for DPIAware sizing of images and their container controls.
- //It scales values relative to the display's resolution (PixelsPerInch).
- //See https://docs.microsoft.com/en-us/windows/desktop/hidpi/high-DPIAware-desktop-application-development-on-windows
- function DPIAware(val: Integer): Integer; overload; {$IFDEF INLINE} inline; {$ENDIF}
- function DPIAware(val: double): double; overload; {$IFDEF INLINE} inline; {$ENDIF}
- function DPIAware(const pt: TPoint): TPoint; overload;
- function DPIAware(const pt: TPointD): TPointD; overload;
- function DPIAware(const rec: TRect): TRect; overload;
- function DPIAware(const rec: TRectD): TRectD; overload;
- {$IFDEF MSWINDOWS}
- {$IFDEF FPC}
- function AlphaBlend(DC: HDC; p2, p3, p4, p5: Integer;
- DC6: HDC; p7, p8, p9, p10: Integer; p11: Windows.TBlendFunction): BOOL;
- stdcall; external 'msimg32.dll' name 'AlphaBlend';
- {$ENDIF}
- {$ENDIF}
- //CreateResourceStream: handles both numeric and string names and types
- function CreateResourceStream(const resName: string;
- resType: PChar): TResourceStream;
- function GetResampler(id: integer): TResamplerFunction;
- function RegisterResampler(func: TResamplerFunction; const name: string): integer;
- procedure GetResamplerList(stringList: TStringList);
- const
- TwoPi = Pi *2;
- angle0 = 0;
- angle1 = Pi/180;
- angle15 = Pi /12;
- angle30 = angle15 *2;
- angle45 = angle15 *3;
- angle60 = angle15 *4;
- angle75 = angle15 *5;
- angle90 = Pi /2;
- angle105 = Pi - angle75;
- angle120 = Pi - angle60;
- angle135 = Pi - angle45;
- angle150 = Pi - angle30;
- angle165 = Pi - angle15;
- angle180 = Pi;
- angle195 = Pi + angle15;
- angle210 = Pi + angle30;
- angle225 = Pi + angle45;
- angle240 = Pi + angle60;
- angle255 = Pi + angle75;
- angle270 = TwoPi - angle90;
- angle285 = TwoPi - angle75;
- angle300 = TwoPi - angle60;
- angle315 = TwoPi - angle45;
- angle330 = TwoPi - angle30;
- angle345 = TwoPi - angle15;
- angle360 = TwoPi;
- div255: Double = 1 / 255;
- var
- //Resampling function identifiers (initialized in Img32.Resamplers)
- rNearestResampler : integer;
- rBilinearResampler: integer;
- rBicubicResampler : integer;
- rWeightedBilinear : integer;
- DefaultResampler: Integer = 0;
- //Both MulTable and DivTable are used in blend functions
- //MulTable[a,b] = a * b / 255
- MulTable: array [Byte,Byte] of Byte;
- //DivTable[a,b] = a * 255/b (for a <= b)
- DivTable: array [Byte,Byte] of Byte;
- //Sigmoid: weight byte values towards each end
- Sigmoid: array[Byte] of Byte;
- dpiAware1 : integer = 1;
- DpiAwareOne : double = 1.0;
- //AND BECAUSE OLDER DELPHI COMPILERS (OLDER THAN D2006)
- //DON'T SUPPORT RECORD METHODS
- procedure RectWidthHeight(const rec: TRect; out width, height: Integer); overload;
- {$IFDEF INLINE} inline; {$ENDIF}
- procedure RectWidthHeight(const rec: TRectD; out width, height: double); overload;
- {$IFDEF INLINE} inline; {$ENDIF}
- function RectWidth(const rec: TRect): Integer;
- {$IFDEF INLINE} inline; {$ENDIF}
- function RectHeight(const rec: TRect): Integer;
- {$IFDEF INLINE} inline; {$ENDIF}
- function IsEmptyRect(const rec: TRect): Boolean; overload;
- {$IFDEF INLINE} inline; {$ENDIF}
- function IsEmptyRect(const rec: TRectD): Boolean; overload;
- {$IFDEF INLINE} inline; {$ENDIF}
- function SwapRedBlue(color: TColor32): TColor32; overload;
- procedure SwapRedBlue(color: PColor32; count: integer); overload;
- function MulBytes(b1, b2: Byte) : Byte;
- function __Trunc(Value: Double): Integer; {$IFNDEF CPUX86} {$IFDEF INLINE} inline; {$ENDIF} {$ENDIF}
- // NewColor32Array creates a new "array of TColor32". "a" is nil'ed
- // before allocating the array. If "count" is zero or negative "a" will
- // be nil. If "uninitialized" is True, the memory will not be zero'ed.
- procedure NewColor32Array(var a: TArrayOfColor32; count: nativeint;
- uninitialized: boolean = False);
- procedure NewIntegerArray(var a: TArrayOfInteger; count: nativeint;
- uninitialized: boolean = False);
- procedure NewByteArray(var a: TArrayOfByte; count: nativeint;
- uninitialized: boolean = False);
- procedure NewPointDArray(var a: TPathD; count: nativeint;
- uninitialized: boolean = False);
- // SetLengthUninit changes the dyn. array's length but does not initialize
- // the new elements with zeros. It can be used as a replacement for
- // SetLength where the zero-initialitation is not required.
- procedure SetLengthUninit(var a: TArrayOfColor32; count: nativeint); overload;
- procedure SetLengthUninit(var a: TArrayOfInteger; count: nativeint); overload;
- procedure SetLengthUninit(var a: TArrayOfByte; count: nativeint); overload;
- procedure SetLengthUninit(var a: TPathD; count: nativeint); overload;
- implementation
- uses
- Img32.Vector, Img32.Resamplers, Img32.Transform
- {$IF DEFINED(USING_VCL_LCL)}
- , Img32.Fmt.BMP
- {$ENDIF}
- ;
- resourcestring
- rsImageTooLarge = 'Image32 error: the image is too large.';
- rsInvalidImageArrayData = 'Image32 error: the specified pixels array and the size does not match.';
- //------------------------------------------------------------------------------
- //------------------------------------------------------------------------------
- {$IFDEF CPUX86}
- const
- // Use faster Trunc for x86 code in this unit.
- Trunc: function(Value: Double): Integer = __Trunc;
- {$ENDIF CPUX86}
- type
- TImgFmtRec = record
- Fmt: string;
- SortOrder: TClipboardPriority;
- Obj: TImageFormatClass;
- end;
- PImgFmtRec = ^TImgFmtRec;
- TResamplerObj = class
- id: integer;
- name: string;
- func: TResamplerFunction;
- end;
- PDynArrayRec = ^TDynArrayRec;
- {$IFDEF FPC}
- tdynarrayindex = sizeint;
- TDynArrayRec = packed record
- refcount: ptrint;
- high: tdynarrayindex;
- Data: record end;
- end;
- {$ELSE}
- TDynArrayRec = packed record
- {$IFDEF CPU64BITS}
- _Padding: Integer;
- {$ENDIF}
- RefCnt: Integer;
- Length: NativeInt;
- Data: record end;
- end;
- {$ENDIF}
- var
- {$IFDEF XPLAT_GENERICS}
- ImageFormatClassList: TList<PImgFmtRec>; //list of supported file extensions
- ResamplerList: TList<TResamplerObj>; //list of resampler functions
- {$ELSE}
- ImageFormatClassList: TList;
- ResamplerList: TList;
- {$ENDIF}
- //------------------------------------------------------------------------------
- //------------------------------------------------------------------------------
- function NewSimpleDynArray(count: nativeint; elemSize: integer; uninitialized: boolean = False): Pointer;
- var
- p: PDynArrayRec;
- begin
- Result := nil;
- if (count > 0) and (elemSize > 0) then
- begin
- if uninitialized then
- GetMem(Pointer(p), SizeOf(TDynArrayRec) + count * elemSize)
- else
- p := AllocMem(SizeOf(TDynArrayRec) + count * elemSize);
- {$IFDEF FPC}
- p.refcount := 1;
- p.high := count -1;
- {$ELSE}
- p.RefCnt := 1;
- p.Length := count;
- {$ENDIF}
- Result := @p.Data;
- end;
- end;
- //------------------------------------------------------------------------------
- function InternSetSimpleDynArrayLengthUninit(a: Pointer; count: nativeint; elemSize: integer): Pointer;
- var
- p: PDynArrayRec;
- oldCount: nativeint;
- begin
- if a = nil then
- Result := NewSimpleDynArray(count, elemSize)
- else if (count > 0) and (elemSize > 0) then
- begin
- p := PDynArrayRec(PByte(a) - SizeOf(TDynArrayRec));
- {$IFDEF FPC}
- oldCount := p.high + 1;
- if p.refcount = 1 then
- {$ELSE}
- oldCount := p.Length;
- if p.RefCnt = 1 then
- {$ENDIF}
- begin
- // There is only one reference to this array and that is "a",
- // so we can use ReallocMem to change the array's length.
- if oldCount = count then
- begin
- Result := a;
- Exit;
- end;
- ReallocMem(Pointer(p), SizeOf(TDynArrayRec) + count * elemSize);
- end
- else
- begin
- // SetLength makes a copy of the dyn array to get RefCnt=1
- GetMem(Pointer(p), SizeOf(TDynArrayRec) + count * elemSize);
- if oldCount < 0 then oldCount := 0; // data corruption detected
- if oldCount > count then oldCount := count;
- Move(a^, p.Data, oldCount * elemSize);
- TArrayOfByte(a) := nil; // use a non-managed dyn.array type
- end;
- {$IFDEF FPC}
- p.refcount := 1;
- p.high := count -1;
- {$ELSE}
- p.RefCnt := 1;
- p.Length := count;
- {$ENDIF}
- Result := @p.Data;
- end
- else
- begin
- TArrayOfByte(a) := nil; // use a non-managed dyn.array type
- Result := nil;
- end;
- end;
- //------------------------------------------------------------------------------
- function CanReuseDynArray(a: Pointer; count: nativeint): Boolean;
- // returns True if RefCnt=1 and Length=count
- begin
- //Assert(a <> nil);
- a := PByte(a) - SizeOf(TDynArrayRec);
- Result :=
- {$IFDEF FPC}
- (PDynArrayRec(a).refcount = 1) and
- (PDynArrayRec(a).high = count - 1);
- {$ELSE}
- (PDynArrayRec(a).RefCnt = 1) and
- (PDynArrayRec(a).Length = count);
- {$ENDIF}
- end;
- //------------------------------------------------------------------------------
- procedure NewColor32Array(var a: TArrayOfColor32; count: nativeint; uninitialized: boolean);
- begin
- {$IF COMPILERVERSION < 16}
- SetLength(a, count);
- {$ELSE}
- if a <> nil then
- begin
- if uninitialized and CanReuseDynArray(a, count) then Exit;
- a := nil;
- end;
- Pointer(a) := NewSimpleDynArray(count, SizeOf(TColor32), uninitialized);
- {$IFEND}
- end;
- //------------------------------------------------------------------------------
- procedure NewIntegerArray(var a: TArrayOfInteger; count: nativeint; uninitialized: boolean);
- begin
- {$IF COMPILERVERSION < 16}
- SetLength(a, count);
- {$ELSE}
- if a <> nil then
- begin
- if uninitialized and CanReuseDynArray(a, count) then
- Exit;
- a := nil;
- end;
- Pointer(a) := NewSimpleDynArray(count, SizeOf(Integer), uninitialized);
- {$IFEND}
- end;
- //------------------------------------------------------------------------------
- procedure NewByteArray(var a: TArrayOfByte; count: nativeint; uninitialized: boolean);
- begin
- {$IF COMPILERVERSION < 16}
- SetLength(a, count);
- {$ELSE}
- if a <> nil then
- begin
- if uninitialized and CanReuseDynArray(a, count) then
- Exit;
- a := nil;
- end;
- Pointer(a) := NewSimpleDynArray(count, SizeOf(Byte), uninitialized);
- {$IFEND}
- end;
- //------------------------------------------------------------------------------
- procedure NewPointDArray(var a: TPathD; count: nativeint; uninitialized: boolean);
- begin
- {$IF COMPILERVERSION < 16}
- SetLength(a, count);
- {$ELSE}
- if a <> nil then
- begin
- if uninitialized and CanReuseDynArray(a, count) then
- Exit;
- a := nil;
- end;
- Pointer(a) := NewSimpleDynArray(count, SizeOf(TPointD), uninitialized);
- {$IFEND}
- end;
- //------------------------------------------------------------------------------
- procedure SetLengthUninit(var a: TArrayOfColor32; count: nativeint);
- begin
- SetLength(a, count);
- // Pointer(a) := InternSetSimpleDynArrayLengthUninit(Pointer(a), count, SizeOf(TColor32));
- end;
- //------------------------------------------------------------------------------
- procedure SetLengthUninit(var a: TArrayOfInteger; count: nativeint);
- begin
- {$IF COMPILERVERSION < 16}
- SetLength(a, count);
- {$ELSE}
- Pointer(a) := InternSetSimpleDynArrayLengthUninit(Pointer(a), count, SizeOf(Integer));
- {$IFEND}
- end;
- //------------------------------------------------------------------------------
- procedure SetLengthUninit(var a: TArrayOfByte; count: nativeint);
- begin
- {$IF COMPILERVERSION < 16}
- SetLength(a, count);
- {$ELSE}
- Pointer(a) := InternSetSimpleDynArrayLengthUninit(Pointer(a), count, SizeOf(Byte));
- {$IFEND}
- end;
- //------------------------------------------------------------------------------
- procedure SetLengthUninit(var a: TPathD; count: nativeint);
- begin
- {$IF COMPILERVERSION < 16}
- SetLength(a, count);
- {$ELSE}
- Pointer(a) := InternSetSimpleDynArrayLengthUninit(Pointer(a), count, SizeOf(TPointD));
- {$IFEND}
- end;
- //------------------------------------------------------------------------------
- procedure CreateImageFormatList;
- begin
- if Assigned(ImageFormatClassList) then Exit;
- {$IFDEF XPLAT_GENERICS}
- ImageFormatClassList := TList<PImgFmtRec>.Create;
- {$ELSE}
- ImageFormatClassList := TList.Create;
- {$ENDIF}
- end;
- //------------------------------------------------------------------------------
- function FMod(const ANumerator, ADenominator: Double): Double;
- begin
- Result := ANumerator - Trunc(ANumerator / ADenominator) * ADenominator;
- end;
- //------------------------------------------------------------------------------
- procedure NormalizeAngle(var angle: double; tolerance: double = Pi/360);
- var
- aa: double;
- begin
- angle := FMod(angle, angle360);
- if angle < -Angle180 then angle := angle + angle360
- else if angle > angle180 then angle := angle - angle360;
- aa := Abs(angle);
- if aa < tolerance then angle := 0
- else if aa > angle180 - tolerance then angle := angle180
- else if (aa < angle90 - tolerance) or (aa > angle90 + tolerance) then Exit
- else if angle < 0 then angle := -angle90
- else angle := angle90;
- end;
- //------------------------------------------------------------------------------
- {$IFDEF CPUX86}
- { Trunc with FPU code is very slow because the x87 ControlWord has to be changed
- and then there is Delphi's Default8087CW variable that is not thread-safe. }
- //__Trunc: An efficient Trunc() algorithm (ie rounds toward zero)
- function __Trunc(Value: Double): Integer;
- var
- exp: integer;
- i64: UInt64 absolute Value;
- valueBytes: array[0..7] of Byte absolute Value;
- begin
- // https://en.wikipedia.org/wiki/Double-precision_floating-point_format
- // 52 bit fractional value, 11bit ($7FF) exponent, and 1bit sign
- Result := 0;
- if i64 = 0 then Exit;
- exp := Integer(Cardinal(i64 shr 52) and $7FF) - 1023;
- // nb: when exp == 1024 then Value == INF or NAN.
- if exp < 0 then
- Exit
- //else if exp > 52 then // ie only for 64bit int results
- // Result := ((i64 and $1FFFFFFFFFFFFF) shl (exp - 52)) or (1 shl exp)
- //else if exp > 31 then // alternatively, range check for 32bit ints ????
- // raise Exception.Create(rsIntegerOverflow)
- else
- Result := Integer((i64 and $1FFFFFFFFFFFFF) shr (52 - exp)) or (1 shl exp);
- // Check for the sign bit without loading Value into the FPU.
- if valueBytes[7] and $80 <> 0 then Result := -Result;
- end;
- //------------------------------------------------------------------------------
- {$ELSE}
- function __Trunc(Value: Double): Integer;
- begin
- // Uses fast SSE2 instruction
- Result := System.Trunc(Value);
- end;
- //------------------------------------------------------------------------------
- {$ENDIF CPUX86}
- function SwapRedBlue(color: TColor32): TColor32;
- var
- c: array[0..3] of byte absolute color;
- r: array[0..3] of byte absolute Result;
- begin
- result := color;
- r[0] := c[2];
- r[2] := c[0];
- end;
- //------------------------------------------------------------------------------
- procedure SwapRedBlue(color: PColor32; count: integer);
- var
- i: integer;
- begin
- for i := 1 to count do
- begin
- color^ := SwapRedBlue(color^);
- inc(color);
- end;
- end;
- //------------------------------------------------------------------------------
- function MulBytes(b1, b2: Byte) : Byte; {$IFDEF INLINE} inline; {$ENDIF}
- begin
- Result := MulTable[b1, b2];
- end;
- //------------------------------------------------------------------------------
- function ImageFormatClassListSort(item1, item2: Pointer): integer;
- var
- imgFmtRec1: PImgFmtRec absolute item1;
- imgFmtRec2: PImgFmtRec absolute item2;
- begin
- Result := Integer(imgFmtRec1.SortOrder) - Integer(imgFmtRec2.SortOrder);
- end;
- //------------------------------------------------------------------------------
- function ClampByte(val: Integer): byte;
- begin
- if val < 0 then result := 0
- else if val > 255 then result := 255
- else result := val;
- end;
- //------------------------------------------------------------------------------
- function ClampByte(val: double): byte;
- begin
- if val <= 0 then result := 0
- else if val >= 255 then result := 255
- else result := Round(val);
- end;
- //------------------------------------------------------------------------------
- //------------------------------------------------------------------------------
- // Blend functions - used by TImage32.CopyBlend()
- //------------------------------------------------------------------------------
- function BlendToOpaque(bgColor, fgColor: TColor32): TColor32;
- var
- fgA: byte;
- fw,bw: PByteArray;
- begin
- fgA := fgColor shr 24;
- if fgA = 0 then Result := bgColor
- else if fgA = 255 then Result := fgColor
- else
- begin
- //assuming bg.A = 255, use just fg.A for color weighting
- fw := PByteArray(@MulTable[fgA]); //ie weight of foreground
- bw := PByteArray(@MulTable[not fgA]); //ie weight of background
- Result := $FF000000
- or (TColor32(Byte(fw[Byte(fgColor shr 16)] + bw[Byte(bgColor shr 16)])) shl 16)
- or (TColor32(Byte(fw[Byte(fgColor shr 8 )] + bw[Byte(bgColor shr 8)])) shl 8)
- or (TColor32(Byte(fw[Byte(fgColor )] + bw[Byte(bgColor )])) );
- end;
- end;
- //------------------------------------------------------------------------------
- function BlendToAlpha(bgColor, fgColor: TColor32): TColor32;
- var
- fgWeight: byte;
- R, InvR: PByteArray;
- bgA, fgA: byte;
- begin
- //(see https://en.wikipedia.org/wiki/Alpha_compositing)
- fgA := fgColor shr 24;
- bgA := bgColor shr 24;
- if fgA = 0 then Result := bgColor
- else if (bgA = 0) or (fgA = 255) then Result := fgColor
- else
- begin
- //combine alphas ...
- bgA := not MulTable[not fgA, not bgA];
- fgWeight := DivTable[fgA, bgA]; // fgWeight = amount foreground color
- // contibutes to the result color
- R := PByteArray(@MulTable[fgWeight]); // ie weight of foreground
- InvR := PByteArray(@MulTable[not fgWeight]); // ie weight of background
- Result := bgA shl 24
- or (TColor32(R[Byte(fgColor shr 16)] + InvR[Byte(bgColor shr 16)]) shl 16)
- or (TColor32(R[Byte(fgColor shr 8 )] + InvR[Byte(bgColor shr 8)]) shl 8)
- or (TColor32(R[Byte(fgColor) ] + InvR[Byte(bgColor) ]) );
- end;
- end;
- //------------------------------------------------------------------------------
- function BlendToAlpha3(bgColor, fgColor: TColor32; blendOpacity: Byte): TColor32;
- var
- fgWeight: byte;
- R, InvR: PByteArray;
- bgA, fgA: byte;
- begin
- fgA := MulTable[blendOpacity, fgColor shr 24];
- bgA := bgColor shr 24;
- if fgA = 0 then
- Result := bgColor // must do first
- else if (bgA = 0) or (fgA = 255) then
- Result := (fgA shl 24) or (fgColor and $FFFFFF)
- else
- begin
- //combine alphas ...
- bgA := not MulTable[not fgA, not bgA];
- fgWeight := DivTable[fgA, bgA]; // fgWeight = amount foreground color
- // contibutes to the result color
- R := PByteArray(@MulTable[fgWeight]); // ie weight of foreground
- InvR := PByteArray(@MulTable[not fgWeight]); // ie weight of background
- Result := bgA shl 24
- or (TColor32(R[Byte(fgColor shr 16)] + InvR[Byte(bgColor shr 16)]) shl 16)
- or (TColor32(R[Byte(fgColor shr 8 )] + InvR[Byte(bgColor shr 8)]) shl 8)
- or (TColor32(R[Byte(fgColor) ] + InvR[Byte(bgColor) ]) );
- end;
- end;
- //------------------------------------------------------------------------------
- {$RANGECHECKS OFF} // negative array index is used
- {$IFNDEF CPUX64}
- function BlendToAlphaLineX86(bgColorArr, fgColorArr: PColor32Array;
- idx: nativeint): nativeint;
- // Helper function for x86 code, reduces the CPU register pressure in
- // BlendToAlphaLine().
- var
- fgWeight: byte;
- R, InvR: PByteArray;
- fgA, bgA, newBgA: byte;
- fgCol, bgCol: TColor32;
- begin
- fgCol := fgColorArr[idx];
- bgCol := bgColorArr[idx];
- Result := idx; // idx - negative offset into color arrays
- while True do
- begin
- fgA := fgCol shr 24;
- bgA := bgCol shr 24;
- //combine alphas ...
- newBgA := not MulTable[not fgA, not bgA];
- fgWeight := DivTable[fgA, newBgA]; //fgWeight = amount foreground color
- //contibutes to total (result) color
- R := PByteArray(@MulTable[fgWeight]); //ie weight of foreground
- InvR := PByteArray(@MulTable[not fgWeight]); //ie weight of foreground
- while True do
- begin
- bgColorArr[Result] := TColor32(newBgA) shl 24
- or (TColor32(R[Byte(fgCol shr 16)] + InvR[Byte(bgCol shr 16)]) shl 16)
- or (TColor32(R[Byte(fgCol shr 8 )] + InvR[Byte(bgCol shr 8)]) shl 8)
- or (TColor32(R[Byte(fgCol) ] + InvR[Byte(bgCol) ]) );
- inc(Result);
- if Result = 0 then exit;
- fgCol := fgColorArr[Result];
- bgCol := bgColorArr[Result];
- // if both alpha channels are the same in the new pixels, we
- // can use the already calculated R/InvR tables.
- if (fgCol shr 24 <> fgA) or (bgCol shr 24 <> bgA) then break;
- end;
- // return if we have alpha channel values for which we have special code
- if (fgCol and $FF000000 = 0) or (fgCol and $FF000000 = $FF000000) or (bgCol and $FF000000 = 0) then exit;
- end;
- end;
- //------------------------------------------------------------------------------
- {$ENDIF ~CPUX64}
- procedure BlendToAlphaLine(bgColor, fgColor: PColor32; width: nativeint);
- label
- LabelBgAlphaIsZero;
- var
- bgColorArr, fgColorArr: PColor32Array;
- bgCol, fgCol: TColor32;
- {$IFDEF CPUX64}
- fgWeight, fgA, bgA: byte;
- R, InvR: PByteArray;
- {$ENDIF CPUX64}
- begin
- //(see https://en.wikipedia.org/wiki/Alpha_compositing)
- // Use the negative offset trick to only increment the array "width"
- // until it reaches zero. And by offsetting the arrays by "width",
- // the negative "width" values also becomes the index into these arrays.
- inc(bgColor, width);
- inc(fgColor, width);
- width := -width;
- bgColorArr := PColor32Array(bgColor);
- fgColorArr := PColor32Array(fgColor);
- while width < 0 do
- begin
- bgCol := bgColorArr[width];
- fgCol := fgColorArr[width];
- // bgColor.A is zero => change bgColor to fgColor
- while bgCol shr 24 = 0 do
- begin
- LabelBgAlphaIsZero:
- bgColorArr[width] := fgCol;
- inc(width);
- if width = 0 then exit;
- fgCol := fgColorArr[width];
- bgCol := bgColorArr[width];
- end;
- // fgColor.A is zero => don't change bgColor
- while fgCol shr 24 = 0 do
- begin
- // bgColorArr[w] := bgColorArr[w];
- inc(width);
- if width = 0 then exit;
- fgCol := fgColorArr[width];
- bgCol := bgColorArr[width];
- if bgCol shr 24 = 0 then goto LabelBgAlphaIsZero;
- end;
- // fgColor.A is 255 => change bgColor to fgColor
- while fgCol shr 24 = 255 do
- begin
- bgColorArr[width] := fgCol;
- inc(width);
- if width = 0 then exit;
- fgCol := fgColorArr[width];
- bgCol := bgColorArr[width];
- if bgCol shr 24 = 0 then goto LabelBgAlphaIsZero;
- end;
- {$IFDEF CPUX64}
- // x64 has more CPU registers than x86 and calling BlendToAlphaLineX86
- // is slower, so we inline it.
- //combine alphas ...
- fgA := fgCol shr 24;
- bgA := bgCol shr 24;
- bgA := not MulTable[not fgA, not bgA];
- fgWeight := DivTable[fgA, bgA]; //fgWeight = amount foreground color
- //contibutes to total (result) color
- R := PByteArray(@MulTable[fgWeight]); //ie weight of foreground
- InvR := PByteArray(@MulTable[not fgWeight]); //ie weight of foreground
- bgColorArr[width] := TColor32(bgA) shl 24
- or (TColor32(R[Byte(fgCol shr 16)] + InvR[Byte(bgCol shr 16)]) shl 16)
- or (TColor32(R[Byte(fgCol shr 8 )] + InvR[Byte(bgCol shr 8)]) shl 8)
- or (TColor32(R[Byte(fgCol) ] + InvR[Byte(bgCol) ]) );
- inc(width);
- {$ELSE}
- // x86 has not enough CPU registers and the loops above will suffer if we
- // inline the code. So we let the compiler use a "new set" of CPU registers
- // by calling a function.
- width := BlendToAlphaLineX86(bgColorArr, fgColorArr, width);
- {$ENDIF CPUX64}
- end;
- end;
- //------------------------------------------------------------------------------
- {
- // reference implementation
- procedure BlendToAlphaLine(bgColor, fgColor: PColor32; width: nativeint);
- var
- fgWeight: byte;
- R, InvR: PByteArray;
- bgA, fgA: Byte;
- bgColorArr, fgColorArr: PColor32Array;
- bgCol, fgCol: TColor32;
- begin
- //(see https://en.wikipedia.org/wiki/Alpha_compositing)
- // Use the negative offset trick to only increment the array "width"
- // until it reaches zero. And by offsetting the arrays by "width",
- // the negative "width" values also becomes the index into these arrays.
- inc(bgColor, width);
- inc(fgColor, width);
- width := -width;
- bgColorArr := PColor32Array(bgColor);
- fgColorArr := PColor32Array(fgColor);
- while width < 0 do
- begin
- bgCol := bgColorArr[width];
- fgCol := fgColorArr[width];
- bgA := bgCol shr 24;
- if bgA = 0 then bgColorArr[width] := fgCol
- else
- begin
- fgA := fgCol shr 24;
- if fgA > 0 then
- begin
- if fgA = 255 then bgColorArr[width] := fgCol
- else if fgA > 0 then
- begin
- //combine alphas ...
- bgA := not MulTable[not fgA, not bgA];
- fgWeight := DivTable[fgA, bgA]; //fgWeight = amount foreground color
- //contibutes to total (result) color
- R := PByteArray(@MulTable[fgWeight]); //ie weight of foreground
- InvR := PByteArray(@MulTable[not fgWeight]); //ie weight of foreground
- bgColorArr[width] := TColor32(bgA) shl 24
- or (TColor32(R[Byte(fgCol shr 16)] + InvR[Byte(bgCol shr 16)]) shl 16)
- or (TColor32(R[Byte(fgCol shr 8 )] + InvR[Byte(bgCol shr 8)]) shl 8)
- or (TColor32(R[Byte(fgCol) ] + InvR[Byte(bgCol) ]) );
- end;
- end;
- end;
- inc(width);
- end;
- end;}
- {$IFDEF RANGECHECKS_ENABLED}
- {$RANGECHECKS ON}
- {$ENDIF}
- //------------------------------------------------------------------------------
- function BlendMask(bgColor, alphaMask: TColor32): TColor32;
- var
- a: byte;
- begin
- a := MulTable[bgColor shr 24, alphaMask shr 24];
- if a <> 0 then Result := (TColor32(a) shl 24) or (bgColor and $00FFFFFF)
- else Result := 0;
- end;
- //------------------------------------------------------------------------------
- {$RANGECHECKS OFF} // negative array index is used
- procedure BlendMaskLine(bgColor, alphaMask: PColor32; width: nativeint);
- label
- SkipNone32;
- var
- a: byte;
- begin
- // Use the negative offset trick to only increment the array "width"
- // until it reaches zero. And by offsetting the arrays by "width",
- // the negative "width" values also becomes the index into these arrays.
- inc(bgColor, width);
- inc(alphaMask, width);
- width := -width;
- // Handle special cases Alpha=0 or 255 as those are the most
- // common values.
- while width < 0 do
- begin
- // MulTable[0, fgA] -> 0, if bgColor is already 0 => skip
- while PARGBArray(bgColor)[width].Color = 0 do
- begin
- SkipNone32:
- inc(width);
- if width = 0 then exit;
- end;
- a := PARGBArray(bgColor)[width].A;
- // MulTable[0, fgA] -> 0 => replace color with 0
- while a = 0 do
- begin
- PColor32Array(bgColor)[width] := 0;
- inc(width);
- if width = 0 then exit;
- if PARGBArray(bgColor)[width].Color = 0 then
- goto SkipNone32;
- a := PARGBArray(bgColor)[width].A;
- end;
- // MulTable[255, fgA] -> fgA => replace alpha with fgA
- while a = 255 do
- begin
- PARGBArray(bgColor)[width].A := PARGBArray(alphaMask)[width].A;
- inc(width);
- if width = 0 then exit;
- a := PARGBArray(bgColor)[width].A;
- end;
- a := PARGBArray(alphaMask)[width].A;
- // MulTable[bgA, 0] -> 0 => replace color with 0
- while a = 0 do
- begin
- PColor32Array(bgColor)[width] := 0;
- inc(width);
- if width = 0 then exit;
- a := PARGBArray(alphaMask)[width].A;
- end;
- // MulTable[bgA, 255] -> bgA => nothing to do
- while a = 255 do
- begin
- inc(width);
- if width = 0 then exit;
- a := PARGBArray(alphaMask)[width].A;
- end;
- a := MulTable[PARGBArray(bgColor)[width].A, a];
- if a <> 0 then PARGBArray(bgColor)[width].A := a
- else PColor32Array(bgColor)[width] := 0;
- inc(width);
- end;
- end;
- //------------------------------------------------------------------------------
- {
- // reference implementation
- procedure BlendMaskLine(bgColor, alphaMask: PColor32; width: nativeint);
- var
- a: byte;
- begin
- // Use the negative offset trick to only increment the array "width"
- // until it reaches zero. And by offsetting the arrays by "width",
- // the negative "width" values also becomes the index into these arrays.
- inc(bgColor, width);
- inc(alphaMask, width);
- width := -width;
- while width < 0 do
- begin
- a := MulTable[PARGBArray(bgColor)[width].A,
- PARGBArray(alphaMask)[width].A];
- if a = 0 then PColor32Array(bgColor)[width] := 0
- else PARGBArray(bgColor)[width].A := a;
- inc(width);
- end;
- end;}
- {$IFDEF RANGECHECKS_ENABLED}
- {$RANGECHECKS ON}
- {$ENDIF}
- //------------------------------------------------------------------------------
- function BlendAltMask(bgColor, alphaMask: TColor32): TColor32;
- var
- a: byte;
- begin
- a := MulTable[bgColor shr 24, (alphaMask shr 24) xor 255];
- if a <> 0 then Result := (TColor32(a) shl 24) or (bgColor and $00FFFFFF)
- else Result := 0;
- end;
- //------------------------------------------------------------------------------
- function BlendDifference(color1, color2: TColor32): TColor32;
- var
- fgA, bgA: byte;
- begin
- fgA := color2 shr 24;
- bgA := color1 shr 24;
- if fgA = 0 then Result := color1
- else if bgA = 0 then Result := color2
- else
- begin
- Result := TColor32(MulTable[(fgA xor 255), (bgA xor 255)] xor 255) shl 24
- or (TColor32(Abs(Byte(color2 shr 16) - Byte(color1 shr 16))) shl 16)
- or (TColor32(Abs(Byte(color2 shr 8) - Byte(color1 shr 8))) shl 8)
- or (TColor32(Abs(Byte(color2 ) - Byte(color1 ))) );
- end;
- end;
- //------------------------------------------------------------------------------
- function BlendSubtract(bgColor, fgColor: TColor32): TColor32;
- var
- fgA, bgA: byte;
- begin
- fgA := fgColor shr 24;
- bgA := bgColor shr 24;
- if fgA = 0 then Result := bgColor
- else if bgA = 0 then Result := fgColor
- else
- begin
- Result := TColor32(MulTable[(fgA xor 255), (bgA xor 255)] xor 255) shl 24
- or (TColor32(ClampByte(Byte(fgColor shr 16) - Byte(bgColor shr 16))) shl 16)
- or (TColor32(ClampByte(Byte(fgColor shr 8 ) - Byte(bgColor shr 8))) shl 8)
- or (TColor32(ClampByte(Byte(fgColor ) - Byte(bgColor ))) );
- end;
- end;
- //------------------------------------------------------------------------------
- function BlendLighten(bgColor, fgColor: TColor32): TColor32;
- var
- fgA, bgA: byte;
- begin
- fgA := fgColor shr 24;
- bgA := bgColor shr 24;
- if fgA = 0 then Result := bgColor
- else if bgA = 0 then Result := fgColor
- else
- begin
- Result := TColor32(MulTable[(fgA xor 255), (bgA xor 255)] xor 255) shl 24
- or (TColor32(Max(Byte(fgColor shr 16), Byte(bgColor shr 16))) shl 16)
- or (TColor32(Max(Byte(fgColor shr 8 ), Byte(bgColor shr 8))) shl 8)
- or (TColor32(Max(Byte(fgColor ), Byte(bgColor ))) );
- end;
- end;
- //------------------------------------------------------------------------------
- function BlendDarken(bgColor, fgColor: TColor32): TColor32;
- var
- fgA, bgA: byte;
- begin
- fgA := fgColor shr 24;
- bgA := bgColor shr 24;
- if fgA = 0 then Result := bgColor
- else if bgA = 0 then Result := fgColor
- else
- begin
- Result := TColor32(MulTable[(fgA xor 255), (bgA xor 255)] xor 255) shl 24
- or (TColor32(Min(Byte(fgColor shr 16), Byte(bgColor shr 16))) shl 16)
- or (TColor32(Min(Byte(fgColor shr 8 ), Byte(bgColor shr 8))) shl 8)
- or (TColor32(Min(Byte(fgColor ), Byte(bgColor ))) );
- end;
- end;
- //------------------------------------------------------------------------------
- function BlendBlueChannel(bgColor, blueMask: TColor32): TColor32;
- begin
- Result := (bgColor and $00FFFFFF) or
- (TColor32(MulTable[bgColor shr 24, Byte(blueMask)]) shl 24);
- end;
- //------------------------------------------------------------------------------
- function BlendInvertedMask(bgColor, alphaMask: TColor32): TColor32;
- var
- a: byte;
- begin
- a := MulTable[bgColor shr 24, (alphaMask shr 24) xor 255];
- if a < 2 then Result := 0
- else Result := (bgColor and $00FFFFFF) or (TColor32(a) shl 24);
- end;
- //------------------------------------------------------------------------------
- {$RANGECHECKS OFF} // negative array index is used
- procedure BlendBlueChannelLine(bgColor, blueMask: PColor32; width: nativeint);
- begin
- inc(bgColor, width);
- inc(blueMask, width);
- width := -width;
- while width < 0 do
- begin
- PARGBArray(bgColor)[width].A :=
- MulTable[PARGBArray(bgColor)[width].A,
- PARGBArray(blueMask)[width].B];
- inc(width);
- end;
- end;
- //------------------------------------------------------------------------------
- procedure BlendInvertedMaskLine(bgColor, alphaMask: PColor32; width: nativeint);
- var
- a: byte;
- begin
- // Use the negative offset trick to only increment the array "width"
- // until it reaches zero. And by offsetting the arrays by "width",
- // the negative "width" values also becomes the index into these arrays.
- inc(bgColor, width);
- inc(alphaMask, width);
- width := -width;
- while width < 0 do
- begin
- a := MulTable[PARGBArray(bgColor)[width].A,
- PARGBArray(alphaMask)[width].A xor 255];
- if a < 2 then PColor32Array(bgColor)[width] := 0
- else PARGBArray(bgColor)[width].A := a;
- inc(width);
- end;
- end;
- {$IFDEF RANGECHECKS_ENABLED}
- {$RANGECHECKS ON}
- {$ENDIF}
- //------------------------------------------------------------------------------
- // Compare functions (see ConvertToBoolMask, FloodFill & Vectorize)
- //------------------------------------------------------------------------------
- function CompareRGB(master, current: TColor32; tolerance: Integer): Boolean;
- var
- mast: TARGB absolute master;
- curr: TARGB absolute current;
- begin
- if curr.A < $80 then
- Result := false
- else if (master and $FFFFFF) = (current and $FFFFFF) then
- Result := true
- else if tolerance = 0 then
- Result := false
- else result :=
- (Abs(curr.R - mast.R) <= tolerance) and
- (Abs(curr.G - mast.G) <= tolerance) and
- (Abs(curr.B - mast.B) <= tolerance);
- end;
- //------------------------------------------------------------------------------
- function CompareAlpha(master, current: TColor32; tolerance: Integer): Boolean;
- var
- mast: TARGB absolute master;
- curr: TARGB absolute current;
- begin
- if mast.A = curr.A then Result := true
- else if tolerance = 0 then Result := false
- else result := Abs(curr.A - mast.A) <= tolerance;
- end;
- //------------------------------------------------------------------------------
- function CompareHue(master, current: TColor32; tolerance: Integer): Boolean;
- var
- curr, mast: THsl;
- val: Integer;
- begin
- if TARGB(current).A < $80 then
- begin
- Result := false;
- Exit;
- end;
- curr := RgbToHsl(current);
- mast := RgbToHsl(master);
- if curr.hue > mast.hue then
- begin
- val := curr.hue - mast.hue;
- if val > 127 then val := mast.hue - curr.hue + 255;
- end else
- begin
- val := mast.hue - curr.hue;
- if val > 127 then val := curr.hue - mast.hue + 255;
- end;
- result := val <= tolerance;
- end;
- //------------------------------------------------------------------------------
- // CompareEx functions (see ConvertToAlphaMask)
- //------------------------------------------------------------------------------
- function CompareRgbEx(master, current: TColor32): Byte;
- var
- mast: TARGB absolute master;
- curr: TARGB absolute current;
- res: Cardinal;
- begin
- res := Sqr(mast.R - curr.R) + Sqr(mast.G - curr.G) + Sqr(mast.B - curr.B);
- if res >= 65025 then result := 255
- else result := Round(Sqrt(res));
- end;
- //------------------------------------------------------------------------------
- function CompareAlphaEx(master, current: TColor32): Byte;
- var
- mast: TARGB absolute master;
- curr: TARGB absolute current;
- begin
- Result := abs(mast.A - curr.A);
- end;
- //------------------------------------------------------------------------------
- // Miscellaneous functions ...
- //------------------------------------------------------------------------------
- function IsAlphaChar(c: Char): Boolean;
- begin
- Result := ((c >= 'A') and (c <= 'Z')) or ((c >= 'a') and (c <= 'z'));
- end;
- //------------------------------------------------------------------------------
- procedure RectWidthHeight(const rec: TRect; out width, height: Integer);
- begin
- width := rec.Right - rec.Left;
- height := rec.Bottom - rec.Top;
- end;
- //------------------------------------------------------------------------------
- procedure RectWidthHeight(const rec: TRectD; out width, height: double);
- begin
- width := rec.Right - rec.Left;
- height := rec.Bottom - rec.Top;
- end;
- //------------------------------------------------------------------------------
- function RectWidth(const rec: TRect): Integer;
- begin
- Result := rec.Right - rec.Left;
- end;
- //------------------------------------------------------------------------------
- function RectHeight(const rec: TRect): Integer;
- begin
- Result := rec.Bottom - rec.Top;
- end;
- //------------------------------------------------------------------------------
- function IsEmptyRect(const rec: TRect): Boolean;
- begin
- Result := (rec.Right <= rec.Left) or (rec.Bottom <= rec.Top);
- end;
- //------------------------------------------------------------------------------
- function IsEmptyRect(const rec: TRectD): Boolean;
- begin
- Result := (rec.Right <= rec.Left) or (rec.Bottom <= rec.Top);
- end;
- //------------------------------------------------------------------------------
- function InvertColor(color: TColor32): TColor32;
- begin
- Result := color xor $00FFFFFF;
- end;
- //------------------------------------------------------------------------------
- function GetAlpha(color: TColor32): Byte;
- begin
- Result := Byte(color shr 24);
- end;
- //------------------------------------------------------------------------------
- function RGBColor(color: TColor32): Cardinal;
- var
- c : TARGB absolute color;
- res: TARGB absolute Result;
- begin
- res.R := c.B; res.G := c.G; res.B := c.R; res.A := 0;
- end;
- //------------------------------------------------------------------------------
- function Color32(a, r, g, b: Byte): TColor32;
- var
- res: TARGB absolute Result;
- begin
- res.A := a; res.R := r; res.G := g; res.B := b;
- end;
- //------------------------------------------------------------------------------
- {$IFDEF MSWINDOWS}
- function Color32(rgbColor: Integer): TColor32;
- var
- res: TARGB absolute Result;
- begin
- if rgbColor < 0 then
- result := GetSysColor(rgbColor and $FFFFFF) else
- result := rgbColor;
- res.A := res.B; res.B := res.R; res.R := res.A; //byte swap
- res.A := 255;
- end;
- //------------------------------------------------------------------------------
- procedure FixPalette(p: PARGB; count: integer);
- var
- i: integer;
- begin
- for i := 1 to count do
- begin
- p.Color := SwapRedBlue(p.Color);
- p.A := 255;
- inc(p);
- end;
- end;
- //------------------------------------------------------------------------------
- function Get32bitBitmapInfoHeader(width, height: Integer): TBitmapInfoHeader;
- begin
- FillChar(Result, sizeof(Result), #0);
- Result.biSize := sizeof(TBitmapInfoHeader);
- Result.biWidth := width;
- Result.biHeight := height;
- Result.biPlanes := 1;
- Result.biBitCount := 32;
- Result.biSizeImage := width * Abs(height) * SizeOf(TColor32);
- Result.biCompression := BI_RGB;
- end;
- //------------------------------------------------------------------------------
- {$ENDIF}
- function DPIAware(val: Integer): Integer;
- begin
- result := Round(val * DpiAwareOne);
- end;
- //------------------------------------------------------------------------------
- function DPIAware(val: double): double;
- begin
- result := val * DpiAwareOne;
- end;
- //------------------------------------------------------------------------------
- function DPIAware(const pt: TPoint): TPoint;
- begin
- result.X := Round(pt.X * DpiAwareOne);
- result.Y := Round(pt.Y * DpiAwareOne);
- end;
- //------------------------------------------------------------------------------
- function DPIAware(const pt: TPointD): TPointD;
- begin
- result.X := pt.X * DpiAwareOne;
- result.Y := pt.Y * DpiAwareOne;
- end;
- //------------------------------------------------------------------------------
- function DPIAware(const rec: TRect): TRect;
- begin
- result.Left := Round(rec.Left * DpiAwareOne);
- result.Top := Round(rec.Top * DpiAwareOne);
- result.Right := Round(rec.Right * DpiAwareOne);
- result.Bottom := Round(rec.Bottom * DpiAwareOne);
- end;
- //------------------------------------------------------------------------------
- function DPIAware(const rec: TRectD): TRectD;
- begin
- result.Left := rec.Left * DpiAwareOne;
- result.Top := rec.Top * DpiAwareOne;
- result.Right := rec.Right * DpiAwareOne;
- result.Bottom := rec.Bottom * DpiAwareOne;
- end;
- //------------------------------------------------------------------------------
- function GrayScale(color: TColor32): TColor32;
- var
- c: TARGB absolute color;
- r: TARGB absolute result;
- g: Byte;
- begin
- //https://www.w3.org/TR/AERT/#color-contrast
- g := ClampByte(0.299 * c.R + 0.587 * c.G + 0.114 * c.B);
- r.A := c.A;
- r.R := g; r.G := g; r.B := g;
- end;
- //------------------------------------------------------------------------------
- function ClampRange(val, min, max: Integer): Integer;
- begin
- if val < min then result := min
- else if val > max then result := max
- else result := val;
- end;
- //------------------------------------------------------------------------------
- function ClampRange(val, min, max: double): double;
- begin
- if val < min then result := min
- else if val > max then result := max
- else result := val;
- end;
- //------------------------------------------------------------------------------
- procedure ScaleRect(var rec: TRect; x,y: double);
- begin
- rec.Right := rec.Left + Round((rec.Right - rec.Left) * x);
- rec.Bottom := rec.Top + Round((rec.Bottom - rec.Top) * y);
- end;
- //------------------------------------------------------------------------------
- function IncPColor32(pc: Pointer; cnt: Integer): PColor32;
- begin
- result := PColor32(PByte(pc) + cnt * SizeOf(TColor32));
- end;
- //------------------------------------------------------------------------------
- function PointD(const X, Y: Double): TPointD;
- begin
- Result.X := X;
- Result.Y := Y;
- end;
- //------------------------------------------------------------------------------
- function PointD(const pt: TPoint): TPointD;
- begin
- Result.X := pt.X;
- Result.Y := pt.Y;
- end;
- //------------------------------------------------------------------------------
- function GetBoolMask(img: TImage32; reference: TColor32;
- compareFunc: TCompareFunction; tolerance: Integer): TArrayOfByte;
- var
- i: integer;
- pa: PByte;
- pc: PColor32;
- begin
- result := nil;
- if not assigned(img) or img.IsEmpty then Exit;
- if not Assigned(compareFunc) then compareFunc := CompareRGB;
- NewByteArray(Result, img.Width * img.Height, True);
- pa := @Result[0];
- pc := img.PixelBase;
- for i := 0 to img.Width * img.Height -1 do
- begin
- if compareFunc(reference, pc^, tolerance) then
- {$IFDEF PBYTE}
- pa^ := 1 else
- pa^ := 0;
- {$ELSE}
- pa^ := #1 else
- pa^ := #0;
- {$ENDIF}
- inc(pc); inc(pa);
- end;
- end;
- //------------------------------------------------------------------------------
- function GetColorMask(img: TImage32; reference: TColor32;
- compareFunc: TCompareFunction; tolerance: Integer): TArrayOfColor32;
- var
- i: integer;
- pDstPxl: PColor32;
- pSrcPxl: PColor32;
- begin
- result := nil;
- if not assigned(img) or img.IsEmpty then Exit;
- if not Assigned(compareFunc) then compareFunc := CompareRGB;
- NewColor32Array(Result, img.Width * img.Height, True);
- pDstPxl := @Result[0];
- pSrcPxl := img.PixelBase;
- for i := 0 to img.Width * img.Height -1 do
- begin
- if compareFunc(reference, pSrcPxl^, tolerance) then
- pDstPxl^ := clWhite32 else
- pDstPxl^ := clBlack32;
- inc(pSrcPxl); inc(pDstPxl);
- end;
- end;
- //------------------------------------------------------------------------------
- function GetAlphaEx(master, current: TColor32): Byte;
- {$IFDEF INLINE} inline; {$ENDIF}
- var
- curr: TARGB absolute current;
- begin
- result := curr.A; //nb: 'master' is ignored
- end;
- //------------------------------------------------------------------------------
- function GetByteMask(img: TImage32; reference: TColor32;
- compareFunc: TCompareFunctionEx): TArrayOfByte;
- var
- i: integer;
- pa: PByte;
- pc: PColor32;
- begin
- result := nil;
- if not assigned(img) or img.IsEmpty then Exit;
- if not Assigned(compareFunc) then compareFunc := GetAlphaEx;
- NewByteArray(Result, img.Width * img.Height, True);
- pa := @Result[0];
- pc := img.PixelBase;
- for i := 0 to img.Width * img.Height -1 do
- begin
- {$IFDEF PBYTE}
- pa^ := compareFunc(reference, pc^);
- {$ELSE}
- pa^ := Char(compareFunc(reference, pc^));
- {$ENDIF}
- inc(pc); inc(pa);
- end;
- end;
- //------------------------------------------------------------------------------
- function RgbToHsl(color: TColor32): THsl;
- var
- rgba: TARGB absolute color;
- hsl: THsl absolute result;
- r,g,b: byte;
- maxRGB, minRGB, mAdd, mSub: Integer;
- begin
- //https://en.wikipedia.org/wiki/HSL_and_HSV and
- //http://en.wikipedia.org/wiki/HSL_color_space
- {$IF DEFINED(ANDROID)}
- color := SwapRedBlue(color);
- {$IFEND}
- r := rgba.R; g := rgba.G; b := rgba.B;
- maxRGB := Max(r, Max(g, b));
- minRGB := Min(r, Min(g, b));
- mAdd := maxRGB + minRGB;
- hsl.lum := mAdd shr 1;
- hsl.alpha := rgba.A;
- if maxRGB = minRGB then
- begin
- hsl.hue := 0; //hsl.hue is undefined when gray
- hsl.sat := 0;
- Exit;
- end;
- mSub := maxRGB - minRGB;
- if mAdd <= 255 then
- hsl.sat := DivTable[mSub, mAdd] else
- hsl.sat := DivTable[mSub, 511 - mAdd];
- mSub := mSub * 6;
- if r = maxRGB then
- begin
- if g >= b then
- hsl.hue := (g - b) * 255 div mSub else
- hsl.hue := 255 - ((b - g) * 255 div mSub);
- end
- else if G = maxRGB then
- begin
- if b > r then
- hsl.hue := 85 + (b - r) * 255 div mSub else
- hsl.hue := 85 - (r - b) * 255 div mSub;
- end else
- begin
- if r > g then
- hsl.hue := 170 + (r - g) * 255 div mSub else
- hsl.hue := 170 - (g - r) * 255 div mSub;
- end;
- end;
- //------------------------------------------------------------------------------
- function HslToRgb(hslColor: THsl): TColor32;
- var
- rgba: TARGB absolute result;
- hsl: THsl absolute hslColor;
- c, x, m, a: Integer;
- begin
- //formula from https://www.rapidtables.com/convert/color/hsl-to-rgb.html
- c := ((255 - abs(2 * hsl.lum - 255)) * hsl.sat) shr 8;
- a := 252 - (hsl.hue mod 85) * 6;
- x := (c * (255 - abs(a))) shr 8;
- m := hsl.lum - c shr 1{div 2}; // Delphi's 64bit compiler can't optimize this
- rgba.A := hsl.alpha;
- case (hsl.hue * 6) shr 8 of
- 0: begin rgba.R := c + m; rgba.G := x + m; rgba.B := 0 + m; end;
- 1: begin rgba.R := x + m; rgba.G := c + m; rgba.B := 0 + m; end;
- 2: begin rgba.R := 0 + m; rgba.G := c + m; rgba.B := x + m; end;
- 3: begin rgba.R := 0 + m; rgba.G := x + m; rgba.B := c + m; end;
- 4: begin rgba.R := x + m; rgba.G := 0 + m; rgba.B := c + m; end;
- 5: begin rgba.R := c + m; rgba.G := 0 + m; rgba.B := x + m; end;
- end;
- {$IF DEFINED(ANDROID)}
- Result := SwapRedBlue(Result);
- {$IFEND}
- end;
- //------------------------------------------------------------------------------
- function AdjustHue(color: TColor32; percent: Integer): TColor32;
- var
- hsl: THsl;
- begin
- percent := percent mod 100;
- if percent < 0 then inc(percent, 100);
- hsl := RgbToHsl(color);
- hsl.hue := (hsl.hue + Round(percent*255/100)) mod 256;
- result := HslToRgb(hsl);
- end;
- //------------------------------------------------------------------------------
- function ArrayOfColor32ToArrayHSL(const clr32Arr: TArrayOfColor32): TArrayofHSL;
- var
- i, len: Integer;
- begin
- len := length(clr32Arr);
- setLength(result, len);
- for i := 0 to len -1 do
- result[i] := RgbToHsl(clr32Arr[i]);
- end;
- //------------------------------------------------------------------------------
- function ArrayOfHSLToArrayColor32(const hslArr: TArrayofHSL): TArrayOfColor32;
- var
- i, len: Integer;
- begin
- len := length(hslArr);
- NewColor32Array(result, len, True);
- for i := 0 to len -1 do
- result[i] := HslToRgb(hslArr[i]);
- end;
- //------------------------------------------------------------------------------
- function NameToId(Name: PChar): Longint;
- begin
- if Name < Pointer(30) then
- begin
- Result := Longint(Name)
- end else
- begin
- if Name^ = '#' then inc(Name);
- Result := StrToIntDef(Name, 0);
- if Result > 65535 then Result := 0;
- end;
- end;
- //------------------------------------------------------------------------------
- function CreateResourceStream(const resName: string;
- resType: PChar): TResourceStream;
- var
- nameId, typeId: Cardinal;
- begin
- Result := nil;
- typeId := NameToId(resType);
- if (typeId > 0) then resType := PChar(typeId)
- else if (resType = 'BMP') then resType := RT_BITMAP;
- nameId := NameToId(PChar(resName));
- if nameId > 0 then
- begin
- if FindResource(hInstance, PChar(nameId), resType) <> 0 then
- Result := TResourceStream.CreateFromID(hInstance, nameId, resType);
- end else
- begin
- if FindResource(hInstance, PChar(resName), resType) <> 0 then
- Result := TResourceStream.Create(hInstance, PChar(resName), resType);
- end;
- end;
- //------------------------------------------------------------------------------
- // TRectD methods (and helpers)
- //------------------------------------------------------------------------------
- function TRectD.IsEmpty: Boolean;
- begin
- result := (right <= left) or (bottom <= top);
- end;
- //------------------------------------------------------------------------------
- function TRectD.Width: double;
- begin
- result := Max(0, right - left);
- end;
- //------------------------------------------------------------------------------
- function TRectD.Height: double;
- begin
- result := Max(0, bottom - top);
- end;
- //------------------------------------------------------------------------------
- function TRectD.MidPoint: TPointD;
- begin
- Result.X := (Right + Left)/2;
- Result.Y := (Bottom + Top)/2;
- end;
- //------------------------------------------------------------------------------
- {$IFNDEF RECORD_METHODS}
- function TRectD.TopLeft: TPointD;
- begin
- Result.X := Left;
- Result.Y := Top;
- end;
- //------------------------------------------------------------------------------
- function TRectD.BottomRight: TPointD;
- begin
- Result.X := Right;
- Result.Y := Bottom;
- end;
- //------------------------------------------------------------------------------
- {$ENDIF}
- function TRectD.Normalize: Boolean;
- var
- d: double;
- begin
- Result := false;
- if Left > Right then
- begin
- d := Left;
- Left := Right;
- Right := d;
- Result := True;
- end;
- if Top > Bottom then
- begin
- d := Top;
- Top := Bottom;
- Bottom := d;
- Result := True;
- end;
- end;
- //------------------------------------------------------------------------------
- function TRectD.Contains(const Pt: TPoint): Boolean;
- begin
- Result := (pt.X >= Left) and (pt.X < Right) and
- (pt.Y >= Top) and (pt.Y < Bottom);
- end;
- //------------------------------------------------------------------------------
- function TRectD.Contains(const Pt: TPointD): Boolean;
- begin
- Result := (pt.X >= Left) and (pt.X < Right) and
- (pt.Y >= Top) and (pt.Y < Bottom);
- end;
- //------------------------------------------------------------------------------
- function RectD(left, top, right, bottom: double): TRectD;
- begin
- result.Left := left;
- result.Top := top;
- result.Right := right;
- result.Bottom := bottom;
- end;
- //------------------------------------------------------------------------------
- function RectD(const rec: TRect): TRectD;
- begin
- with rec do
- begin
- result.Left := left;
- result.Top := top;
- result.Right := right;
- result.Bottom := bottom;
- end;
- end;
- //------------------------------------------------------------------------------
- // TImage32 methods
- //------------------------------------------------------------------------------
- constructor TImage32.Create(width: Integer; height: Integer);
- begin
- fAntiAliased := true;
- fResampler := DefaultResampler;
- fwidth := Max(0, width);
- fheight := Max(0, height);
- NewColor32Array(fPixels, fwidth * fheight);
- end;
- //------------------------------------------------------------------------------
- constructor TImage32.Create(const src: TArrayOfColor32; width: Integer; height: Integer);
- begin
- fAntiAliased := true;
- fResampler := DefaultResampler;
- width := Max(0, width);
- height := Max(0, height);
- if Length(src) <> width * height then
- raise Exception.Create(rsInvalidImageArrayData);
- fWidth := width;
- fHeight := height;
- fPixels := src;
- end;
- //------------------------------------------------------------------------------
- constructor TImage32.Create(src: TImage32);
- begin
- Assign(src);
- end;
- //------------------------------------------------------------------------------
- constructor TImage32.Create(src: TImage32; const srcRec: TRect);
- var
- rec: TRect;
- begin
- fAntiAliased := src.AntiAliased;
- fResampler := src.fResampler;
- types.IntersectRect(rec, src.Bounds, srcRec);
- RectWidthHeight(rec, fWidth, fHeight);
- if (fWidth = 0) or (fheight = 0) then Exit;
- fPixels := src.CopyPixels(rec);
- end;
- //------------------------------------------------------------------------------
- destructor TImage32.Destroy;
- begin
- fPixels := nil;
- inherited;
- end;
- //------------------------------------------------------------------------------
- class function TImage32.IsRegisteredFormat(const ext: string): Boolean;
- begin
- result := Assigned(TImage32.GetImageFormatClass(ext));
- end;
- //------------------------------------------------------------------------------
- class procedure TImage32.RegisterImageFormatClass(ext: string;
- bm32ExClass: TImageFormatClass; clipPriority: TClipboardPriority);
- var
- i: Integer;
- imgFmtRec: PImgFmtRec;
- isNewFormat: Boolean;
- begin
- if not Assigned(ImageFormatClassList) then CreateImageFormatList;
- if (ext = '') or (ext = '.') then Exit;
- if (ext[1] = '.') then Delete(ext, 1,1);
- if not IsAlphaChar(ext[1]) then Exit;
- isNewFormat := true;
- // avoid duplicates but still allow overriding
- for i := 0 to imageFormatClassList.count -1 do
- begin
- imgFmtRec := PImgFmtRec(imageFormatClassList[i]);
- if SameText(imgFmtRec.Fmt, ext) then
- begin
- imgFmtRec.Obj := bm32ExClass; // replace prior class
- if imgFmtRec.SortOrder = clipPriority then
- Exit; // re-sorting isn't required
- imgFmtRec.SortOrder := clipPriority;
- isNewFormat := false;
- Break;
- end;
- end;
- if isNewFormat then
- begin
- new(imgFmtRec);
- imgFmtRec.Fmt := ext;
- imgFmtRec.SortOrder := clipPriority;
- imgFmtRec.Obj := bm32ExClass;
- ImageFormatClassList.Add(imgFmtRec);
- end;
- // Sort with lower priority before higher.
- // Sorting here is arguably inefficient but, with so few
- // entries, this inefficiency will be inconsequential.
- {$IFDEF XPLAT_GENERICS}
- ImageFormatClassList.Sort(TComparer<PImgFmtRec>.Construct(
- function(const imgFmtRec1, imgFmtRec2: PImgFmtRec): Integer
- begin
- Result := Integer(imgFmtRec1.SortOrder) - Integer(imgFmtRec2.SortOrder);
- end));
- {$ELSE}
- ImageFormatClassList.Sort(ImageFormatClassListSort);
- {$ENDIF}
- end;
- //------------------------------------------------------------------------------
- class function TImage32.GetImageFormatClass(const ext: string): TImageFormatClass;
- var
- i: Integer;
- pattern: string;
- imgFmtRec: PImgFmtRec;
- begin
- Result := nil;
- pattern := ext;
- if (pattern = '') or (pattern = '.') then Exit;
- if pattern[1] = '.' then Delete(pattern, 1,1);
- //try for highest priority first
- for i := imageFormatClassList.count -1 downto 0 do
- begin
- imgFmtRec := PImgFmtRec(imageFormatClassList[i]);
- if not SameText(imgFmtRec.Fmt, pattern) then Continue;
- Result := imgFmtRec.Obj;
- break;
- end;
- end;
- //------------------------------------------------------------------------------
- class function TImage32.GetImageFormatClass(stream: TStream): TImageFormatClass;
- var
- i: integer;
- begin
- Result := nil;
- for i := 0 to imageFormatClassList.count -1 do
- with PImgFmtRec(imageFormatClassList[i])^ do
- if Obj.IsValidImageStream(stream) then
- begin
- Result := Obj;
- break;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.Assign(src: TImage32);
- begin
- if assigned(src) then
- src.AssignTo(self);
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.AssignTo(dst: TImage32);
- begin
- if dst = self then Exit;
- dst.BeginUpdate;
- try
- dst.AssignSettings(Self);
- try
- dst.fPixels := System.Copy(fPixels, 0, Length(fPixels));
- dst.fWidth := fWidth;
- dst.fHeight := fHeight;
- dst.Resized;
- except
- dst.SetSize(0,0);
- end;
- finally
- dst.EndUpdate;
- end;
- dst.fColorCount := fColorCount; // dst.EndUpdate called ResetColorCount
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.AssignSettings(src: TImage32);
- begin
- if assigned(src) and (src <> Self) then
- begin
- BeginUpdate;
- try
- fResampler := src.fResampler;
- fIsPremultiplied := src.fIsPremultiplied;
- fAntiAliased := src.fAntiAliased;
- ResetColorCount;
- finally
- EndUpdate;
- end;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.AssignPixelArray(const src: TArrayOfColor32; width: Integer; height: Integer);
- var
- wasResized: Boolean;
- begin
- width := Max(0, width);
- height := Max(0, height);
- if Length(src) <> width * height then
- raise Exception.Create(rsInvalidImageArrayData);
- wasResized := (fWidth <> width) or (fHeight <> height);
- BeginUpdate;
- try
- fWidth := width;
- fHeight := height;
- fPixels := src;
- finally
- EndUpdate;
- end;
- if wasResized then
- Resized;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.Changed;
- begin
- if fUpdateCnt <> 0 then Exit;
- ResetColorCount;
- if Assigned(fOnChange) then fOnChange(Self);
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.Resized;
- begin
- if fUpdateCnt <> 0 then Exit
- else if Assigned(fOnResize) then fOnResize(Self)
- else Changed;
- end;
- //------------------------------------------------------------------------------
- function TImage32.SetPixels(const newPixels: TArrayOfColor32): Boolean;
- var
- len: integer;
- begin
- len := Length(newPixels);
- Result := (len > 0)and (len = Width * height);
- if Result then fPixels := System.Copy(newPixels, 0, len);
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.BeginUpdate;
- begin
- if fNotifyBlockCnt > 0 then Exit;
- inc(fUpdateCnt);
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.EndUpdate;
- begin
- if fNotifyBlockCnt > 0 then Exit;
- dec(fUpdateCnt);
- if fUpdateCnt = 0 then Changed;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.BlockNotify;
- begin
- inc(fNotifyBlockCnt);
- inc(fUpdateCnt);
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.UnblockNotify;
- begin
- dec(fNotifyBlockCnt);
- dec(fUpdateCnt);
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.SetBackgroundColor(bgColor: TColor32);
- var
- i: Integer;
- pc: PColor32;
- begin
- pc := Pixelbase;
- for i := 0 to high(fPixels) do
- begin
- pc^ := BlendToOpaque(bgColor, pc^);
- inc(pc);
- end;
- Changed;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.Clear(color: TColor32);
- var
- i: Integer;
- pc: PColor32;
- begin
- fIsPremultiplied := false;
- if IsEmpty then Exit;
- if color = clNone32 then
- FillChar(fPixels[0], Width * Height * SizeOf(TColor32), 0)
- else
- begin
- pc := PixelBase;
- for i := 0 to Width * Height -1 do
- begin
- pc^ := color;
- inc(pc);
- end;
- end;
- Changed;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.Clear(const rec: TRect; color: TColor32 = 0);
- begin
- FillRect(rec, color);
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.FillRect(const rec: TRect; color: TColor32);
- var
- i,j, rw, w: Integer;
- c: PColor32;
- r: TRect;
- begin
- Types.IntersectRect(r, rec, bounds);
- if IsEmptyRect(r) then Exit;
- rw := RectWidth(r);
- w := Width;
- c := @Pixels[r.Top * w + r.Left];
- if (color = 0) and (w = rw) then
- FillChar(c^, (r.Bottom - r.Top) * rw * SizeOf(TColor32), 0)
- else if rw = 1 then
- begin
- for i := r.Top to r.Bottom -1 do
- begin
- c^ := color;
- inc(c, w);
- end;
- end
- else if (color = 0) and (rw > 15) then
- begin
- for i := r.Top to r.Bottom -1 do
- begin
- FillChar(c^, rw * SizeOf(TColor32), 0);
- inc(c, w);
- end;
- end
- else
- begin
- for i := r.Top to r.Bottom -1 do
- begin
- for j := 1 to rw do
- begin
- c^ := color;
- inc(c);
- end;
- inc(c, w - rw);
- end;
- end;
- Changed;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.ResetColorCount;
- begin
- fColorCount := 0;
- end;
- //------------------------------------------------------------------------------
- {$RANGECHECKS OFF} // negative array index is used
- function TImage32.RectHasTransparency(const rec: TRect): Boolean;
- var
- i, j, rw: Integer;
- lineByteOffset: nativeint;
- c: PARGB;
- r: TRect;
- begin
- Result := True;
- Types.IntersectRect(r, rec, bounds);
- if IsEmptyRect(r) then Exit;
- rw := RectWidth(r);
- c := @Pixels[r.Top * Width + r.Left];
- if rw = Width then // we can use one loop
- begin
- i := (r.Bottom - r.Top) * rw;
- inc(c, i);
- i := -i;
- while i < 0 do
- begin
- if PARGBArray(c)[i].A < 254 then Exit;
- inc(i);
- end;
- end
- else
- begin
- lineByteOffset := (Width - rw) * SizeOf(TColor32);
- for i := r.Top to r.Bottom -1 do
- begin
- for j := 1 to rw do
- begin
- if c.A < 254 then Exit;
- inc(c);
- end;
- inc(PByte(c), lineByteOffset);
- end;
- end;
- Result := False;
- end;
- {$IFDEF RANGECHECKS_ENABLED}
- {$RANGECHECKS ON}
- {$ENDIF}
- //------------------------------------------------------------------------------
- procedure CheckBlendFill(pc: PColor32; color: TColor32);
- {$IFDEF INLINE} inline; {$ENDIF}
- begin
- if not assigned(pc) then Exit;
- pc^ := BlendToAlpha(pc^, color);
- end;
- //------------------------------------------------------------------------------
- function TImage32.CopyPixels(const rec: TRect): TArrayOfColor32;
- var
- i, clipW, w,h: Integer;
- pSrc, pDst, pDst2: PColor32;
- recClipped: TRect;
- begin
- RectWidthHeight(rec, w,h);
- NewColor32Array(result, w * h, True);
- if w * h = 0 then Exit;
- Types.IntersectRect(recClipped, rec, Bounds);
- //if recClipped is wholely outside the bounds of the image ...
- if IsEmptyRect(recClipped) then
- begin
- //rec is considered valid even when completely outside the image bounds,
- //and so when that happens we simply return a fully transparent image ...
- FillChar(Result[0], w * h * SizeOf(TColor32), 0);
- Exit;
- end;
- //if recClipped is wholely within the bounds of the image ...
- if RectsEqual(recClipped, rec) then
- begin
- pDst := @Result[0];
- pSrc := @fPixels[recClipped.Top * Width + rec.Left];
- for i := recClipped.Top to recClipped.Bottom -1 do
- begin
- Move(pSrc^, pDst^, w * SizeOf(TColor32));
- inc(pSrc, Width); inc(pDst, w);
- end;
- Exit;
- end;
- //a part of 'rec' must be outside the bounds of the image ...
- pDst := @Result[0];
- for i := rec.Top to -1 do
- begin
- FillChar(pDst^, w * SizeOf(TColor32), 0);
- inc(pDst, w);
- end;
- pSrc := @fPixels[recClipped.Top * Width + Max(0,rec.Left)];
- if (rec.Left < 0) or (rec.Right > Width) then
- begin
- clipW := RectWidth(recClipped);
- pDst2 := IncPColor32(pDst, -Min(0, rec.Left));
- for i := recClipped.Top to recClipped.Bottom -1 do
- begin
- //when rec.left < 0 or rec.right > width it's simplest to
- //start with a prefilled row of transparent pixels
- FillChar(pDst^, w * SizeOf(TColor32), 0);
- Move(pSrc^, pDst2^, clipW * SizeOf(TColor32));
- inc(pDst, w); inc(pDst2, w); inc(pSrc, Width);
- end;
- end else
- begin
- //things are simpler when there's no part of 'rec' is
- //outside the image, at least not on the left or right sides ...
- for i := recClipped.Top to recClipped.Bottom -1 do
- begin
- Move(pSrc^, pDst^, w * SizeOf(TColor32));
- inc(pSrc, Width); inc(pDst, w);
- end;
- end;
- for i := Height to rec.Bottom -1 do
- begin
- FillChar(pDst^, w * SizeOf(TColor32), 0);
- inc(pDst, w);
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.Crop(const rec: TRect);
- var
- newPixels: TArrayOfColor32;
- w,h: integer;
- begin
- RectWidthHeight(rec, w, h);
- if (w = Width) and (h = Height) then Exit;
- newPixels := CopyPixels(rec); // get pixels **before** resizing
- BlockNotify;
- try
- SetSize(w, h);
- if not IsEmptyRect(rec) then
- fPixels := newPixels;
- finally
- UnblockNotify;
- end;
- Resized;
- end;
- //------------------------------------------------------------------------------
- function TImage32.GetBounds: TRect;
- begin
- result := Types.Rect(0, 0, Width, Height);
- end;
- //------------------------------------------------------------------------------
- function TImage32.GetMidPoint: TPointD;
- begin
- Result := PointD(fWidth * 0.5, fHeight * 0.5);
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.SetSize(newWidth, newHeight: Integer; color: TColor32);
- begin
- //very large images are usually due to a bug
- if (newWidth > 20000) or (newHeight > 20000) then
- raise Exception.Create(rsImageTooLarge);
- fwidth := Max(0, newWidth);
- fheight := Max(0, newHeight);
- fPixels := nil; //forces a blank image
- NewColor32Array(fPixels, fwidth * fheight, True);
- fIsPremultiplied := false;
- BlockNotify;
- Clear(color);
- UnblockNotify;
- Resized;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.Resize(newWidth, newHeight: Integer);
- begin
- ResizeTo(Self, newWidth, newHeight);
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.ResizeTo(targetImg: TImage32; newWidth, newHeight: Integer);
- begin
- if (newWidth <= 0) or (newHeight <= 0) then
- begin
- targetImg.SetSize(0, 0);
- Exit;
- end
- else if (newWidth = fwidth) and (newHeight = fheight) then
- begin
- if targetImg <> Self then targetImg.Assign(Self);
- Exit
- end
- else if IsEmpty then
- begin
- targetImg.SetSize(newWidth, newHeight);
- Exit;
- end;
- targetImg.BlockNotify;
- try
- if targetImg.fResampler <= rNearestResampler then
- NearestNeighborResize(Self, targetImg, newWidth, newHeight)
- else
- ResamplerResize(Self, targetImg, newWidth, newHeight);
- finally
- targetImg.UnblockNotify;
- end;
- targetImg.Resized;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.Scale(s: double);
- begin
- Scale(s, s);
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.ScaleTo(targetImg: TImage32; s: double);
- begin
- ScaleTo(targetImg, s, s);
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.Scale(sx, sy: double);
- begin
- if (sx > 0) and (sy > 0) then
- Resize(Round(width * sx), Round(height * sy));
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.ScaleTo(targetImg: TImage32; sx, sy: double);
- begin
- if (sx > 0) and (sy > 0) then
- ResizeTo(targetImg, Round(width * sx), Round(height * sy));
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.ScaleToFit(width, height: integer);
- var
- sx, sy: double;
- begin
- if IsEmpty or (width < 2) or (height < 2) then Exit;
- sx := width / self.Width;
- sy := height / self.Height;
- if sx <= sy then
- Scale(sx) else
- Scale(sy);
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.ScaleToFitCentered(const rect: TRect);
- begin
- ScaleToFitCentered(RectWidth(rect), RectHeight(rect));
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.ScaleToFitCentered(width, height: integer);
- var
- sx, sy: double;
- tmp: TImage32;
- rec2: TRect;
- begin
- if IsEmpty or (width <= 0) or (height <= 0) or
- ((width = self.Width) and (height = self.Height)) then Exit;
- sx := width / self.Width;
- sy := height / self.Height;
- BlockNotify;
- try
- if sx <= sy then
- begin
- Scale(sx);
- if height = self.Height then Exit;
- rec2 := Bounds;
- TranslateRect(rec2, 0, (height - self.Height) div 2);
- tmp := TImage32.Create(self);
- try
- SetSize(width, height);
- CopyInternal(tmp, tmp.Bounds, rec2, nil);
- finally
- tmp.Free;
- end;
- end else
- begin
- Scale(sy);
- if width = self.Width then Exit;
- rec2 := Bounds;
- TranslateRect(rec2, (width - self.Width) div 2, 0);
- tmp := TImage32.Create(self);
- try
- SetSize(width, height);
- CopyInternal(tmp, tmp.Bounds, rec2, nil);
- finally
- tmp.Free;
- end;
- end;
- finally
- UnblockNotify;
- end;
- Resized;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.RotateLeft90;
- var
- x,y, xx: Integer;
- src, dst: PColor32;
- tmp: TImage32;
- begin
- if IsEmpty then Exit;
- BeginUpdate;
- tmp := TImage32.create(Self);
- try
- SetSize(Height, Width);
- xx := (width - 1) * Height;
- dst := PixelBase;
- for y := 0 to Height -1 do
- begin
- src := @tmp.Pixels[xx + y];
- for x := 0 to Width -1 do
- begin
- dst^ := src^;
- inc(dst); dec(src, Height);
- end;
- end;
- finally
- tmp.Free;
- EndUpdate;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.RotateRight90;
- var
- x,y: Integer;
- src, dst: PColor32;
- tmp: TImage32;
- begin
- if IsEmpty then Exit;
- BeginUpdate;
- tmp := TImage32.create(Self);
- try
- SetSize(Height, Width);
- dst := PixelBase;
- for y := 0 to Height -1 do
- begin
- src := @tmp.Pixels[Height -1 - y];
- for x := 0 to Width -1 do
- begin
- dst^ := src^;
- inc(dst); inc(src, Height);
- end;
- end;
- finally
- tmp.Free;
- EndUpdate;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.Rotate180;
- var
- x,y: Integer;
- src, dst: PColor32;
- tmp: TImage32;
- begin
- if IsEmpty then Exit;
- tmp := TImage32.create(Self);
- try
- dst := PixelBase;
- src := @tmp.Pixels[Width * Height -1];
- for y := 0 to Height -1 do
- begin
- for x := 0 to Width -1 do
- begin
- dst^ := src^;
- inc(dst); dec(src);
- end;
- end;
- finally
- tmp.Free;
- end;
- Changed;
- end;
- //------------------------------------------------------------------------------
- function TImage32.GetColorCount: Integer;
- var
- allColors: PByteArray;
- i: Integer;
- c: PColor32;
- const
- cube256 = 256 * 256 * 256;
- begin
- result := 0;
- if IsEmpty then Exit;
- if fColorCount > 0 then
- begin
- result := fColorCount;
- Exit;
- end;
- //because 'allColors' uses quite a chunk of memory, it's
- //allocated on the heap rather than the stack
- allColors := AllocMem(cube256); //nb: zero initialized
- try
- c := PixelBase;
- for i := 0 to Width * Height -1 do
- begin
- //ignore colors with signifcant transparency
- if GetAlpha(c^) > $80 then
- allColors[c^ and $FFFFFF] := 1;
- inc(c);
- end;
- for i := 0 to cube256 -1 do
- if allColors[i] = 1 then inc(Result);
- finally
- FreeMem(allColors);
- end;
- fColorCount := Result; //avoids repeating the above unnecessarily
- end;
- //------------------------------------------------------------------------------
- function TImage32.GetHasTransparency: Boolean;
- var
- i: Integer;
- pc: PARGB;
- begin
- result := true;
- If IsEmpty then Exit;
- pc := PARGB(PixelBase);
- for i := 0 to Width * Height -1 do
- begin
- if pc.A < 128 then Exit;
- inc(pc);
- end;
- result := false;
- end;
- //------------------------------------------------------------------------------
- function TImage32.SaveToFile(filename: string; compressionQuality: integer): Boolean;
- var
- fileFormatClass: TImageFormatClass;
- begin
- result := false;
- if IsEmpty or (length(filename) < 5) then Exit;
- //use the process's current working directory if no path supplied ...
- if ExtractFilePath(filename) = '' then
- filename := GetCurrentDir + '\'+ filename;
- fileFormatClass := GetImageFormatClass(ExtractFileExt(filename));
- if assigned(fileFormatClass) then
- with fileFormatClass.Create do
- try
- result := SaveToFile(filename, self, compressionQuality);
- finally
- free;
- end;
- end;
- //------------------------------------------------------------------------------
- function TImage32.SaveToStream(stream: TStream;
- const FmtExt: string; compressionQuality: integer): Boolean;
- var
- fileFormatClass: TImageFormatClass;
- begin
- result := false;
- fileFormatClass := GetImageFormatClass(FmtExt);
- if assigned(fileFormatClass) then
- with fileFormatClass.Create do
- try
- SaveToStream(stream, self, compressionQuality);
- result := true;
- finally
- free;
- end;
- end;
- //------------------------------------------------------------------------------
- function TImage32.LoadFromFile(const filename: string): Boolean;
- var
- stream: TFileStream;
- begin
- Result := false;
- if not FileExists(filename) then Exit;
- stream := TFileStream.Create(filename, fmOpenRead or fmShareDenyNone);
- try
- result := LoadFromStream(stream);
- finally
- stream.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- function TImage32.LoadFromStream(stream: TStream; imgIdx: integer): Boolean;
- var
- ifc: TImageFormatClass;
- begin
- ifc := GetImageFormatClass(stream);
- Result := Assigned(ifc);
- if not Result then Exit;
- with ifc.Create do
- try
- result := LoadFromStream(stream, self, imgIdx);
- finally
- free;
- end;
- end;
- //------------------------------------------------------------------------------
- function TImage32.GetPixel(x, y: Integer): TColor32;
- begin
- if (x < 0) or (x >= Width) or (y < 0) or (y >= Height) then
- result := clNone32 else
- result := fPixels[y * width + x];
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.SetPixel(x,y: Integer; color: TColor32);
- begin
- if (x < 0) or (x >= Width) or (y < 0) or (y >= Height) then Exit;
- fPixels[y * width + x] := color;
- //nb: no notify event here
- end;
- //------------------------------------------------------------------------------
- function TImage32.GetIsBlank: Boolean;
- var
- i: integer;
- pc: PARGB;
- begin
- result := IsEmpty;
- if result then Exit;
- pc := PARGB(PixelBase);
- for i := 0 to width * height -1 do
- begin
- if pc.A > 0 then Exit;
- inc(pc);
- end;
- result := true;
- end;
- //------------------------------------------------------------------------------
- function TImage32.GetIsEmpty: Boolean;
- begin
- result := fPixels = nil;
- end;
- //------------------------------------------------------------------------------
- function TImage32.GetPixelBase: PColor32;
- begin
- if IsEmpty then result := nil
- else result := @fPixels[0];
- end;
- //------------------------------------------------------------------------------
- function TImage32.GetPixelRow(row: Integer): PColor32;
- begin
- if IsEmpty then result := nil
- else result := @fPixels[row * Width];
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.CopyInternal(src: TImage32;
- const srcRec, dstRec: TRect; blendFunc: TBlendFunction);
- var
- i, j: integer;
- srcRecWidth, srcRecHeight: nativeint;
- srcWidth, dstWidth: nativeint;
- s, d: PColor32;
- begin
- // occasionally, due to rounding, srcRec and dstRec
- // don't have exactly the same widths and heights, so ...
- srcRecWidth :=
- Min(srcRec.Right - srcRec.Left, dstRec.Right - dstRec.Left);
- srcRecHeight :=
- Min(srcRec.Bottom - srcRec.Top, dstRec.Bottom - dstRec.Top);
- srcWidth := src.Width;
- dstWidth := Width;
- s := @src.Pixels[srcRec.Top * srcWidth + srcRec.Left];
- d := @Pixels[dstRec.top * dstWidth + dstRec.Left];
- if assigned(blendFunc) then
- begin
- srcWidth := (srcWidth - srcRecWidth) * SizeOf(TColor32);
- dstWidth := (dstWidth - srcRecWidth) * SizeOf(TColor32);
- for i := 1 to srcRecHeight do
- begin
- for j := 1 to srcRecWidth do
- begin
- d^ := blendFunc(d^, s^);
- inc(s); inc(d);
- end;
- inc(PByte(s), srcWidth); // byte offset to the next s line
- inc(PByte(d), dstWidth); // byte offset to the next d line
- end;
- end
- //simply overwrite src with dst (ie without blending)
- else if (srcRecWidth = dstWidth) and (srcWidth = dstWidth) then
- move(s^, d^, srcRecWidth * srcRecHeight * SizeOf(TColor32))
- else
- begin
- srcWidth := srcWidth * SizeOf(TColor32);
- dstWidth := dstWidth * SizeOf(TColor32);
- srcRecWidth := srcRecWidth * SizeOf(TColor32);
- for i := 1 to srcRecHeight do
- begin
- move(s^, d^, srcRecWidth);
- inc(PByte(s), srcWidth); // srcWidth is in bytes
- inc(PByte(d), dstWidth); // dstWidth is in bytes
- end;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.CopyInternalLine(src: TImage32;
- const srcRec, dstRec: TRect; blendLineFunc: TBlendLineFunction);
- var
- i: integer;
- srcRecWidth, srcRecHeight: nativeint;
- srcWidth, dstWidth: nativeint;
- s, d: PColor32;
- begin
- if not Assigned(blendLineFunc) then
- begin
- CopyInternal(src, srcRec, dstRec, nil);
- Exit;
- end;
- // occasionally, due to rounding, srcRec and dstRec
- // don't have exactly the same widths and heights, so ...
- srcRecWidth :=
- Min(srcRec.Right - srcRec.Left, dstRec.Right - dstRec.Left);
- srcRecHeight :=
- Min(srcRec.Bottom - srcRec.Top, dstRec.Bottom - dstRec.Top);
- srcWidth := src.Width;
- dstWidth := Width;
- s := @src.Pixels[srcRec.Top * srcWidth + srcRec.Left];
- d := @Pixels[dstRec.top * dstWidth + dstRec.Left];
- if (srcRecWidth = dstWidth) and (srcWidth = dstWidth) then
- blendLineFunc(d, s, srcRecWidth * srcRecHeight)
- else
- begin
- srcWidth := srcWidth * SizeOf(TColor32);
- dstWidth := dstWidth * SizeOf(TColor32);
- for i := 1 to srcRecHeight do
- begin
- blendLineFunc(d, s, srcRecWidth);
- inc(PByte(s), srcWidth); // srcWidth is in bytes
- inc(PByte(d), dstWidth); // dstWidth is in bytes
- end;
- end;
- end;
- //------------------------------------------------------------------------------
- function TImage32.Copy(src: TImage32; srcRec, dstRec: TRect): Boolean;
- begin
- Result := CopyBlendInternal(src, srcRec, dstRec, nil, nil);
- end;
- //------------------------------------------------------------------------------
- function TImage32.CopyBlend(src: TImage32; const srcRec, dstRec: TRect;
- blendFunc: TBlendFunction): Boolean;
- begin
- Result := CopyBlendInternal(src, srcRec, dstRec, blendFunc, nil);
- end;
- //------------------------------------------------------------------------------
- function TImage32.CopyBlend(src: TImage32; const srcRec, dstRec: TRect;
- blendLineFunc: TBlendLineFunction): Boolean;
- begin
- Result := CopyBlendInternal(src, srcRec, dstRec, nil, blendLineFunc);
- end;
- //------------------------------------------------------------------------------
- function TImage32.CopyBlendInternal(src: TImage32; const srcRec: TRect; dstRec: TRect;
- blendFunc: TBlendFunction; blendLineFunc: TBlendLineFunction): Boolean;
- var
- tmp: TImage32;
- srcRecClipped, dstRecClipped, r: TRect;
- scaleX, scaleY: double;
- w,h, dstW,dstH, srcW,srcH: integer;
- begin
- result := false;
- if IsEmptyRect(srcRec) or IsEmptyRect(dstRec) then Exit;
- Types.IntersectRect(srcRecClipped, srcRec, src.Bounds);
- //get the scaling amount (if any) before
- //dstRec might be adjusted due to clipping ...
- RectWidthHeight(dstRec, dstW, dstH);
- RectWidthHeight(srcRec, srcW, srcH);
- //watching out for insignificant scaling
- if Abs(dstW - srcW) < 2 then
- scaleX := 1 else
- scaleX := dstW / srcW;
- if Abs(dstH - srcH) < 2 then
- scaleY := 1 else
- scaleY := dstH / srcH;
- //check if the source rec has been clipped ...
- if not RectsEqual(srcRecClipped, srcRec) then
- begin
- if IsEmptyRect(srcRecClipped) then Exit;
- //the source has been clipped so clip the destination too ...
- RectWidthHeight(srcRecClipped, w, h);
- RectWidthHeight(srcRec, srcW, srcH);
- ScaleRect(dstRec, w / srcW, h / srcH);
- TranslateRect(dstRec,
- srcRecClipped.Left - srcRec.Left,
- srcRecClipped.Top - srcRec.Top);
- end;
- if (scaleX <> 1.0) or (scaleY <> 1.0) then
- begin
- //scale source (tmp) to the destination then call CopyBlend() again ...^
- tmp := TImage32.Create;
- try
- tmp.AssignSettings(src);
- src.ScaleTo(tmp, scaleX, scaleY);
- ScaleRect(srcRecClipped, scaleX, scaleY);
- result := CopyBlendInternal(tmp, srcRecClipped, dstRec, blendFunc, blendLineFunc);
- finally
- tmp.Free;
- end;
- Exit;
- end;
- Types.IntersectRect(dstRecClipped, dstRec, Bounds);
- if IsEmptyRect(dstRecClipped) then Exit;
- //there's no scaling if we get here, but further clipping may be needed if
- //the destination rec is partially outside the destination image's bounds
- if not RectsEqual(dstRecClipped, dstRec) then
- begin
- //the destination rec has been clipped so clip the source too ...
- RectWidthHeight(dstRecClipped, w, h);
- RectWidthHeight(dstRec, dstW, dstH);
- ScaleRect(srcRecClipped, w / dstW, h / dstH);
- TranslateRect(srcRecClipped,
- dstRecClipped.Left - dstRec.Left,
- dstRecClipped.Top - dstRec.Top);
- end;
- //when copying to self and srcRec & dstRec overlap then
- //copy srcRec to a temporary image and use it as the source ...
- if (src = self) and Types.IntersectRect(r, srcRecClipped, dstRecClipped) then
- begin
- tmp := TImage32.Create(self, srcRecClipped);
- try
- result := src.CopyBlendInternal(tmp, tmp.Bounds, dstRecClipped, blendFunc, blendLineFunc);
- finally
- tmp.Free;
- end;
- Exit;
- end;
- if Assigned(blendLineFunc) then
- CopyInternalLine(src, srcRecClipped, dstRecClipped, blendLineFunc)
- else
- CopyInternal(src, srcRecClipped, dstRecClipped, blendFunc);
- result := true;
- Changed;
- end;
- //------------------------------------------------------------------------------
- function TImage32.LoadFromResource(const resName: string; resType: PChar): Boolean;
- var
- resStream: TResourceStream;
- begin
- resStream := CreateResourceStream(resName, resType);
- try
- Result := assigned(resStream) and
- LoadFromStream(resStream);
- finally
- resStream.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- {$IF DEFINED (MSWINDOWS)}
- procedure TImage32.CopyFromDC(srcDc: HDC; const srcRect: TRect);
- var
- bi: TBitmapInfoHeader;
- bm, oldBm: HBitmap;
- dc, memDc: HDC;
- pixels: Pointer;
- w,h: integer;
- begin
- BeginUpdate;
- try
- RectWidthHeight(srcRect, w,h);
- SetSize(w, h);
- bi := Get32bitBitmapInfoHeader(w, -h); // -h => avoids need to flip image
- dc := GetDC(0);
- memDc := CreateCompatibleDC(dc);
- try
- bm := CreateDIBSection(dc,
- PBITMAPINFO(@bi)^, DIB_RGB_COLORS, pixels, 0, 0);
- if bm = 0 then Exit;
- try
- oldBm := SelectObject(memDc, bm);
- BitBlt(memDc, 0, 0, w, h, srcDc, srcRect.Left,srcRect.Top, SRCCOPY);
- Move(pixels^, fPixels[0], w * h * sizeOf(TColor32));
- SelectObject(memDc, oldBm);
- finally
- DeleteObject(bm);
- end;
- finally
- DeleteDc(memDc);
- ReleaseDc(0, dc);
- end;
- if IsBlank then SetAlpha(255);
- //FlipVertical;
- finally
- EndUpdate;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.CopyToDc(dstDc: HDC; x,y: Integer; transparent: Boolean);
- begin
- CopyToDc(Bounds, Types.Rect(x,y, x+Width, y+Height),
- dstDc, transparent);
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.CopyToDc(const srcRect: TRect; dstDc: HDC;
- x: Integer = 0; y: Integer = 0; transparent: Boolean = true);
- var
- recW, recH: integer;
- begin
- RectWidthHeight(srcRect, recW, recH);
- CopyToDc(srcRect, Types.Rect(x,y, x+recW, y+recH), dstDc, transparent);
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.CopyToDc(const srcRect, dstRect: TRect;
- dstDc: HDC; transparent: Boolean = true);
- var
- i, x,y, wSrc ,hSrc, wDest, hDest, wBytes: integer;
- rec: TRect;
- bi: TBitmapInfoHeader;
- bm, oldBm: HBitmap;
- dibBits: Pointer;
- pDst, pSrc: PARGB;
- memDc: HDC;
- isTransparent: Boolean;
- bf: BLENDFUNCTION;
- oldStretchBltMode: integer;
- begin
- Types.IntersectRect(rec, srcRect, Bounds);
- if IsEmpty or IsEmptyRect(rec) or IsEmptyRect(dstRect) then Exit;
- RectWidthHeight(rec, wSrc, hSrc);
- RectWidthHeight(dstRect, wDest, hDest);
- x := dstRect.Left;
- y := dstRect.Top;
- inc(x, rec.Left - srcRect.Left);
- inc(y, rec.Top - srcRect.Top);
- bi := Get32bitBitmapInfoHeader(wSrc, hSrc);
- isTransparent := transparent and RectHasTransparency(srcRect);
- memDc := CreateCompatibleDC(dstDc);
- try
- bm := CreateDIBSection(memDc, PBITMAPINFO(@bi)^,
- DIB_RGB_COLORS, dibBits, 0, 0);
- if bm = 0 then Exit;
- try
- //copy Image to dibBits (with vertical flip)
- wBytes := wSrc * SizeOf(TColor32);
- pDst := dibBits;
- pSrc := PARGB(PixelRow[rec.Bottom -1]);
- inc(pSrc, rec.Left);
- if isTransparent and not IsPremultiplied then
- begin
- //premultiplied alphas are required when alpha blending
- for i := rec.Bottom -1 downto rec.Top do
- begin
- PremultiplyAlpha(pSrc, pDst, wSrc);
- dec(pSrc, Width);
- inc(pDst, wSrc);
- end;
- end
- else
- begin
- for i := rec.Bottom -1 downto rec.Top do
- begin
- Move(pSrc^, pDst^, wBytes);
- dec(pSrc, Width);
- inc(pDst, wSrc);
- end;
- end;
- oldBm := SelectObject(memDC, bm);
- if isTransparent then
- begin
- //premultiplied alphas are required when alpha blending
- bf.BlendOp := AC_SRC_OVER;
- bf.BlendFlags := 0;
- bf.SourceConstantAlpha := 255;
- bf.AlphaFormat := AC_SRC_ALPHA;
- AlphaBlend(dstDc, x,y, wDest,hDest, memDC, 0,0, wSrc,hSrc, bf);
- end
- else if (wDest = wSrc) and (hDest = hSrc) then
- begin
- BitBlt(dstDc, x,y, wSrc, hSrc, memDc, 0,0, SRCCOPY)
- end else
- begin
- oldStretchBltMode := SetStretchBltMode(dstDc, COLORONCOLOR);
- StretchBlt(dstDc, x,y, wDest, hDest, memDc, 0,0, wSrc,hSrc, SRCCOPY);
- if oldStretchBltMode <> COLORONCOLOR then // restore mode
- SetStretchBltMode(dstDc, oldStretchBltMode);
- end;
- SelectObject(memDC, oldBm);
- finally
- DeleteObject(bm);
- end;
- finally
- DeleteDc(memDc);
- end;
- end;
- {$IFEND}
- //------------------------------------------------------------------------------
- {$IF DEFINED(USING_VCL_LCL)}
- procedure TImage32.CopyFromBitmap(bmp: TBitmap);
- var
- ms: TMemoryStream;
- bmpFormat: TImageFormat_BMP;
- begin
- ms := TMemoryStream.create;
- bmpFormat := TImageFormat_BMP.Create;
- try
- bmp.SaveToStream(ms);
- ms.Position := 0;
- bmpFormat.LoadFromStream(ms, self);
- finally
- ms.Free;
- bmpFormat.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.CopyToBitmap(bmp: TBitmap);
- var
- ms: TMemoryStream;
- bmpFormat: TImageFormat_BMP;
- begin
- ms := TMemoryStream.create;
- bmpFormat := TImageFormat_BMP.Create;
- try
- bmpFormat.IncludeFileHeaderInSaveStream := true;
- bmpFormat.SaveToStream(ms, self);
- ms.Position := 0;
- bmp.PixelFormat := pf32bit;
- {$IF DEFINED(USING_VCL) AND DEFINED(ALPHAFORMAT)}
- bmp.AlphaFormat := afDefined;
- {$IFEND}
- bmp.LoadFromStream(ms);
- finally
- ms.Free;
- bmpFormat.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- {$IFEND}
- function TImage32.CopyToClipBoard: Boolean;
- var
- i: Integer;
- formatClass: TImageFormatClass;
- begin
- //Sadly with CF_DIB (and even CF_DIBV5) clipboard formats, transparency is
- //usually lost, so we'll copy all available formats including CF_PNG, that
- //is if it's registered.
- result := not IsEmpty;
- if not result then Exit;
- result := false;
- for i := ImageFormatClassList.Count -1 downto 0 do
- begin
- formatClass := PImgFmtRec(ImageFormatClassList[i]).Obj;
- if not formatClass.CanCopyToClipboard then Continue;
- with formatClass.Create do
- try
- result := CopyToClipboard(self);
- finally
- free;
- end;
- end;
- end;
- //------------------------------------------------------------------------------
- class function TImage32.CanPasteFromClipBoard: Boolean;
- var
- i: Integer;
- formatClass: TImageFormatClass;
- begin
- result := false;
- for i := ImageFormatClassList.Count -1 downto 0 do
- begin
- formatClass := PImgFmtRec(ImageFormatClassList[i]).Obj;
- if formatClass.CanPasteFromClipboard then
- begin
- result := true;
- Exit;
- end;
- end;
- end;
- //------------------------------------------------------------------------------
- function TImage32.PasteFromClipBoard: Boolean;
- var
- i: Integer;
- formatClass: TImageFormatClass;
- begin
- result := false;
- for i := ImageFormatClassList.Count -1 downto 0 do
- begin
- formatClass := PImgFmtRec(ImageFormatClassList[i]).Obj;
- if not formatClass.CanPasteFromClipboard then Continue;
- with formatClass.Create do
- try
- result := PasteFromClipboard(self);
- if not Result then Continue;
- finally
- free;
- end;
- Changed;
- Break;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.ConvertToBoolMask(reference: TColor32; tolerance: integer;
- colorFunc: TCompareFunction; maskBg: TColor32; maskFg: TColor32);
- var
- i: Integer;
- mask: TArrayOfByte;
- c: PColor32;
- b: PByte;
- begin
- if IsEmpty then Exit;
- mask := GetBoolMask(self, reference, colorFunc, tolerance);
- c := PixelBase;
- b := @mask[0];
- for i := 0 to Width * Height -1 do
- begin
- {$IFDEF PBYTE}
- if b^ = 0 then c^ := maskBg else c^ := maskFg;
- {$ELSE}
- if b^ = #0 then c^ := maskBg else c^ := maskFg;
- {$ENDIF}
- inc(c); inc(b);
- end;
- Changed;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.ConvertToAlphaMask(reference: TColor32;
- colorFunc: TCompareFunctionEx);
- var
- i: Integer;
- mask: TArrayOfByte;
- c: PColor32;
- b: PByte;
- begin
- if IsEmpty then Exit;
- mask := GetByteMask(self, reference, colorFunc);
- c := PixelBase;
- b := @mask[0];
- for i := 0 to Width * Height -1 do
- begin
- {$IFDEF PBYTE}
- c^ := b^ shl 24;
- {$ELSE}
- c^ := Ord(b^) shl 24;
- {$ENDIF}
- inc(c); inc(b);
- end;
- Changed;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.FlipVertical;
- var
- i: Integer;
- a: TArrayOfColor32;
- src, dst: PColor32;
- begin
- if IsEmpty then Exit;
- NewColor32Array(a, fWidth * fHeight, True);
- src := @fPixels[(height-1) * width];
- dst := @a[0];
- for i := 0 to fHeight -1 do
- begin
- move(src^, dst^, fWidth * SizeOf(TColor32));
- dec(src, fWidth); inc(dst, fWidth);
- end;
- fPixels := a;
- Changed;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.FlipHorizontal;
- var
- i,j, widthLess1: Integer;
- a: TArrayOfColor32;
- row: PColor32;
- begin
- if IsEmpty then Exit;
- NewColor32Array(a, fWidth, True);
- widthLess1 := fWidth -1;
- row := @fPixels[(height-1) * width]; //top row
- for i := 0 to fHeight -1 do
- begin
- move(row^, a[0], fWidth * SizeOf(TColor32));
- for j := 0 to widthLess1 do
- begin
- row^ := a[widthLess1 - j];
- inc(row);
- end;
- dec(row, fWidth *2);
- end;
- Changed;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.PreMultiply;
- begin
- if IsEmpty or fIsPremultiplied then Exit;
- fIsPremultiplied := true;
- PremultiplyAlpha(PARGB(PixelBase), PARGB(PixelBase), Width * Height);
- //nb: no OnChange notify event here
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.SetRGB(rgbColor: TColor32);
- var
- i: Integer;
- pc: PColor32;
- c: TColor32;
- begin
- //this method leaves the alpha channel untouched
- if IsEmpty then Exit;
- pc := PixelBase;
- rgbColor := rgbColor and $00FFFFFF;
- for i := 0 to Width * Height - 1 do
- begin
- c := pc^;
- if c and $FF000000 = 0 then
- pc^ := 0 else
- pc^ := c and $FF000000 or rgbColor;
- inc(pc);
- end;
- Changed;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.SetRGB(rgbColor: TColor32; rec: TRect);
- var
- i,j, dx: Integer;
- pc: PColor32;
- begin
- Types.IntersectRect(rec, rec, bounds);
- if IsEmptyRect(rec) then Exit;
- rgbColor := rgbColor and $00FFFFFF;
- pc := PixelBase;
- inc(pc, rec.Left);
- dx := Width - RectWidth(rec);
- for i := rec.Top to rec.Bottom -1 do
- begin
- for j := rec.Left to rec.Right -1 do
- begin
- pc^ := pc^ and $FF000000 or rgbColor;
- inc(pc);
- end;
- inc(pc, dx);
- end;
- Changed;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.SetAlpha(alpha: Byte);
- var
- i: Integer;
- c: PARGB;
- begin
- //this method only changes the alpha channel
- if IsEmpty then Exit;
- c := PARGB(PixelBase);
- for i := 0 to Width * Height -1 do
- begin
- c.A := alpha;
- inc(c);
- end;
- Changed;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.ReduceOpacity(opacity: Byte);
- var
- i: Integer;
- c: PARGB;
- a: Byte;
- begin
- if opacity = 255 then Exit;
- c := PARGB(PixelBase);
- for i := 0 to Width * Height -1 do
- begin
- a := c.A;
- if a <> 0 then
- c.A := MulTable[a, opacity];
- inc(c);
- end;
- Changed;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.ReduceOpacity(opacity: Byte; rec: TRect);
- var
- i,j, rw: Integer;
- c: PARGB;
- a: Byte;
- lineOffsetInBytes: integer;
- begin
- Types.IntersectRect(rec, rec, bounds);
- if IsEmptyRect(rec) then Exit;
- rw := RectWidth(rec);
- c := @Pixels[rec.Top * Width + rec.Left];
- lineOffsetInBytes := (Width - rw) * SizeOf(TARGB);
- for i := rec.Top to rec.Bottom - 1 do
- begin
- for j := 1 to rw do
- begin
- a := c.A;
- if a <> 0 then
- c.A := MulTable[a, opacity];
- inc(c);
- end;
- inc(PByte(c), lineOffsetInBytes);
- end;
- Changed;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.Grayscale(mode: TGrayscaleMode;
- linearAmountPercentage: double);
- var
- i: SizeInt;
- cLinear: double;
- c, lastC, grayC: TColor32;
- p: PColor32Array;
- amountCalc: Boolean;
- oneMinusAmount: double;
- begin
- if mode = gsmSaturation then
- begin
- // linearAmountPercentage has no effect here
- AdjustSaturation(-100);
- Exit;
- end;
- // Colorimetric (perceptual luminance-preserving) conversion to grayscale
- // See https://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
- if IsEmpty then Exit;
- if linearAmountPercentage <= 0.0 then Exit;
- amountCalc := linearAmountPercentage < 1.0;
- oneMinusAmount := 1.0 - linearAmountPercentage;
- p := PColor32Array(PixelBase);
- lastC := 0;
- grayC := 0;
- for i := 0 to high(fPixels) do
- begin
- c := p[i] and $00FFFFFF;
- if c <> 0 then
- begin
- if c <> lastC then // only do the calculation if the color channels changed
- begin
- lastC := c;
- {$IF DEFINED(ANDROID)}
- c := SwapRedBlue(c);
- {$IFEND}
- // We don't divide by 255 here, so can skip some division and multiplications.
- // That means cLinear is actually "cLinear * 255"
- cLinear := (0.2126 * Byte(c shr 16)) + (0.7152 * Byte(c shr 8)) + (0.0722 * Byte(c));
- //cLinear := (0.2126 * TARGB(c).R) + (0.7152 * TARGB(c).G) + (0.0722 * TARGB(c).B);
- if mode = gsmLinear then
- c := ClampByte(cLinear)
- else //if mode = gsmColorimetric then
- begin
- if cLinear <= (0.0031308 * 255) then // adjust for cLinear being "cLiniear * 255"
- c := ClampByte(Integer(Round(12.92 * cLinear)))
- else // for Power we must divide by 255 and then later multipy by 255
- //c := ClampByte(Integer(Round((1.055 * 255) * Power(cLinear / 255, 1/2.4) - (0.055 * 255))));
- end;
- if not amountCalc then
- grayC := (c shl 16) or (c shl 8) or c
- else
- begin
- cLinear := c * linearAmountPercentage;
- grayC := ClampByte(Integer(Round(Byte(lastC shr 16) * oneMinusAmount + cLinear))) shl 16 or
- ClampByte(Integer(Round(Byte(lastC shr 8) * oneMinusAmount + cLinear))) shl 8 or
- ClampByte(Integer(Round(Byte(lastC ) * oneMinusAmount + cLinear)));
- end;
- {$IF DEFINED(ANDROID)}
- grayC := SwapRedBlue(grayC);
- {$IFEND}
- end;
- p[i] := (p[i] and $FF000000) or grayC;
- end;
- end;
- Changed;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.InvertColors;
- var
- pc: PColor32Array;
- i: SizeInt;
- begin
- pc := PColor32Array(PixelBase);
- for i := 0 to Width * Height -1 do
- pc[i] := pc[i] xor $00FFFFFF; // keep the alpha channel untouched
- Changed;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.InvertAlphas;
- var
- pc: PColor32Array;
- i: SizeInt;
- begin
- pc := PColor32Array(PixelBase);
- for i := 0 to Width * Height -1 do
- pc[i] := pc[i] xor $FF000000; // keep the color channels untouched
- Changed;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.AdjustHue(percent: Integer);
- var
- i: SizeInt;
- hsl: THsl;
- lut: array [byte] of byte;
- c, lastC, newC: TColor32;
- p: PColor32Array;
- begin
- percent := percent mod 100;
- if percent < 0 then inc(percent, 100);
- percent := Round(percent * 255 / 100);
- if (percent = 0) or IsEmpty then Exit;
- for i := 0 to 255 do lut[i] := (i + percent) mod 255;
- lastC := 0;
- newC := 0;
- p := PColor32Array(fPixels);
- for i := 0 to high(fPixels) do
- begin
- c := p[i];
- c := c and $00FFFFFF;
- if c <> 0 then
- begin
- if c <> lastC then // only do the calculation if the color channels changed
- begin
- lastC := C;
- hsl := RgbToHsl(c);
- hsl.hue := lut[hsl.hue];
- newC := HslToRgb(hsl);
- end;
- p[i] := (p[i] and $FF000000) or newC; // keep the alpha channel
- end;
- end;
- Changed;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.AdjustLuminance(percent: Integer);
- var
- i: SizeInt;
- hsl: THsl;
- pc: double;
- lut: array [byte] of byte;
- c, lastC, newC: TColor32;
- p: PColor32Array;
- begin
- if (percent = 0) or IsEmpty then Exit;
- percent := percent mod 101;
- pc := percent / 100;
- if pc > 0 then
- for i := 0 to 255 do lut[i] := Round(i + (255 - i) * pc)
- else
- for i := 0 to 255 do lut[i] := Round(i + (i * pc));
- lastC := 0;
- newC := 0;
- p := PColor32Array(fPixels);
- for i := 0 to high(fPixels) do
- begin
- c := p[i];
- c := c and $00FFFFFF;
- if c <> 0 then
- begin
- if c <> lastC then // only do the calculation if the color channels changed
- begin
- lastC := C;
- hsl := RgbToHsl(c);
- hsl.lum := lut[hsl.lum];
- newC := HslToRgb(hsl);
- end;
- p[i] := (p[i] and $FF000000) or newC; // keep the alpha channel
- end;
- end;
- Changed;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.AdjustSaturation(percent: Integer);
- var
- i: SizeInt;
- hsl: THsl;
- lut: array [byte] of byte;
- pc: double;
- c, lastC, newC: TColor32;
- p: PColor32Array;
- begin
- if (percent = 0) or IsEmpty then Exit;
- percent := percent mod 101;
- pc := percent / 100;
- if pc > 0 then
- for i := 0 to 255 do lut[i] := Round(i + (255 - i) * pc)
- else
- for i := 0 to 255 do lut[i] := Round(i + (i * pc));
- lastC := 0;
- newC := 0;
- p := PColor32Array(fPixels);
- for i := 0 to high(fPixels) do
- begin
- c := p[i];
- c := c and $00FFFFFF;
- if c <> 0 then
- begin
- if c <> lastC then // only do the calculation if the color channels changed
- begin
- lastC := C;
- hsl := RgbToHsl(c);
- hsl.sat := lut[hsl.sat];
- newC := HslToRgb(hsl);
- end;
- p[i] := (p[i] and $FF000000) or newC; // keep the alpha channel
- end;
- end;
- Changed;
- end;
- //------------------------------------------------------------------------------
- function TImage32.GetOpaqueBounds: TRect;
- var
- x,y, x1,x2,y1,y2: Integer;
- found: Boolean;
- begin
- y1 := 0; y2 := 0;
- found := false;
- Result := NullRect;
- for y := 0 to Height -1 do
- begin
- for x := 0 to Width -1 do
- if TARGB(fPixels[y * Width + x]).A > 0 then
- begin
- y1 := y;
- found := true;
- break;
- end;
- if found then break;
- end;
- if not found then
- Exit;
- found := false;
- for y := Height -1 downto 0 do
- begin
- for x := 0 to Width -1 do
- if TARGB(fPixels[y * Width + x]).A > 0 then
- begin
- y2 := y;
- found := true;
- break;
- end;
- if found then break;
- end;
- x1 := Width; x2 := 0;
- for y := y1 to y2 do
- for x := 0 to Width -1 do
- if TARGB(fPixels[y * Width + x]).A > 0 then
- begin
- if x < x1 then x1 := x;
- if x > x2 then x2 := x;
- end;
- Result := Types.Rect(x1, y1, x2+1, y2+1);
- end;
- //------------------------------------------------------------------------------
- function TImage32.CropTransparentPixels: TRect;
- begin
- Result := GetOpaqueBounds;
- if IsEmptyRect(Result) then
- SetSize(0,0) else
- Crop(Result);
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.Rotate(angleRads: double);
- var
- mat: TMatrixD;
- begin
- {$IFDEF CLOCKWISE_ROTATION_WITH_NEGATIVE_ANGLES}
- angleRads := -angleRads;
- {$ENDIF}
- //nb: There's no point rotating about a specific point
- //since the rotated image will be recentered.
- NormalizeAngle(angleRads);
- if IsEmpty or (angleRads = 0) then Exit;
- if angleRads = angle180 then
- begin
- Rotate180; //because we've excluded 0 & 360 deg angles
- end
- else if angleRads = angle90 then
- begin
- RotateRight90;
- end
- else if angleRads = -angle90 then
- begin
- RotateLeft90;
- end else
- begin
- mat := IdentityMatrix;
- // the rotation point isn't important
- // because AffineTransformImage() will
- // will resize and recenter the image
- MatrixRotate(mat, NullPointD, angleRads);
- AffineTransformImage(self, mat);
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.RotateRect(const rec: TRect;
- angleRads: double; eraseColor: TColor32 = 0);
- var
- tmp: TImage32;
- rec2: TRect;
- recWidth, recHeight: integer;
- begin
- recWidth := rec.Right - rec.Left;
- recHeight := rec.Bottom - rec.Top;
- //create a tmp image with a copy of the pixels inside rec ...
- tmp := TImage32.Create(self, rec);
- try
- tmp.Rotate(angleRads);
- //since rotating also resizes, get a centered
- //(clipped) rect of the rotated pixels ...
- rec2.Left := (tmp.Width - recWidth) div 2;
- rec2.Top := (tmp.Height - recHeight) div 2;
- rec2.Right := rec2.Left + recWidth;
- rec2.Bottom := rec2.Top + recHeight;
- //finally move the rotated rec back to the image ...
- FillRect(rec, eraseColor);
- CopyBlend(tmp, rec2, rec);
- finally
- tmp.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.Skew(dx,dy: double);
- var
- mat: TMatrixD;
- begin
- if IsEmpty or ((dx = 0) and (dy = 0)) then Exit;
- //limit skewing to twice the image's width and/or height
- dx := ClampRange(dx, -2.0, 2.0);
- dy := ClampRange(dy, -2.0, 2.0);
- mat := IdentityMatrix;
- MatrixSkew(mat, dx, dy);
- AffineTransformImage(self, mat);
- end;
- //------------------------------------------------------------------------------
- procedure TImage32.ScaleAlpha(scale: double);
- var
- i: Integer;
- pb: PARGB;
- begin
- pb := PARGB(PixelBase);
- for i := 0 to Width * Height - 1 do
- begin
- pb.A := ClampByte(Integer(Round(pb.A * scale)));
- inc(pb);
- end;
- Changed;
- end;
- //------------------------------------------------------------------------------
- // TImageList32
- //------------------------------------------------------------------------------
- constructor TImageList32.Create;
- begin
- {$IFDEF XPLAT_GENERICS}
- fList := TList<TImage32>.Create;
- {$ELSE}
- fList := TList.Create;
- {$ENDIF}
- fIsImageOwner := true;
- end;
- //------------------------------------------------------------------------------
- destructor TImageList32.Destroy;
- begin
- Clear;
- fList.Free;
- inherited;
- end;
- //------------------------------------------------------------------------------
- function TImageList32.Count: integer;
- begin
- result := fList.Count;
- end;
- //------------------------------------------------------------------------------
- procedure TImageList32.Clear;
- var
- i: integer;
- begin
- if IsImageOwner then
- for i := 0 to fList.Count -1 do
- TImage32(fList[i]).Free;
- fList.Clear;
- end;
- //------------------------------------------------------------------------------
- function TImageList32.GetImage(index: integer): TImage32;
- begin
- result := TImage32(fList[index]);
- end;
- //------------------------------------------------------------------------------
- procedure TImageList32.SetImage(index: integer; img: TIMage32);
- begin
- if fIsImageOwner then TImage32(fList[index]).Free;
- fList[index] := img;
- end;
- //------------------------------------------------------------------------------
- function TImageList32.GetLast: TImage32;
- begin
- if Count = 0 then Result := nil
- else Result := TImage32(fList[Count -1]);
- end;
- //------------------------------------------------------------------------------
- procedure TImageList32.Add(image: TImage32);
- begin
- fList.Add(image);
- end;
- //------------------------------------------------------------------------------
- function TImageList32.Add(width, height: integer): TImage32;
- begin
- Result := TImage32.create(width, height);
- fList.Add(Result);
- end;
- //------------------------------------------------------------------------------
- procedure TImageList32.Insert(index: integer; image: TImage32);
- begin
- fList.Insert(index, image);
- end;
- //------------------------------------------------------------------------------
- procedure TImageList32.Move(currentIndex, newIndex: integer);
- begin
- fList.Move(currentIndex, newIndex);
- end;
- //------------------------------------------------------------------------------
- procedure TImageList32.Delete(index: integer);
- begin
- if fIsImageOwner then TImage32(fList[index]).Free;
- fList.Delete(index);
- end;
- //------------------------------------------------------------------------------
- // TImageFormat methods
- //------------------------------------------------------------------------------
- function TImageFormat.LoadFromFile(const filename: string;
- img32: TImage32): Boolean;
- var
- fs: TFileStream;
- begin
- result := FileExists(filename);
- if not result then Exit;
- fs := TFileStream.Create(filename, fmOpenRead or fmShareDenyWrite);
- try
- Result := LoadFromStream(fs, img32);
- finally
- fs.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- function TImageFormat.SaveToFile(const filename: string;
- img32: TImage32; quality: integer): Boolean;
- var
- fs: TFileStream;
- begin
- result := (pos('.', filename) = 1) or
- DirectoryExists(ExtractFilePath(filename));
- if not result then Exit;
- fs := TFileStream.Create(filename, fmCreate);
- try
- SaveToStream(fs, img32, quality);
- finally
- fs.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- class function TImageFormat.CanCopyToClipboard: Boolean;
- begin
- Result := false;
- end;
- //------------------------------------------------------------------------------
- class function TImageFormat.GetImageCount(stream: TStream): integer;
- begin
- Result := 1;
- end;
- //------------------------------------------------------------------------------
- // TInterfacedObj
- //------------------------------------------------------------------------------
- {$IFDEF FPC}
- function TInterfacedObj._AddRef: Integer;
- {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
- begin
- Result := -1;
- end;
- //------------------------------------------------------------------------------
- function TInterfacedObj._Release: Integer;
- {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
- begin
- Result := -1;
- end;
- //------------------------------------------------------------------------------
- function TInterfacedObj.QueryInterface(
- {$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} iid : tguid;
- out obj) : longint;
- begin
- if GetInterface(IID, Obj) then Result := 0
- else Result := E_NOINTERFACE;
- end;
- {$ELSE}
- function TInterfacedObj._AddRef: Integer; stdcall;
- begin
- Result := -1;
- end;
- //------------------------------------------------------------------------------
- function TInterfacedObj._Release: Integer; stdcall;
- begin
- Result := -1;
- end;
- //------------------------------------------------------------------------------
- function TInterfacedObj.QueryInterface(const IID: TGUID;
- out Obj): HResult;
- begin
- if GetInterface(IID, Obj) then Result := 0
- else Result := E_NOINTERFACE;
- end;
- {$ENDIF}
- //------------------------------------------------------------------------------
- // Initialization and Finalization functions
- //------------------------------------------------------------------------------
- procedure MakeBlendTables;
- var
- i,j: Integer;
- begin
- for j := 0 to 255 do MulTable[0, j] := 0;
- for i := 0 to 255 do MulTable[i, 0] := 0;
- for j := 0 to 255 do DivTable[0, j] := 0;
- for i := 0 to 255 do DivTable[i, 0] := 0;
- for i := 1 to 255 do
- begin
- for j := 1 to 255 do
- begin
- MulTable[i, j] := Round(i * j * div255);
- if i >= j then
- DivTable[i, j] := 255 else
- DivTable[i, j] := Round(i * $FF / j);
- end;
- end;
- Sigmoid[128] := 128;
- for i := 1 to 127 do
- Sigmoid[128+i] := 128 + Round(127 * sin(angle90 * i/127));
- for i := 0 to 127 do
- Sigmoid[i] := 255- Sigmoid[255-i];
- end;
- //------------------------------------------------------------------------------
- {$IFDEF MSWINDOWS}
- procedure GetScreenScale;
- var
- dc: HDC;
- ScreenPixelsY: integer;
- begin
- dc := GetDC(0);
- try
- ScreenPixelsY := GetDeviceCaps(dc, LOGPIXELSY);
- DpiAwareOne := ScreenPixelsY / 96;
- finally
- ReleaseDC(0, dc);
- end;
- dpiAware1 := Round(DpiAwareOne);
- end;
- {$ENDIF}
- //------------------------------------------------------------------------------
- procedure CleanUpImageFormatClassList;
- var
- i: integer;
- begin
- for i := ImageFormatClassList.Count -1 downto 0 do
- Dispose(PImgFmtRec(ImageFormatClassList[i]));
- ImageFormatClassList.Free;
- end;
- //------------------------------------------------------------------------------
- //------------------------------------------------------------------------------
- procedure CreateResamplerList;
- begin
- {$IFDEF XPLAT_GENERICS}
- ResamplerList := TList<TResamplerObj>.Create;
- {$ELSE}
- ResamplerList := TList.Create;
- {$ENDIF}
- end;
- //------------------------------------------------------------------------------
- function GetResampler(id: integer): TResamplerFunction;
- var
- i: integer;
- begin
- result := nil;
- if not Assigned(ResamplerList) then Exit;
- for i := ResamplerList.Count -1 downto 0 do
- if TResamplerObj(ResamplerList[i]).id = id then
- begin
- Result := TResamplerObj(ResamplerList[i]).func;
- Break;
- end;
- end;
- //------------------------------------------------------------------------------
- function RegisterResampler(func: TResamplerFunction; const name: string): integer;
- var
- resampleObj: TResamplerObj;
- begin
- if not Assigned(ResamplerList) then
- CreateResamplerList;
- resampleObj := TResamplerObj.Create;
- Result := ResamplerList.Add(resampleObj) +1;
- resampleObj.id := Result;
- resampleObj.name := name;
- resampleObj.func := func;
- end;
- //------------------------------------------------------------------------------
- procedure GetResamplerList(stringList: TStringList);
- var
- i: integer;
- resampleObj: TResamplerObj;
- begin
- stringList.Clear;
- stringList.Capacity := ResamplerList.Count;
- for i := 0 to ResamplerList.Count -1 do
- begin
- resampleObj := ResamplerList[i];
- stringList.AddObject(resampleObj.name, resampleObj);
- end;
- end;
- //------------------------------------------------------------------------------
- procedure CleanUpResamplerClassList;
- var
- i: integer;
- begin
- if not Assigned(ResamplerList) then Exit;
- for i := ResamplerList.Count -1 downto 0 do
- TResamplerObj(ResamplerList[i]).Free;
- ResamplerList.Free;
- end;
- //------------------------------------------------------------------------------
- initialization
- CreateImageFormatList;
- MakeBlendTables;
- {$IFDEF MSWINDOWS}
- GetScreenScale;
- {$ENDIF}
- finalization
- CleanUpImageFormatClassList;
- CleanUpResamplerClassList;
- end.
|