Img32.pas 136 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569
  1. unit Img32;
  2. (*******************************************************************************
  3. * Author : Angus Johnson *
  4. * Version : 4.8 *
  5. * Date : 10 January 2025 *
  6. * Website : http://www.angusj.com *
  7. * Copyright : Angus Johnson 2019-2025 *
  8. * Purpose : The core module of the Image32 library *
  9. * License : http://www.boost.org/LICENSE_1_0.txt *
  10. *******************************************************************************)
  11. interface
  12. {$I Img32.inc}
  13. uses
  14. Types, SysUtils, Classes,
  15. {$IFDEF MSWINDOWS} Windows,{$ENDIF}
  16. {$IFDEF USING_VCL_LCL}
  17. {$IFDEF USES_NAMESPACES} Vcl.Graphics, Vcl.Forms,
  18. {$ELSE}Graphics, Forms,
  19. {$ENDIF}
  20. {$ENDIF}
  21. {$IFDEF XPLAT_GENERICS}
  22. Generics.Collections, Generics.Defaults, Character,
  23. {$ENDIF}
  24. {$IFDEF UITYPES} UITypes,{$ENDIF} Math;
  25. type
  26. {$IF not declared(SizeInt)} // FPC has SizeInt
  27. {$IF CompilerVersion < 20.0}
  28. SizeInt = Integer; // Delphi 7-2007 can't use NativeInt with "FOR"
  29. SizeUInt = Cardinal; // Delphi 7-2007 can't use NativeUInt with "FOR"
  30. {$ELSE}
  31. SizeInt = NativeInt;
  32. SizeUInt = NativeUInt;
  33. {$IFEND}
  34. {$IFEND}
  35. TRect = Types.TRect;
  36. TColor32 = type Cardinal;
  37. TPointD = record
  38. X, Y: double;
  39. end;
  40. PARGB = ^TARGB;
  41. TARGB = packed record
  42. case boolean of
  43. false: (B: Byte; G: Byte; R: Byte; A: Byte);
  44. true : (Color: TColor32);
  45. end;
  46. TArrayOfARGB = array of TARGB;
  47. const
  48. clNone32 = TColor32($00000000);
  49. clAqua32 = TColor32($FF00FFFF);
  50. clBlack32 = TColor32($FF000000);
  51. clBlue32 = TColor32($FF0000FF);
  52. clFuchsia32 = TColor32($FFFF00FF);
  53. clGray32 = TColor32($FF808080);
  54. clGreen32 = TColor32($FF008000);
  55. clGrey32 = TColor32($FF808080);
  56. clLime32 = TColor32($FF00FF00);
  57. clMaroon32 = TColor32($FF800000);
  58. clNavy32 = TColor32($FF000080);
  59. clOlive32 = TColor32($FF7F7F00);
  60. clOrange32 = TColor32($FFFF7F00);
  61. clPurple32 = TColor32($FF7F00FF);
  62. clRed32 = TColor32($FFFF0000);
  63. clSilver32 = TColor32($FFC0C0C0);
  64. clTeal32 = TColor32($FF007F7F);
  65. clWhite32 = TColor32($FFFFFFFF);
  66. clYellow32 = TColor32($FFFFFF00);
  67. //custom gray colors
  68. clDarkGray32 = TColor32($FF505050);
  69. clDarkGrey32 = TColor32($FF505050);
  70. //clGray32 = TColor32($FF808080);
  71. //clSilver32 = TColor32($FFC0C0C0);
  72. clLiteGray32 = TColor32($FFD3D3D3);
  73. clLiteGrey32 = TColor32($FFD3D3D3);
  74. clPaleGray32 = TColor32($FFE0E0E0);
  75. clPaleGrey32 = TColor32($FFE0E0E0);
  76. clDarkBtn32 = TColor32($FFE8E8E8);
  77. clBtnFace32 = TColor32($FFF0F0F0);
  78. clLiteBtn32 = TColor32($FFF8F8F8);
  79. defaultCompression = -1;
  80. {$IFDEF ZEROBASEDSTR}
  81. {$ZEROBASEDSTRINGS OFF}
  82. {$ENDIF}
  83. RT_BITMAP = PChar(2);
  84. type
  85. {$IFDEF SUPPORTS_POINTERMATH}
  86. // Works for Delphi 2009 and newer. For FPC, POINTERMATH is
  87. // a requirement for negative indices. Otherwise 32bit and 64bit
  88. // code would behave differently since FPC doesn't otherwise
  89. // sign-extend the index variable of type Integer when it's used
  90. // as an array-index into an array with an unsigned index range.
  91. // i32:=-1; i64:=-1 => i32=i64 but @arr[i32] <> @arr[i64]
  92. PByteArray = PByte; // PByte already has PointerMath
  93. {$POINTERMATH ON}
  94. PDoubleArray = ^Double;
  95. PInt64Array = ^Int64;
  96. PColor32Array = ^TColor32;
  97. PARGBArray = ^TARGB;
  98. {$POINTERMATH OFF}
  99. {$ELSE} // Delphi 7-2007
  100. PByteArray = ^TStaticByteArray;
  101. TStaticByteArray = array[0..MaxInt div SizeOf(byte) - 1] of byte;
  102. PDoubleArray = ^TStaticDoubleArray;
  103. TStaticDoubleArray = array[0..MaxInt div SizeOf(double) - 1] of double;
  104. PInt64Array = ^TStaticInt64Array;
  105. TStaticInt64Array = array[0..MaxInt div SizeOf(int64) - 1] of int64;
  106. PColor32Array = ^TStaticColor32Array;
  107. TStaticColor32Array = array[0..MaxInt div SizeOf(TColor32) - 1] of TColor32;
  108. PARGBArray = ^TStaticARGBArray;
  109. TStaticARGBArray = array[0..MaxInt div SizeOf(TARGB) - 1] of TARGB;
  110. {$ENDIF}
  111. TArrayOfByte = array of Byte;
  112. TArrayOfWord = array of WORD;
  113. TArrayOfInteger = array of Integer;
  114. TArrayOfDouble = array of double;
  115. PColor32 = ^TColor32;
  116. TArrayOfColor32 = array of TColor32;
  117. TArrayOfArrayOfColor32 = array of TArrayOfColor32;
  118. TArrayOfString = array of string;
  119. TClipboardPriority = (cpLow, cpMedium, cpHigh);
  120. TImg32Notification = (inStateChange, inDestroy);
  121. //A INotifyRecipient receives change notifications though a property
  122. //interface from a single NotifySender (eg a Font property).
  123. //A NotifySender can send change notificatons to multiple NotifyRecipients
  124. //(eg where multiple object use the same font property). NotifyRecipients can
  125. //still receive change notificatons from mulitple NotifySenders, but it
  126. //must use a separate property for each NotifySender. (Also there's little
  127. //benefit in using INotifySender and INotifyRecipient interfaces where there
  128. //will only be one receiver - eg scroll - scrolling window.)
  129. INotifyRecipient = interface
  130. ['{95F50C62-D321-46A4-A42C-8E9D0E3149B5}']
  131. procedure ReceiveNotification(Sender: TObject; notify: TImg32Notification);
  132. end;
  133. TRecipients = array of INotifyRecipient;
  134. INotifySender = interface
  135. ['{52072382-8B2F-481D-BE0A-E1C0A216B03E}']
  136. procedure AddRecipient(recipient: INotifyRecipient);
  137. procedure DeleteRecipient(recipient: INotifyRecipient);
  138. end;
  139. TInterfacedObj = class(TObject, IInterface)
  140. public
  141. {$IFDEF FPC}
  142. function _AddRef: Integer;
  143. {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
  144. function _Release: Integer;
  145. {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
  146. function QueryInterface(
  147. {$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} iid : tguid;
  148. out obj) : longint;
  149. {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
  150. {$ELSE}
  151. function _AddRef: Integer; stdcall;
  152. function _Release: Integer; stdcall;
  153. function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
  154. {$ENDIF}
  155. end;
  156. TImage32 = class;
  157. TImageFormatClass = class of TImageFormat;
  158. //TImageFormat: Abstract base class for loading and saving images in TImage32.<br>
  159. //This class is overridden to provide support for separate
  160. //file storage formats (eg BMP, PNG, GIF & JPG).<br>
  161. //Derived classes register with TImage32 using TImage32.RegisterImageFormatClass.
  162. TImageFormat = class
  163. public
  164. class function IsValidImageStream(stream: TStream): Boolean; virtual; abstract;
  165. procedure SaveToStream(stream: TStream; img32: TImage32; quality: integer = 0); virtual; abstract;
  166. function SaveToFile(const filename: string; img32: TImage32; quality: integer = 0): Boolean; virtual;
  167. function LoadFromStream(stream: TStream;
  168. img32: TImage32; imgIndex: integer = 0): Boolean; virtual; abstract;
  169. function LoadFromFile(const filename: string; img32: TImage32): Boolean; virtual;
  170. class function GetImageCount(stream: TStream): integer; virtual;
  171. class function CanCopyToClipboard: Boolean; virtual;
  172. class function CopyToClipboard(img32: TImage32): Boolean; virtual; abstract;
  173. class function CanPasteFromClipboard: Boolean; virtual; abstract;
  174. class function PasteFromClipboard(img32: TImage32): Boolean; virtual; abstract;
  175. end;
  176. TBlendFunction = function(bgColor, fgColor: TColor32): TColor32;
  177. TBlendLineFunction = procedure(bgColor, fgColor: PColor32; width: nativeint);
  178. TCompareFunction = function(master, current: TColor32; data: integer): Boolean;
  179. TCompareFunctionEx = function(master, current: TColor32): Byte;
  180. TTileFillStyle = (tfsRepeat, tfsMirrorHorz, tfsMirrorVert, tfsRotate180);
  181. TResamplerFunction = function(img: TImage32; x, y: double): TColor32;
  182. TGrayscaleMode = (gsmSaturation, gsmLinear, gsmColorimetric);
  183. TImage32 = class(TObject)
  184. private
  185. fWidth: integer;
  186. fHeight: Integer;
  187. fResampler: integer;
  188. fIsPremultiplied: Boolean;
  189. fColorCount: integer;
  190. fPixels: TArrayOfColor32;
  191. fOnChange: TNotifyEvent;
  192. fOnResize: TNotifyEvent;
  193. fUpdateCnt: integer;
  194. fAntiAliased: Boolean;
  195. fNotifyBlockCnt: integer;
  196. function GetPixel(x,y: Integer): TColor32;
  197. procedure SetPixel(x,y: Integer; color: TColor32);
  198. function GetIsBlank: Boolean;
  199. function GetIsEmpty: Boolean;
  200. function GetPixelBase: PColor32;
  201. function GetPixelRow(row: Integer): PColor32;
  202. procedure RotateLeft90;
  203. procedure RotateRight90;
  204. procedure Rotate180;
  205. function GetColorCount: Integer;
  206. function GetHasTransparency: Boolean;
  207. function GetBounds: TRect;
  208. function GetMidPoint: TPointD;
  209. protected
  210. procedure ResetColorCount;
  211. function RectHasTransparency(const rec: TRect): Boolean;
  212. function CopyPixels(const rec: TRect): TArrayOfColor32;
  213. //CopyInternal: Internal routine (has no scaling or bounds checking)
  214. procedure CopyInternal(src: TImage32;
  215. const srcRec, dstRec: TRect; blendFunc: TBlendFunction);
  216. procedure CopyInternalLine(src: TImage32;
  217. const srcRec, dstRec: TRect; blendLineFunc: TBlendLineFunction);
  218. function CopyBlendInternal(src: TImage32; const srcRec: TRect; dstRec: TRect;
  219. blendFunc: TBlendFunction = nil; blendLineFunc: TBlendLineFunction = nil): Boolean; overload;
  220. procedure Changed; virtual;
  221. procedure Resized; virtual;
  222. function SetPixels(const newPixels: TArrayOfColor32): Boolean;
  223. property UpdateCount: integer read fUpdateCnt;
  224. public
  225. constructor Create(width: Integer = 0; height: Integer = 0); overload;
  226. //Create(src:array, width, height): Uses the specified array for the pixels.
  227. // Uses src for the pixels without copying it.
  228. constructor Create(const src: TArrayOfColor32; width: Integer; height: Integer); overload;
  229. constructor Create(src: TImage32); overload;
  230. constructor Create(src: TImage32; const srcRec: TRect); overload;
  231. destructor Destroy; override;
  232. //BeginUpdate/EndUpdate: postpones calls to OnChange event (can be nested)
  233. procedure BeginUpdate;
  234. procedure EndUpdate;
  235. //BlockUpdate/UnBlockUpdate: blocks calls to OnChange event (can be nested)
  236. procedure BlockNotify;
  237. procedure UnblockNotify;
  238. procedure Assign(src: TImage32);
  239. procedure AssignTo(dst: TImage32);
  240. procedure AssignSettings(src: TImage32);
  241. //AssignPixelArray: Replaces the content and takes ownership of src.
  242. // Uses src for the pixels without copying it.
  243. procedure AssignPixelArray(const src: TArrayOfColor32; width: Integer; height: Integer);
  244. //SetSize: Erases any current image, and fills with the specified color.
  245. procedure SetSize(newWidth, newHeight: Integer; color: TColor32 = 0);
  246. //Resize: is very similar to Scale()
  247. procedure Resize(newWidth, newHeight: Integer);
  248. procedure ResizeTo(targetImg: TImage32; newWidth, newHeight: Integer);
  249. //ScaleToFit: The image will be scaled proportionally
  250. procedure ScaleToFit(width, height: integer);
  251. //ScaleToFitCentered: The new image will be scaled and also centred
  252. procedure ScaleToFitCentered(width, height: integer); overload;
  253. procedure ScaleToFitCentered(const rect: TRect); overload;
  254. procedure Scale(s: double); overload;
  255. procedure Scale(sx, sy: double); overload;
  256. procedure ScaleTo(targetImg: TImage32; s: double); overload;
  257. procedure ScaleTo(targetImg: TImage32; sx, sy: double); overload;
  258. function Copy(src: TImage32; srcRec, dstRec: TRect): Boolean;
  259. //CopyBlend: Copies part or all of another image (src) on top of the
  260. //existing image. If no blend function is provided, then the function
  261. //will behave exactly as the Copy function above. However, when a blend
  262. //function is specified, that function will determine how the images will
  263. //be blended. If srcRec and dstRec have different widths or heights,
  264. //then the image in srcRec will also be stretched to fit dstRec.
  265. function CopyBlend(src: TImage32; const srcRec, dstRec: TRect;
  266. blendFunc: TBlendFunction = nil): Boolean; overload; {$IFDEF INLINE} inline; {$ENDIF}
  267. function CopyBlend(src: TImage32; const srcRec, dstRec: TRect;
  268. blendLineFunc: TBlendLineFunction): Boolean; overload; {$IFDEF INLINE} inline; {$ENDIF}
  269. {$IFDEF MSWINDOWS}
  270. //CopyFromDC: Copies an image from a Windows device context, erasing
  271. //any current image in TImage32. (eg copying from TBitmap.canvas.handle)
  272. procedure CopyFromDC(srcDc: HDC; const srcRect: TRect);
  273. //CopyToDc: Copies the image into a Windows device context
  274. procedure CopyToDc(dstDc: HDC; x: Integer = 0; y: Integer = 0;
  275. transparent: Boolean = true); overload;
  276. procedure CopyToDc(const srcRect: TRect; dstDc: HDC;
  277. x: Integer = 0; y: Integer = 0; transparent: Boolean = true); overload;
  278. procedure CopyToDc(const srcRect, dstRect: TRect; dstDc: HDC;
  279. transparent: Boolean = true); overload;
  280. {$ENDIF}
  281. {$IF DEFINED(USING_VCL_LCL)}
  282. procedure CopyFromBitmap(bmp: TBitmap);
  283. procedure CopyToBitmap(bmp: TBitmap);
  284. {$IFEND}
  285. function CopyToClipBoard: Boolean;
  286. class function CanPasteFromClipBoard: Boolean;
  287. function PasteFromClipBoard: Boolean;
  288. procedure Crop(const rec: TRect);
  289. //SetBackgroundColor: Assumes the current image is semi-transparent.
  290. procedure SetBackgroundColor(bgColor: TColor32);
  291. procedure Clear(color: TColor32 = 0); overload;
  292. procedure Clear(const rec: TRect; color: TColor32 = 0); overload;
  293. procedure FillRect(const rec: TRect; color: TColor32);
  294. procedure ConvertToBoolMask(reference: TColor32;
  295. tolerance: integer; colorFunc: TCompareFunction;
  296. maskBg: TColor32 = clWhite32; maskFg: TColor32 = clBlack32);
  297. procedure ConvertToAlphaMask(reference: TColor32;
  298. colorFunc: TCompareFunctionEx);
  299. procedure FlipVertical;
  300. procedure FlipHorizontal;
  301. procedure PreMultiply;
  302. //SetAlpha: Sets 'alpha' to the alpha byte of every pixel in the image
  303. procedure SetAlpha(alpha: Byte);
  304. procedure ReduceOpacity(opacity: Byte); overload;
  305. procedure ReduceOpacity(opacity: Byte; rec: TRect); overload;
  306. //SetRGB: Sets the RGB channels leaving the alpha channel unchanged
  307. procedure SetRGB(rgbColor: TColor32); overload;
  308. procedure SetRGB(rgbColor: TColor32; rec: TRect); overload;
  309. //Grayscale: Only changes color channels. The alpha channel is untouched.
  310. procedure Grayscale(mode: TGrayscaleMode = gsmSaturation;
  311. linearAmountPercentage: double = 1.0);
  312. procedure InvertColors;
  313. procedure InvertAlphas;
  314. procedure AdjustHue(percent: Integer); //ie +/- 100%
  315. procedure AdjustLuminance(percent: Integer); //ie +/- 100%
  316. procedure AdjustSaturation(percent: Integer); //ie +/- 100%
  317. function GetOpaqueBounds: TRect;
  318. //CropTransparentPixels: Trims transparent edges until each edge contains
  319. //at least one opaque or semi-opaque pixel.
  320. function CropTransparentPixels: TRect;
  321. procedure Rotate(angleRads: double);
  322. //RotateRect: Rotates part of an image, but also clips those parts of the
  323. //rotated image that fall outside rec. The eraseColor parameter indicates
  324. //the color to fill those uncovered pixels in rec following rotation.
  325. procedure RotateRect(const rec: TRect;
  326. angleRads: double; eraseColor: TColor32 = 0);
  327. procedure Skew(dx,dy: double);
  328. //ScaleAlpha: Scales the alpha byte of every pixel by the specified amount.
  329. procedure ScaleAlpha(scale: double);
  330. class procedure RegisterImageFormatClass(ext: string;
  331. bm32ExClass: TImageFormatClass; clipPriority: TClipboardPriority);
  332. class function GetImageFormatClass(const ext: string): TImageFormatClass; overload;
  333. class function GetImageFormatClass(stream: TStream): TImageFormatClass; overload;
  334. class function IsRegisteredFormat(const ext: string): Boolean;
  335. function SaveToFile(filename: string;
  336. compressionQuality: integer = defaultCompression): Boolean;
  337. function SaveToStream(stream: TStream; const FmtExt: string;
  338. compressionQuality: integer = defaultCompression): Boolean;
  339. function LoadFromFile(const filename: string): Boolean;
  340. function LoadFromStream(stream: TStream; imgIdx: integer = 0): Boolean;
  341. function LoadFromResource(const resName: string; resType: PChar): Boolean;
  342. //properties ...
  343. property AntiAliased: Boolean read fAntiAliased write fAntiAliased;
  344. property Width: Integer read fWidth;
  345. property Height: Integer read fHeight;
  346. property Bounds: TRect read GetBounds;
  347. property IsBlank: Boolean read GetIsBlank;
  348. property IsEmpty: Boolean read GetIsEmpty;
  349. property IsPreMultiplied: Boolean read fIsPremultiplied;
  350. property MidPoint: TPointD read GetMidPoint;
  351. property Pixel[x,y: Integer]: TColor32 read GetPixel write SetPixel;
  352. property Pixels: TArrayOfColor32 read fPixels;
  353. property PixelBase: PColor32 read GetPixelBase;
  354. property PixelRow[row: Integer]: PColor32 read GetPixelRow;
  355. property ColorCount: Integer read GetColorCount;
  356. //HasTransparency: Returns true if any pixel's alpha byte < 255.
  357. property HasTransparency: Boolean read GetHasTransparency;
  358. //Resampler: is used in scaling and rotation transforms
  359. property Resampler: integer read fResampler write fResampler;
  360. property OnChange: TNotifyEvent read fOnChange write fOnChange;
  361. property OnResize: TNotifyEvent read fOnResize write fOnResize;
  362. end;
  363. TImageList32 = class
  364. private
  365. {$IFDEF XPLAT_GENERICS}
  366. fList: TList<TImage32>;
  367. {$ELSE}
  368. fList: TList;
  369. {$ENDIF}
  370. fIsImageOwner: Boolean;
  371. function GetImage(index: integer): TImage32;
  372. procedure SetImage(index: integer; img: TIMage32);
  373. function GetLast: TImage32;
  374. public
  375. constructor Create;
  376. destructor Destroy; override;
  377. procedure Clear;
  378. function Count: integer;
  379. procedure Add(image: TImage32); overload;
  380. function Add(width, height: integer): TImage32; overload;
  381. procedure Insert(index: integer; image: TImage32);
  382. procedure Move(currentIndex, newIndex: integer);
  383. procedure Delete(index: integer);
  384. property Image[index: integer]: TImage32 read GetImage write SetImage; default;
  385. property IsImageOwner: Boolean read fIsImageOwner write fIsImageOwner;
  386. property Last: TImage32 read GetLast;
  387. end;
  388. THsl = packed record
  389. hue : byte;
  390. sat : byte;
  391. lum : byte;
  392. alpha: byte;
  393. end;
  394. PHsl = ^THsl;
  395. TArrayofHSL = array of THsl;
  396. TTriState = (tsUnknown = 0, tsYes = 1, tsChecked = 1, tsNo = 2, tsUnchecked = 2);
  397. PPointD = ^TPointD;
  398. TPathD = array of TPointD; //nb: watch for ambiguity with Clipper.pas
  399. TPathsD = array of TPathD; //nb: watch for ambiguity with Clipper.pas
  400. TArrayOfPathsD = array of TPathsD;
  401. TRectD = {$IFDEF RECORD_METHODS} record {$ELSE} object {$ENDIF}
  402. {$IFNDEF RECORD_METHODS}
  403. Left, Top, Right, Bottom: Double;
  404. function TopLeft: TPointD;
  405. function BottomRight: TPointD;
  406. {$ENDIF}
  407. function IsEmpty: Boolean;
  408. function Width: double;
  409. function Height: double;
  410. //Normalize: Returns True if swapping top & bottom or left & right
  411. function Normalize: Boolean;
  412. function Contains(const Pt: TPoint): Boolean; overload;
  413. function Contains(const Pt: TPointD): Boolean; overload;
  414. function MidPoint: TPointD;
  415. {$IFDEF RECORD_METHODS}
  416. case Integer of
  417. 0: (Left, Top, Right, Bottom: Double);
  418. 1: (TopLeft, BottomRight: TPointD);
  419. {$ENDIF}
  420. end;
  421. {$IFNDEF PBYTE}
  422. PByte = type PChar;
  423. {$ENDIF}
  424. //BLEND FUNCTIONS ( see TImage32.CopyBlend() )
  425. //BlendToOpaque: Blends a semi-transparent image onto an opaque background
  426. function BlendToOpaque(bgColor, fgColor: TColor32): TColor32;
  427. //BlendToAlpha: Blends two semi-transparent images (slower than BlendToOpaque)
  428. function BlendToAlpha(bgColor, fgColor: TColor32): TColor32;
  429. function BlendToAlpha3(bgColor, fgColor: TColor32; blendOpacity: Byte): TColor32;
  430. procedure BlendToAlphaLine(bgColor, fgColor: PColor32; width: nativeint);
  431. //BlendMask: Whereever the mask is, preserves the background
  432. function BlendMask(bgColor, alphaMask: TColor32): TColor32;
  433. procedure BlendMaskLine(bgColor, alphaMask: PColor32; width: nativeint);
  434. function BlendAltMask(bgColor, alphaMask: TColor32): TColor32;
  435. function BlendDifference(color1, color2: TColor32): TColor32;
  436. function BlendSubtract(bgColor, fgColor: TColor32): TColor32;
  437. function BlendLighten(bgColor, fgColor: TColor32): TColor32;
  438. function BlendDarken(bgColor, fgColor: TColor32): TColor32;
  439. function BlendInvertedMask(bgColor, alphaMask: TColor32): TColor32;
  440. procedure BlendInvertedMaskLine(bgColor, alphaMask: PColor32; width: nativeint);
  441. //BlendBlueChannel: typically useful for white color masks
  442. function BlendBlueChannel(bgColor, blueMask: TColor32): TColor32;
  443. procedure BlendBlueChannelLine(bgColor, blueMask: PColor32; width: nativeint);
  444. //COMPARE COLOR FUNCTIONS (ConvertToBoolMask, FloodFill, Vectorize etc.)
  445. function CompareRGB(master, current: TColor32; tolerance: Integer): Boolean;
  446. function CompareHue(master, current: TColor32; tolerance: Integer): Boolean;
  447. function CompareAlpha(master, current: TColor32; tolerance: Integer): Boolean;
  448. //CompareEx COLOR FUNCTIONS (see ConvertToAlphaMask)
  449. function CompareRgbEx(master, current: TColor32): Byte;
  450. function CompareAlphaEx(master, current: TColor32): Byte;
  451. //MISCELLANEOUS FUNCTIONS ...
  452. function GetBoolMask(img: TImage32; reference: TColor32;
  453. compareFunc: TCompareFunction; tolerance: Integer): TArrayOfByte;
  454. function GetByteMask(img: TImage32; reference: TColor32;
  455. compareFunc: TCompareFunctionEx): TArrayOfByte;
  456. function GetColorMask(img: TImage32; reference: TColor32;
  457. compareFunc: TCompareFunction; tolerance: Integer): TArrayOfColor32;
  458. {$IFDEF MSWINDOWS}
  459. //Color32: Converts a Graphics.TColor value into a TColor32 value.
  460. function Color32(rgbColor: Integer): TColor32; overload; {$IFDEF INLINE} inline; {$ENDIF}
  461. procedure FixPalette(p: PARGB; count: integer);
  462. {$ENDIF}
  463. function Color32(a, r, g, b: Byte): TColor32; overload; {$IFDEF INLINE} inline; {$ENDIF}
  464. //RGBColor: Converts a TColor32 value into a COLORREF value
  465. function RGBColor(color: TColor32): Cardinal; {$IFDEF INLINE} inline; {$ENDIF}
  466. function InvertColor(color: TColor32): TColor32; {$IFDEF INLINE} inline; {$ENDIF}
  467. //RgbToHsl: See https://en.wikipedia.org/wiki/HSL_and_HSV
  468. function RgbToHsl(color: TColor32): THsl;
  469. //HslToRgb: See https://en.wikipedia.org/wiki/HSL_and_HSV
  470. function HslToRgb(hslColor: THsl): TColor32;
  471. function AdjustHue(color: TColor32; percent: Integer): TColor32;
  472. function ArrayOfColor32ToArrayHSL(const clr32Arr: TArrayOfColor32): TArrayofHSL;
  473. function ArrayOfHSLToArrayColor32(const hslArr: TArrayofHSL): TArrayOfColor32;
  474. function GetAlpha(color: TColor32): Byte; {$IFDEF INLINE} inline; {$ENDIF}
  475. function PointD(const X, Y: Double): TPointD; overload; {$IFDEF INLINE} inline; {$ENDIF}
  476. function PointD(const pt: TPoint): TPointD; overload; {$IFDEF INLINE} inline; {$ENDIF}
  477. function RectD(left, top, right, bottom: double): TRectD; overload;
  478. function RectD(const rec: TRect): TRectD; overload;
  479. function ClampByte(val: Integer): byte; overload; {$IFDEF INLINE} inline; {$ENDIF}
  480. function ClampByte(val: double): byte; overload; {$IFDEF INLINE} inline; {$ENDIF}
  481. function ClampRange(val, min, max: Integer): Integer; overload;
  482. {$IFDEF INLINE} inline; {$ENDIF}
  483. function ClampRange(val, min, max: double): double; overload;
  484. {$IFDEF INLINE} inline; {$ENDIF}
  485. function IncPColor32(pc: Pointer; cnt: Integer): PColor32; {$IFDEF INLINE} inline; {$ENDIF}
  486. procedure NormalizeAngle(var angle: double; tolerance: double = Pi/360);
  487. function GrayScale(color: TColor32): TColor32; {$IFDEF INLINE} inline; {$ENDIF}
  488. //DPIAware: Useful for DPIAware sizing of images and their container controls.
  489. //It scales values relative to the display's resolution (PixelsPerInch).
  490. //See https://docs.microsoft.com/en-us/windows/desktop/hidpi/high-DPIAware-desktop-application-development-on-windows
  491. function DPIAware(val: Integer): Integer; overload; {$IFDEF INLINE} inline; {$ENDIF}
  492. function DPIAware(val: double): double; overload; {$IFDEF INLINE} inline; {$ENDIF}
  493. function DPIAware(const pt: TPoint): TPoint; overload;
  494. function DPIAware(const pt: TPointD): TPointD; overload;
  495. function DPIAware(const rec: TRect): TRect; overload;
  496. function DPIAware(const rec: TRectD): TRectD; overload;
  497. {$IFDEF MSWINDOWS}
  498. {$IFDEF FPC}
  499. function AlphaBlend(DC: HDC; p2, p3, p4, p5: Integer;
  500. DC6: HDC; p7, p8, p9, p10: Integer; p11: Windows.TBlendFunction): BOOL;
  501. stdcall; external 'msimg32.dll' name 'AlphaBlend';
  502. {$ENDIF}
  503. {$ENDIF}
  504. //CreateResourceStream: handles both numeric and string names and types
  505. function CreateResourceStream(const resName: string;
  506. resType: PChar): TResourceStream;
  507. function GetResampler(id: integer): TResamplerFunction;
  508. function RegisterResampler(func: TResamplerFunction; const name: string): integer;
  509. procedure GetResamplerList(stringList: TStringList);
  510. const
  511. TwoPi = Pi *2;
  512. angle0 = 0;
  513. angle1 = Pi/180;
  514. angle15 = Pi /12;
  515. angle30 = angle15 *2;
  516. angle45 = angle15 *3;
  517. angle60 = angle15 *4;
  518. angle75 = angle15 *5;
  519. angle90 = Pi /2;
  520. angle105 = Pi - angle75;
  521. angle120 = Pi - angle60;
  522. angle135 = Pi - angle45;
  523. angle150 = Pi - angle30;
  524. angle165 = Pi - angle15;
  525. angle180 = Pi;
  526. angle195 = Pi + angle15;
  527. angle210 = Pi + angle30;
  528. angle225 = Pi + angle45;
  529. angle240 = Pi + angle60;
  530. angle255 = Pi + angle75;
  531. angle270 = TwoPi - angle90;
  532. angle285 = TwoPi - angle75;
  533. angle300 = TwoPi - angle60;
  534. angle315 = TwoPi - angle45;
  535. angle330 = TwoPi - angle30;
  536. angle345 = TwoPi - angle15;
  537. angle360 = TwoPi;
  538. div255: Double = 1 / 255;
  539. var
  540. //Resampling function identifiers (initialized in Img32.Resamplers)
  541. rNearestResampler : integer;
  542. rBilinearResampler: integer;
  543. rBicubicResampler : integer;
  544. rWeightedBilinear : integer;
  545. DefaultResampler: Integer = 0;
  546. //Both MulTable and DivTable are used in blend functions
  547. //MulTable[a,b] = a * b / 255
  548. MulTable: array [Byte,Byte] of Byte;
  549. //DivTable[a,b] = a * 255/b (for a &lt;= b)
  550. DivTable: array [Byte,Byte] of Byte;
  551. //Sigmoid: weight byte values towards each end
  552. Sigmoid: array[Byte] of Byte;
  553. dpiAware1 : integer = 1;
  554. DpiAwareOne : double = 1.0;
  555. //AND BECAUSE OLDER DELPHI COMPILERS (OLDER THAN D2006)
  556. //DON'T SUPPORT RECORD METHODS
  557. procedure RectWidthHeight(const rec: TRect; out width, height: Integer); overload;
  558. {$IFDEF INLINE} inline; {$ENDIF}
  559. procedure RectWidthHeight(const rec: TRectD; out width, height: double); overload;
  560. {$IFDEF INLINE} inline; {$ENDIF}
  561. function RectWidth(const rec: TRect): Integer;
  562. {$IFDEF INLINE} inline; {$ENDIF}
  563. function RectHeight(const rec: TRect): Integer;
  564. {$IFDEF INLINE} inline; {$ENDIF}
  565. function IsEmptyRect(const rec: TRect): Boolean; overload;
  566. {$IFDEF INLINE} inline; {$ENDIF}
  567. function IsEmptyRect(const rec: TRectD): Boolean; overload;
  568. {$IFDEF INLINE} inline; {$ENDIF}
  569. function SwapRedBlue(color: TColor32): TColor32; overload;
  570. procedure SwapRedBlue(color: PColor32; count: integer); overload;
  571. function MulBytes(b1, b2: Byte) : Byte;
  572. function __Trunc(Value: Double): Integer; {$IFNDEF CPUX86} {$IFDEF INLINE} inline; {$ENDIF} {$ENDIF}
  573. // NewColor32Array creates a new "array of TColor32". "a" is nil'ed
  574. // before allocating the array. If "count" is zero or negative "a" will
  575. // be nil. If "uninitialized" is True, the memory will not be zero'ed.
  576. procedure NewColor32Array(var a: TArrayOfColor32; count: nativeint;
  577. uninitialized: boolean = False);
  578. procedure NewIntegerArray(var a: TArrayOfInteger; count: nativeint;
  579. uninitialized: boolean = False);
  580. procedure NewByteArray(var a: TArrayOfByte; count: nativeint;
  581. uninitialized: boolean = False);
  582. procedure NewPointDArray(var a: TPathD; count: nativeint;
  583. uninitialized: boolean = False);
  584. // SetLengthUninit changes the dyn. array's length but does not initialize
  585. // the new elements with zeros. It can be used as a replacement for
  586. // SetLength where the zero-initialitation is not required.
  587. procedure SetLengthUninit(var a: TArrayOfColor32; count: nativeint); overload;
  588. procedure SetLengthUninit(var a: TArrayOfInteger; count: nativeint); overload;
  589. procedure SetLengthUninit(var a: TArrayOfByte; count: nativeint); overload;
  590. procedure SetLengthUninit(var a: TPathD; count: nativeint); overload;
  591. implementation
  592. uses
  593. Img32.Vector, Img32.Resamplers, Img32.Transform
  594. {$IF DEFINED(USING_VCL_LCL)}
  595. , Img32.Fmt.BMP
  596. {$ENDIF}
  597. ;
  598. resourcestring
  599. rsImageTooLarge = 'Image32 error: the image is too large.';
  600. rsInvalidImageArrayData = 'Image32 error: the specified pixels array and the size does not match.';
  601. //------------------------------------------------------------------------------
  602. //------------------------------------------------------------------------------
  603. {$IFDEF CPUX86}
  604. const
  605. // Use faster Trunc for x86 code in this unit.
  606. Trunc: function(Value: Double): Integer = __Trunc;
  607. {$ENDIF CPUX86}
  608. type
  609. TImgFmtRec = record
  610. Fmt: string;
  611. SortOrder: TClipboardPriority;
  612. Obj: TImageFormatClass;
  613. end;
  614. PImgFmtRec = ^TImgFmtRec;
  615. TResamplerObj = class
  616. id: integer;
  617. name: string;
  618. func: TResamplerFunction;
  619. end;
  620. PDynArrayRec = ^TDynArrayRec;
  621. {$IFDEF FPC}
  622. tdynarrayindex = sizeint;
  623. TDynArrayRec = packed record
  624. refcount: ptrint;
  625. high: tdynarrayindex;
  626. Data: record end;
  627. end;
  628. {$ELSE}
  629. TDynArrayRec = packed record
  630. {$IFDEF CPU64BITS}
  631. _Padding: Integer;
  632. {$ENDIF}
  633. RefCnt: Integer;
  634. Length: NativeInt;
  635. Data: record end;
  636. end;
  637. {$ENDIF}
  638. var
  639. {$IFDEF XPLAT_GENERICS}
  640. ImageFormatClassList: TList<PImgFmtRec>; //list of supported file extensions
  641. ResamplerList: TList<TResamplerObj>; //list of resampler functions
  642. {$ELSE}
  643. ImageFormatClassList: TList;
  644. ResamplerList: TList;
  645. {$ENDIF}
  646. //------------------------------------------------------------------------------
  647. //------------------------------------------------------------------------------
  648. function NewSimpleDynArray(count: nativeint; elemSize: integer; uninitialized: boolean = False): Pointer;
  649. var
  650. p: PDynArrayRec;
  651. begin
  652. Result := nil;
  653. if (count > 0) and (elemSize > 0) then
  654. begin
  655. if uninitialized then
  656. GetMem(Pointer(p), SizeOf(TDynArrayRec) + count * elemSize)
  657. else
  658. p := AllocMem(SizeOf(TDynArrayRec) + count * elemSize);
  659. {$IFDEF FPC}
  660. p.refcount := 1;
  661. p.high := count -1;
  662. {$ELSE}
  663. p.RefCnt := 1;
  664. p.Length := count;
  665. {$ENDIF}
  666. Result := @p.Data;
  667. end;
  668. end;
  669. //------------------------------------------------------------------------------
  670. function InternSetSimpleDynArrayLengthUninit(a: Pointer; count: nativeint; elemSize: integer): Pointer;
  671. var
  672. p: PDynArrayRec;
  673. oldCount: nativeint;
  674. begin
  675. if a = nil then
  676. Result := NewSimpleDynArray(count, elemSize)
  677. else if (count > 0) and (elemSize > 0) then
  678. begin
  679. p := PDynArrayRec(PByte(a) - SizeOf(TDynArrayRec));
  680. {$IFDEF FPC}
  681. oldCount := p.high + 1;
  682. if p.refcount = 1 then
  683. {$ELSE}
  684. oldCount := p.Length;
  685. if p.RefCnt = 1 then
  686. {$ENDIF}
  687. begin
  688. // There is only one reference to this array and that is "a",
  689. // so we can use ReallocMem to change the array's length.
  690. if oldCount = count then
  691. begin
  692. Result := a;
  693. Exit;
  694. end;
  695. ReallocMem(Pointer(p), SizeOf(TDynArrayRec) + count * elemSize);
  696. end
  697. else
  698. begin
  699. // SetLength makes a copy of the dyn array to get RefCnt=1
  700. GetMem(Pointer(p), SizeOf(TDynArrayRec) + count * elemSize);
  701. if oldCount < 0 then oldCount := 0; // data corruption detected
  702. if oldCount > count then oldCount := count;
  703. Move(a^, p.Data, oldCount * elemSize);
  704. TArrayOfByte(a) := nil; // use a non-managed dyn.array type
  705. end;
  706. {$IFDEF FPC}
  707. p.refcount := 1;
  708. p.high := count -1;
  709. {$ELSE}
  710. p.RefCnt := 1;
  711. p.Length := count;
  712. {$ENDIF}
  713. Result := @p.Data;
  714. end
  715. else
  716. begin
  717. TArrayOfByte(a) := nil; // use a non-managed dyn.array type
  718. Result := nil;
  719. end;
  720. end;
  721. //------------------------------------------------------------------------------
  722. function CanReuseDynArray(a: Pointer; count: nativeint): Boolean;
  723. // returns True if RefCnt=1 and Length=count
  724. begin
  725. //Assert(a <> nil);
  726. a := PByte(a) - SizeOf(TDynArrayRec);
  727. Result :=
  728. {$IFDEF FPC}
  729. (PDynArrayRec(a).refcount = 1) and
  730. (PDynArrayRec(a).high = count - 1);
  731. {$ELSE}
  732. (PDynArrayRec(a).RefCnt = 1) and
  733. (PDynArrayRec(a).Length = count);
  734. {$ENDIF}
  735. end;
  736. //------------------------------------------------------------------------------
  737. procedure NewColor32Array(var a: TArrayOfColor32; count: nativeint; uninitialized: boolean);
  738. begin
  739. {$IF COMPILERVERSION < 16}
  740. SetLength(a, count);
  741. {$ELSE}
  742. if a <> nil then
  743. begin
  744. if uninitialized and CanReuseDynArray(a, count) then Exit;
  745. a := nil;
  746. end;
  747. Pointer(a) := NewSimpleDynArray(count, SizeOf(TColor32), uninitialized);
  748. {$IFEND}
  749. end;
  750. //------------------------------------------------------------------------------
  751. procedure NewIntegerArray(var a: TArrayOfInteger; count: nativeint; uninitialized: boolean);
  752. begin
  753. {$IF COMPILERVERSION < 16}
  754. SetLength(a, count);
  755. {$ELSE}
  756. if a <> nil then
  757. begin
  758. if uninitialized and CanReuseDynArray(a, count) then
  759. Exit;
  760. a := nil;
  761. end;
  762. Pointer(a) := NewSimpleDynArray(count, SizeOf(Integer), uninitialized);
  763. {$IFEND}
  764. end;
  765. //------------------------------------------------------------------------------
  766. procedure NewByteArray(var a: TArrayOfByte; count: nativeint; uninitialized: boolean);
  767. begin
  768. {$IF COMPILERVERSION < 16}
  769. SetLength(a, count);
  770. {$ELSE}
  771. if a <> nil then
  772. begin
  773. if uninitialized and CanReuseDynArray(a, count) then
  774. Exit;
  775. a := nil;
  776. end;
  777. Pointer(a) := NewSimpleDynArray(count, SizeOf(Byte), uninitialized);
  778. {$IFEND}
  779. end;
  780. //------------------------------------------------------------------------------
  781. procedure NewPointDArray(var a: TPathD; count: nativeint; uninitialized: boolean);
  782. begin
  783. {$IF COMPILERVERSION < 16}
  784. SetLength(a, count);
  785. {$ELSE}
  786. if a <> nil then
  787. begin
  788. if uninitialized and CanReuseDynArray(a, count) then
  789. Exit;
  790. a := nil;
  791. end;
  792. Pointer(a) := NewSimpleDynArray(count, SizeOf(TPointD), uninitialized);
  793. {$IFEND}
  794. end;
  795. //------------------------------------------------------------------------------
  796. procedure SetLengthUninit(var a: TArrayOfColor32; count: nativeint);
  797. begin
  798. SetLength(a, count);
  799. // Pointer(a) := InternSetSimpleDynArrayLengthUninit(Pointer(a), count, SizeOf(TColor32));
  800. end;
  801. //------------------------------------------------------------------------------
  802. procedure SetLengthUninit(var a: TArrayOfInteger; count: nativeint);
  803. begin
  804. {$IF COMPILERVERSION < 16}
  805. SetLength(a, count);
  806. {$ELSE}
  807. Pointer(a) := InternSetSimpleDynArrayLengthUninit(Pointer(a), count, SizeOf(Integer));
  808. {$IFEND}
  809. end;
  810. //------------------------------------------------------------------------------
  811. procedure SetLengthUninit(var a: TArrayOfByte; count: nativeint);
  812. begin
  813. {$IF COMPILERVERSION < 16}
  814. SetLength(a, count);
  815. {$ELSE}
  816. Pointer(a) := InternSetSimpleDynArrayLengthUninit(Pointer(a), count, SizeOf(Byte));
  817. {$IFEND}
  818. end;
  819. //------------------------------------------------------------------------------
  820. procedure SetLengthUninit(var a: TPathD; count: nativeint);
  821. begin
  822. {$IF COMPILERVERSION < 16}
  823. SetLength(a, count);
  824. {$ELSE}
  825. Pointer(a) := InternSetSimpleDynArrayLengthUninit(Pointer(a), count, SizeOf(TPointD));
  826. {$IFEND}
  827. end;
  828. //------------------------------------------------------------------------------
  829. procedure CreateImageFormatList;
  830. begin
  831. if Assigned(ImageFormatClassList) then Exit;
  832. {$IFDEF XPLAT_GENERICS}
  833. ImageFormatClassList := TList<PImgFmtRec>.Create;
  834. {$ELSE}
  835. ImageFormatClassList := TList.Create;
  836. {$ENDIF}
  837. end;
  838. //------------------------------------------------------------------------------
  839. function FMod(const ANumerator, ADenominator: Double): Double;
  840. begin
  841. Result := ANumerator - Trunc(ANumerator / ADenominator) * ADenominator;
  842. end;
  843. //------------------------------------------------------------------------------
  844. procedure NormalizeAngle(var angle: double; tolerance: double = Pi/360);
  845. var
  846. aa: double;
  847. begin
  848. angle := FMod(angle, angle360);
  849. if angle < -Angle180 then angle := angle + angle360
  850. else if angle > angle180 then angle := angle - angle360;
  851. aa := Abs(angle);
  852. if aa < tolerance then angle := 0
  853. else if aa > angle180 - tolerance then angle := angle180
  854. else if (aa < angle90 - tolerance) or (aa > angle90 + tolerance) then Exit
  855. else if angle < 0 then angle := -angle90
  856. else angle := angle90;
  857. end;
  858. //------------------------------------------------------------------------------
  859. {$IFDEF CPUX86}
  860. { Trunc with FPU code is very slow because the x87 ControlWord has to be changed
  861. and then there is Delphi's Default8087CW variable that is not thread-safe. }
  862. //__Trunc: An efficient Trunc() algorithm (ie rounds toward zero)
  863. function __Trunc(Value: Double): Integer;
  864. var
  865. exp: integer;
  866. i64: UInt64 absolute Value;
  867. valueBytes: array[0..7] of Byte absolute Value;
  868. begin
  869. // https://en.wikipedia.org/wiki/Double-precision_floating-point_format
  870. // 52 bit fractional value, 11bit ($7FF) exponent, and 1bit sign
  871. Result := 0;
  872. if i64 = 0 then Exit;
  873. exp := Integer(Cardinal(i64 shr 52) and $7FF) - 1023;
  874. // nb: when exp == 1024 then Value == INF or NAN.
  875. if exp < 0 then
  876. Exit
  877. //else if exp > 52 then // ie only for 64bit int results
  878. // Result := ((i64 and $1FFFFFFFFFFFFF) shl (exp - 52)) or (1 shl exp)
  879. //else if exp > 31 then // alternatively, range check for 32bit ints ????
  880. // raise Exception.Create(rsIntegerOverflow)
  881. else
  882. Result := Integer((i64 and $1FFFFFFFFFFFFF) shr (52 - exp)) or (1 shl exp);
  883. // Check for the sign bit without loading Value into the FPU.
  884. if valueBytes[7] and $80 <> 0 then Result := -Result;
  885. end;
  886. //------------------------------------------------------------------------------
  887. {$ELSE}
  888. function __Trunc(Value: Double): Integer;
  889. begin
  890. // Uses fast SSE2 instruction
  891. Result := System.Trunc(Value);
  892. end;
  893. //------------------------------------------------------------------------------
  894. {$ENDIF CPUX86}
  895. function SwapRedBlue(color: TColor32): TColor32;
  896. var
  897. c: array[0..3] of byte absolute color;
  898. r: array[0..3] of byte absolute Result;
  899. begin
  900. result := color;
  901. r[0] := c[2];
  902. r[2] := c[0];
  903. end;
  904. //------------------------------------------------------------------------------
  905. procedure SwapRedBlue(color: PColor32; count: integer);
  906. var
  907. i: integer;
  908. begin
  909. for i := 1 to count do
  910. begin
  911. color^ := SwapRedBlue(color^);
  912. inc(color);
  913. end;
  914. end;
  915. //------------------------------------------------------------------------------
  916. function MulBytes(b1, b2: Byte) : Byte; {$IFDEF INLINE} inline; {$ENDIF}
  917. begin
  918. Result := MulTable[b1, b2];
  919. end;
  920. //------------------------------------------------------------------------------
  921. function ImageFormatClassListSort(item1, item2: Pointer): integer;
  922. var
  923. imgFmtRec1: PImgFmtRec absolute item1;
  924. imgFmtRec2: PImgFmtRec absolute item2;
  925. begin
  926. Result := Integer(imgFmtRec1.SortOrder) - Integer(imgFmtRec2.SortOrder);
  927. end;
  928. //------------------------------------------------------------------------------
  929. function ClampByte(val: Integer): byte;
  930. begin
  931. if val < 0 then result := 0
  932. else if val > 255 then result := 255
  933. else result := val;
  934. end;
  935. //------------------------------------------------------------------------------
  936. function ClampByte(val: double): byte;
  937. begin
  938. if val <= 0 then result := 0
  939. else if val >= 255 then result := 255
  940. else result := Round(val);
  941. end;
  942. //------------------------------------------------------------------------------
  943. //------------------------------------------------------------------------------
  944. // Blend functions - used by TImage32.CopyBlend()
  945. //------------------------------------------------------------------------------
  946. function BlendToOpaque(bgColor, fgColor: TColor32): TColor32;
  947. var
  948. fgA: byte;
  949. fw,bw: PByteArray;
  950. begin
  951. fgA := fgColor shr 24;
  952. if fgA = 0 then Result := bgColor
  953. else if fgA = 255 then Result := fgColor
  954. else
  955. begin
  956. //assuming bg.A = 255, use just fg.A for color weighting
  957. fw := PByteArray(@MulTable[fgA]); //ie weight of foreground
  958. bw := PByteArray(@MulTable[not fgA]); //ie weight of background
  959. Result := $FF000000
  960. or (TColor32(Byte(fw[Byte(fgColor shr 16)] + bw[Byte(bgColor shr 16)])) shl 16)
  961. or (TColor32(Byte(fw[Byte(fgColor shr 8 )] + bw[Byte(bgColor shr 8)])) shl 8)
  962. or (TColor32(Byte(fw[Byte(fgColor )] + bw[Byte(bgColor )])) );
  963. end;
  964. end;
  965. //------------------------------------------------------------------------------
  966. function BlendToAlpha(bgColor, fgColor: TColor32): TColor32;
  967. var
  968. fgWeight: byte;
  969. R, InvR: PByteArray;
  970. bgA, fgA: byte;
  971. begin
  972. //(see https://en.wikipedia.org/wiki/Alpha_compositing)
  973. fgA := fgColor shr 24;
  974. bgA := bgColor shr 24;
  975. if fgA = 0 then Result := bgColor
  976. else if (bgA = 0) or (fgA = 255) then Result := fgColor
  977. else
  978. begin
  979. //combine alphas ...
  980. bgA := not MulTable[not fgA, not bgA];
  981. fgWeight := DivTable[fgA, bgA]; // fgWeight = amount foreground color
  982. // contibutes to the result color
  983. R := PByteArray(@MulTable[fgWeight]); // ie weight of foreground
  984. InvR := PByteArray(@MulTable[not fgWeight]); // ie weight of background
  985. Result := bgA shl 24
  986. or (TColor32(R[Byte(fgColor shr 16)] + InvR[Byte(bgColor shr 16)]) shl 16)
  987. or (TColor32(R[Byte(fgColor shr 8 )] + InvR[Byte(bgColor shr 8)]) shl 8)
  988. or (TColor32(R[Byte(fgColor) ] + InvR[Byte(bgColor) ]) );
  989. end;
  990. end;
  991. //------------------------------------------------------------------------------
  992. function BlendToAlpha3(bgColor, fgColor: TColor32; blendOpacity: Byte): TColor32;
  993. var
  994. fgWeight: byte;
  995. R, InvR: PByteArray;
  996. bgA, fgA: byte;
  997. begin
  998. fgA := MulTable[blendOpacity, fgColor shr 24];
  999. bgA := bgColor shr 24;
  1000. if fgA = 0 then
  1001. Result := bgColor // must do first
  1002. else if (bgA = 0) or (fgA = 255) then
  1003. Result := (fgA shl 24) or (fgColor and $FFFFFF)
  1004. else
  1005. begin
  1006. //combine alphas ...
  1007. bgA := not MulTable[not fgA, not bgA];
  1008. fgWeight := DivTable[fgA, bgA]; // fgWeight = amount foreground color
  1009. // contibutes to the result color
  1010. R := PByteArray(@MulTable[fgWeight]); // ie weight of foreground
  1011. InvR := PByteArray(@MulTable[not fgWeight]); // ie weight of background
  1012. Result := bgA shl 24
  1013. or (TColor32(R[Byte(fgColor shr 16)] + InvR[Byte(bgColor shr 16)]) shl 16)
  1014. or (TColor32(R[Byte(fgColor shr 8 )] + InvR[Byte(bgColor shr 8)]) shl 8)
  1015. or (TColor32(R[Byte(fgColor) ] + InvR[Byte(bgColor) ]) );
  1016. end;
  1017. end;
  1018. //------------------------------------------------------------------------------
  1019. {$RANGECHECKS OFF} // negative array index is used
  1020. {$IFNDEF CPUX64}
  1021. function BlendToAlphaLineX86(bgColorArr, fgColorArr: PColor32Array;
  1022. idx: nativeint): nativeint;
  1023. // Helper function for x86 code, reduces the CPU register pressure in
  1024. // BlendToAlphaLine().
  1025. var
  1026. fgWeight: byte;
  1027. R, InvR: PByteArray;
  1028. fgA, bgA, newBgA: byte;
  1029. fgCol, bgCol: TColor32;
  1030. begin
  1031. fgCol := fgColorArr[idx];
  1032. bgCol := bgColorArr[idx];
  1033. Result := idx; // idx - negative offset into color arrays
  1034. while True do
  1035. begin
  1036. fgA := fgCol shr 24;
  1037. bgA := bgCol shr 24;
  1038. //combine alphas ...
  1039. newBgA := not MulTable[not fgA, not bgA];
  1040. fgWeight := DivTable[fgA, newBgA]; //fgWeight = amount foreground color
  1041. //contibutes to total (result) color
  1042. R := PByteArray(@MulTable[fgWeight]); //ie weight of foreground
  1043. InvR := PByteArray(@MulTable[not fgWeight]); //ie weight of foreground
  1044. while True do
  1045. begin
  1046. bgColorArr[Result] := TColor32(newBgA) shl 24
  1047. or (TColor32(R[Byte(fgCol shr 16)] + InvR[Byte(bgCol shr 16)]) shl 16)
  1048. or (TColor32(R[Byte(fgCol shr 8 )] + InvR[Byte(bgCol shr 8)]) shl 8)
  1049. or (TColor32(R[Byte(fgCol) ] + InvR[Byte(bgCol) ]) );
  1050. inc(Result);
  1051. if Result = 0 then exit;
  1052. fgCol := fgColorArr[Result];
  1053. bgCol := bgColorArr[Result];
  1054. // if both alpha channels are the same in the new pixels, we
  1055. // can use the already calculated R/InvR tables.
  1056. if (fgCol shr 24 <> fgA) or (bgCol shr 24 <> bgA) then break;
  1057. end;
  1058. // return if we have alpha channel values for which we have special code
  1059. if (fgCol and $FF000000 = 0) or (fgCol and $FF000000 = $FF000000) or (bgCol and $FF000000 = 0) then exit;
  1060. end;
  1061. end;
  1062. //------------------------------------------------------------------------------
  1063. {$ENDIF ~CPUX64}
  1064. procedure BlendToAlphaLine(bgColor, fgColor: PColor32; width: nativeint);
  1065. label
  1066. LabelBgAlphaIsZero;
  1067. var
  1068. bgColorArr, fgColorArr: PColor32Array;
  1069. bgCol, fgCol: TColor32;
  1070. {$IFDEF CPUX64}
  1071. fgWeight, fgA, bgA: byte;
  1072. R, InvR: PByteArray;
  1073. {$ENDIF CPUX64}
  1074. begin
  1075. //(see https://en.wikipedia.org/wiki/Alpha_compositing)
  1076. // Use the negative offset trick to only increment the array "width"
  1077. // until it reaches zero. And by offsetting the arrays by "width",
  1078. // the negative "width" values also becomes the index into these arrays.
  1079. inc(bgColor, width);
  1080. inc(fgColor, width);
  1081. width := -width;
  1082. bgColorArr := PColor32Array(bgColor);
  1083. fgColorArr := PColor32Array(fgColor);
  1084. while width < 0 do
  1085. begin
  1086. bgCol := bgColorArr[width];
  1087. fgCol := fgColorArr[width];
  1088. // bgColor.A is zero => change bgColor to fgColor
  1089. while bgCol shr 24 = 0 do
  1090. begin
  1091. LabelBgAlphaIsZero:
  1092. bgColorArr[width] := fgCol;
  1093. inc(width);
  1094. if width = 0 then exit;
  1095. fgCol := fgColorArr[width];
  1096. bgCol := bgColorArr[width];
  1097. end;
  1098. // fgColor.A is zero => don't change bgColor
  1099. while fgCol shr 24 = 0 do
  1100. begin
  1101. // bgColorArr[w] := bgColorArr[w];
  1102. inc(width);
  1103. if width = 0 then exit;
  1104. fgCol := fgColorArr[width];
  1105. bgCol := bgColorArr[width];
  1106. if bgCol shr 24 = 0 then goto LabelBgAlphaIsZero;
  1107. end;
  1108. // fgColor.A is 255 => change bgColor to fgColor
  1109. while fgCol shr 24 = 255 do
  1110. begin
  1111. bgColorArr[width] := fgCol;
  1112. inc(width);
  1113. if width = 0 then exit;
  1114. fgCol := fgColorArr[width];
  1115. bgCol := bgColorArr[width];
  1116. if bgCol shr 24 = 0 then goto LabelBgAlphaIsZero;
  1117. end;
  1118. {$IFDEF CPUX64}
  1119. // x64 has more CPU registers than x86 and calling BlendToAlphaLineX86
  1120. // is slower, so we inline it.
  1121. //combine alphas ...
  1122. fgA := fgCol shr 24;
  1123. bgA := bgCol shr 24;
  1124. bgA := not MulTable[not fgA, not bgA];
  1125. fgWeight := DivTable[fgA, bgA]; //fgWeight = amount foreground color
  1126. //contibutes to total (result) color
  1127. R := PByteArray(@MulTable[fgWeight]); //ie weight of foreground
  1128. InvR := PByteArray(@MulTable[not fgWeight]); //ie weight of foreground
  1129. bgColorArr[width] := TColor32(bgA) shl 24
  1130. or (TColor32(R[Byte(fgCol shr 16)] + InvR[Byte(bgCol shr 16)]) shl 16)
  1131. or (TColor32(R[Byte(fgCol shr 8 )] + InvR[Byte(bgCol shr 8)]) shl 8)
  1132. or (TColor32(R[Byte(fgCol) ] + InvR[Byte(bgCol) ]) );
  1133. inc(width);
  1134. {$ELSE}
  1135. // x86 has not enough CPU registers and the loops above will suffer if we
  1136. // inline the code. So we let the compiler use a "new set" of CPU registers
  1137. // by calling a function.
  1138. width := BlendToAlphaLineX86(bgColorArr, fgColorArr, width);
  1139. {$ENDIF CPUX64}
  1140. end;
  1141. end;
  1142. //------------------------------------------------------------------------------
  1143. {
  1144. // reference implementation
  1145. procedure BlendToAlphaLine(bgColor, fgColor: PColor32; width: nativeint);
  1146. var
  1147. fgWeight: byte;
  1148. R, InvR: PByteArray;
  1149. bgA, fgA: Byte;
  1150. bgColorArr, fgColorArr: PColor32Array;
  1151. bgCol, fgCol: TColor32;
  1152. begin
  1153. //(see https://en.wikipedia.org/wiki/Alpha_compositing)
  1154. // Use the negative offset trick to only increment the array "width"
  1155. // until it reaches zero. And by offsetting the arrays by "width",
  1156. // the negative "width" values also becomes the index into these arrays.
  1157. inc(bgColor, width);
  1158. inc(fgColor, width);
  1159. width := -width;
  1160. bgColorArr := PColor32Array(bgColor);
  1161. fgColorArr := PColor32Array(fgColor);
  1162. while width < 0 do
  1163. begin
  1164. bgCol := bgColorArr[width];
  1165. fgCol := fgColorArr[width];
  1166. bgA := bgCol shr 24;
  1167. if bgA = 0 then bgColorArr[width] := fgCol
  1168. else
  1169. begin
  1170. fgA := fgCol shr 24;
  1171. if fgA > 0 then
  1172. begin
  1173. if fgA = 255 then bgColorArr[width] := fgCol
  1174. else if fgA > 0 then
  1175. begin
  1176. //combine alphas ...
  1177. bgA := not MulTable[not fgA, not bgA];
  1178. fgWeight := DivTable[fgA, bgA]; //fgWeight = amount foreground color
  1179. //contibutes to total (result) color
  1180. R := PByteArray(@MulTable[fgWeight]); //ie weight of foreground
  1181. InvR := PByteArray(@MulTable[not fgWeight]); //ie weight of foreground
  1182. bgColorArr[width] := TColor32(bgA) shl 24
  1183. or (TColor32(R[Byte(fgCol shr 16)] + InvR[Byte(bgCol shr 16)]) shl 16)
  1184. or (TColor32(R[Byte(fgCol shr 8 )] + InvR[Byte(bgCol shr 8)]) shl 8)
  1185. or (TColor32(R[Byte(fgCol) ] + InvR[Byte(bgCol) ]) );
  1186. end;
  1187. end;
  1188. end;
  1189. inc(width);
  1190. end;
  1191. end;}
  1192. {$IFDEF RANGECHECKS_ENABLED}
  1193. {$RANGECHECKS ON}
  1194. {$ENDIF}
  1195. //------------------------------------------------------------------------------
  1196. function BlendMask(bgColor, alphaMask: TColor32): TColor32;
  1197. var
  1198. a: byte;
  1199. begin
  1200. a := MulTable[bgColor shr 24, alphaMask shr 24];
  1201. if a <> 0 then Result := (TColor32(a) shl 24) or (bgColor and $00FFFFFF)
  1202. else Result := 0;
  1203. end;
  1204. //------------------------------------------------------------------------------
  1205. {$RANGECHECKS OFF} // negative array index is used
  1206. procedure BlendMaskLine(bgColor, alphaMask: PColor32; width: nativeint);
  1207. label
  1208. SkipNone32;
  1209. var
  1210. a: byte;
  1211. begin
  1212. // Use the negative offset trick to only increment the array "width"
  1213. // until it reaches zero. And by offsetting the arrays by "width",
  1214. // the negative "width" values also becomes the index into these arrays.
  1215. inc(bgColor, width);
  1216. inc(alphaMask, width);
  1217. width := -width;
  1218. // Handle special cases Alpha=0 or 255 as those are the most
  1219. // common values.
  1220. while width < 0 do
  1221. begin
  1222. // MulTable[0, fgA] -> 0, if bgColor is already 0 => skip
  1223. while PARGBArray(bgColor)[width].Color = 0 do
  1224. begin
  1225. SkipNone32:
  1226. inc(width);
  1227. if width = 0 then exit;
  1228. end;
  1229. a := PARGBArray(bgColor)[width].A;
  1230. // MulTable[0, fgA] -> 0 => replace color with 0
  1231. while a = 0 do
  1232. begin
  1233. PColor32Array(bgColor)[width] := 0;
  1234. inc(width);
  1235. if width = 0 then exit;
  1236. if PARGBArray(bgColor)[width].Color = 0 then
  1237. goto SkipNone32;
  1238. a := PARGBArray(bgColor)[width].A;
  1239. end;
  1240. // MulTable[255, fgA] -> fgA => replace alpha with fgA
  1241. while a = 255 do
  1242. begin
  1243. PARGBArray(bgColor)[width].A := PARGBArray(alphaMask)[width].A;
  1244. inc(width);
  1245. if width = 0 then exit;
  1246. a := PARGBArray(bgColor)[width].A;
  1247. end;
  1248. a := PARGBArray(alphaMask)[width].A;
  1249. // MulTable[bgA, 0] -> 0 => replace color with 0
  1250. while a = 0 do
  1251. begin
  1252. PColor32Array(bgColor)[width] := 0;
  1253. inc(width);
  1254. if width = 0 then exit;
  1255. a := PARGBArray(alphaMask)[width].A;
  1256. end;
  1257. // MulTable[bgA, 255] -> bgA => nothing to do
  1258. while a = 255 do
  1259. begin
  1260. inc(width);
  1261. if width = 0 then exit;
  1262. a := PARGBArray(alphaMask)[width].A;
  1263. end;
  1264. a := MulTable[PARGBArray(bgColor)[width].A, a];
  1265. if a <> 0 then PARGBArray(bgColor)[width].A := a
  1266. else PColor32Array(bgColor)[width] := 0;
  1267. inc(width);
  1268. end;
  1269. end;
  1270. //------------------------------------------------------------------------------
  1271. {
  1272. // reference implementation
  1273. procedure BlendMaskLine(bgColor, alphaMask: PColor32; width: nativeint);
  1274. var
  1275. a: byte;
  1276. begin
  1277. // Use the negative offset trick to only increment the array "width"
  1278. // until it reaches zero. And by offsetting the arrays by "width",
  1279. // the negative "width" values also becomes the index into these arrays.
  1280. inc(bgColor, width);
  1281. inc(alphaMask, width);
  1282. width := -width;
  1283. while width < 0 do
  1284. begin
  1285. a := MulTable[PARGBArray(bgColor)[width].A,
  1286. PARGBArray(alphaMask)[width].A];
  1287. if a = 0 then PColor32Array(bgColor)[width] := 0
  1288. else PARGBArray(bgColor)[width].A := a;
  1289. inc(width);
  1290. end;
  1291. end;}
  1292. {$IFDEF RANGECHECKS_ENABLED}
  1293. {$RANGECHECKS ON}
  1294. {$ENDIF}
  1295. //------------------------------------------------------------------------------
  1296. function BlendAltMask(bgColor, alphaMask: TColor32): TColor32;
  1297. var
  1298. a: byte;
  1299. begin
  1300. a := MulTable[bgColor shr 24, (alphaMask shr 24) xor 255];
  1301. if a <> 0 then Result := (TColor32(a) shl 24) or (bgColor and $00FFFFFF)
  1302. else Result := 0;
  1303. end;
  1304. //------------------------------------------------------------------------------
  1305. function BlendDifference(color1, color2: TColor32): TColor32;
  1306. var
  1307. fgA, bgA: byte;
  1308. begin
  1309. fgA := color2 shr 24;
  1310. bgA := color1 shr 24;
  1311. if fgA = 0 then Result := color1
  1312. else if bgA = 0 then Result := color2
  1313. else
  1314. begin
  1315. Result := TColor32(MulTable[(fgA xor 255), (bgA xor 255)] xor 255) shl 24
  1316. or (TColor32(Abs(Byte(color2 shr 16) - Byte(color1 shr 16))) shl 16)
  1317. or (TColor32(Abs(Byte(color2 shr 8) - Byte(color1 shr 8))) shl 8)
  1318. or (TColor32(Abs(Byte(color2 ) - Byte(color1 ))) );
  1319. end;
  1320. end;
  1321. //------------------------------------------------------------------------------
  1322. function BlendSubtract(bgColor, fgColor: TColor32): TColor32;
  1323. var
  1324. fgA, bgA: byte;
  1325. begin
  1326. fgA := fgColor shr 24;
  1327. bgA := bgColor shr 24;
  1328. if fgA = 0 then Result := bgColor
  1329. else if bgA = 0 then Result := fgColor
  1330. else
  1331. begin
  1332. Result := TColor32(MulTable[(fgA xor 255), (bgA xor 255)] xor 255) shl 24
  1333. or (TColor32(ClampByte(Byte(fgColor shr 16) - Byte(bgColor shr 16))) shl 16)
  1334. or (TColor32(ClampByte(Byte(fgColor shr 8 ) - Byte(bgColor shr 8))) shl 8)
  1335. or (TColor32(ClampByte(Byte(fgColor ) - Byte(bgColor ))) );
  1336. end;
  1337. end;
  1338. //------------------------------------------------------------------------------
  1339. function BlendLighten(bgColor, fgColor: TColor32): TColor32;
  1340. var
  1341. fgA, bgA: byte;
  1342. begin
  1343. fgA := fgColor shr 24;
  1344. bgA := bgColor shr 24;
  1345. if fgA = 0 then Result := bgColor
  1346. else if bgA = 0 then Result := fgColor
  1347. else
  1348. begin
  1349. Result := TColor32(MulTable[(fgA xor 255), (bgA xor 255)] xor 255) shl 24
  1350. or (TColor32(Max(Byte(fgColor shr 16), Byte(bgColor shr 16))) shl 16)
  1351. or (TColor32(Max(Byte(fgColor shr 8 ), Byte(bgColor shr 8))) shl 8)
  1352. or (TColor32(Max(Byte(fgColor ), Byte(bgColor ))) );
  1353. end;
  1354. end;
  1355. //------------------------------------------------------------------------------
  1356. function BlendDarken(bgColor, fgColor: TColor32): TColor32;
  1357. var
  1358. fgA, bgA: byte;
  1359. begin
  1360. fgA := fgColor shr 24;
  1361. bgA := bgColor shr 24;
  1362. if fgA = 0 then Result := bgColor
  1363. else if bgA = 0 then Result := fgColor
  1364. else
  1365. begin
  1366. Result := TColor32(MulTable[(fgA xor 255), (bgA xor 255)] xor 255) shl 24
  1367. or (TColor32(Min(Byte(fgColor shr 16), Byte(bgColor shr 16))) shl 16)
  1368. or (TColor32(Min(Byte(fgColor shr 8 ), Byte(bgColor shr 8))) shl 8)
  1369. or (TColor32(Min(Byte(fgColor ), Byte(bgColor ))) );
  1370. end;
  1371. end;
  1372. //------------------------------------------------------------------------------
  1373. function BlendBlueChannel(bgColor, blueMask: TColor32): TColor32;
  1374. begin
  1375. Result := (bgColor and $00FFFFFF) or
  1376. (TColor32(MulTable[bgColor shr 24, Byte(blueMask)]) shl 24);
  1377. end;
  1378. //------------------------------------------------------------------------------
  1379. function BlendInvertedMask(bgColor, alphaMask: TColor32): TColor32;
  1380. var
  1381. a: byte;
  1382. begin
  1383. a := MulTable[bgColor shr 24, (alphaMask shr 24) xor 255];
  1384. if a < 2 then Result := 0
  1385. else Result := (bgColor and $00FFFFFF) or (TColor32(a) shl 24);
  1386. end;
  1387. //------------------------------------------------------------------------------
  1388. {$RANGECHECKS OFF} // negative array index is used
  1389. procedure BlendBlueChannelLine(bgColor, blueMask: PColor32; width: nativeint);
  1390. begin
  1391. inc(bgColor, width);
  1392. inc(blueMask, width);
  1393. width := -width;
  1394. while width < 0 do
  1395. begin
  1396. PARGBArray(bgColor)[width].A :=
  1397. MulTable[PARGBArray(bgColor)[width].A,
  1398. PARGBArray(blueMask)[width].B];
  1399. inc(width);
  1400. end;
  1401. end;
  1402. //------------------------------------------------------------------------------
  1403. procedure BlendInvertedMaskLine(bgColor, alphaMask: PColor32; width: nativeint);
  1404. var
  1405. a: byte;
  1406. begin
  1407. // Use the negative offset trick to only increment the array "width"
  1408. // until it reaches zero. And by offsetting the arrays by "width",
  1409. // the negative "width" values also becomes the index into these arrays.
  1410. inc(bgColor, width);
  1411. inc(alphaMask, width);
  1412. width := -width;
  1413. while width < 0 do
  1414. begin
  1415. a := MulTable[PARGBArray(bgColor)[width].A,
  1416. PARGBArray(alphaMask)[width].A xor 255];
  1417. if a < 2 then PColor32Array(bgColor)[width] := 0
  1418. else PARGBArray(bgColor)[width].A := a;
  1419. inc(width);
  1420. end;
  1421. end;
  1422. {$IFDEF RANGECHECKS_ENABLED}
  1423. {$RANGECHECKS ON}
  1424. {$ENDIF}
  1425. //------------------------------------------------------------------------------
  1426. // Compare functions (see ConvertToBoolMask, FloodFill & Vectorize)
  1427. //------------------------------------------------------------------------------
  1428. function CompareRGB(master, current: TColor32; tolerance: Integer): Boolean;
  1429. var
  1430. mast: TARGB absolute master;
  1431. curr: TARGB absolute current;
  1432. begin
  1433. if curr.A < $80 then
  1434. Result := false
  1435. else if (master and $FFFFFF) = (current and $FFFFFF) then
  1436. Result := true
  1437. else if tolerance = 0 then
  1438. Result := false
  1439. else result :=
  1440. (Abs(curr.R - mast.R) <= tolerance) and
  1441. (Abs(curr.G - mast.G) <= tolerance) and
  1442. (Abs(curr.B - mast.B) <= tolerance);
  1443. end;
  1444. //------------------------------------------------------------------------------
  1445. function CompareAlpha(master, current: TColor32; tolerance: Integer): Boolean;
  1446. var
  1447. mast: TARGB absolute master;
  1448. curr: TARGB absolute current;
  1449. begin
  1450. if mast.A = curr.A then Result := true
  1451. else if tolerance = 0 then Result := false
  1452. else result := Abs(curr.A - mast.A) <= tolerance;
  1453. end;
  1454. //------------------------------------------------------------------------------
  1455. function CompareHue(master, current: TColor32; tolerance: Integer): Boolean;
  1456. var
  1457. curr, mast: THsl;
  1458. val: Integer;
  1459. begin
  1460. if TARGB(current).A < $80 then
  1461. begin
  1462. Result := false;
  1463. Exit;
  1464. end;
  1465. curr := RgbToHsl(current);
  1466. mast := RgbToHsl(master);
  1467. if curr.hue > mast.hue then
  1468. begin
  1469. val := curr.hue - mast.hue;
  1470. if val > 127 then val := mast.hue - curr.hue + 255;
  1471. end else
  1472. begin
  1473. val := mast.hue - curr.hue;
  1474. if val > 127 then val := curr.hue - mast.hue + 255;
  1475. end;
  1476. result := val <= tolerance;
  1477. end;
  1478. //------------------------------------------------------------------------------
  1479. // CompareEx functions (see ConvertToAlphaMask)
  1480. //------------------------------------------------------------------------------
  1481. function CompareRgbEx(master, current: TColor32): Byte;
  1482. var
  1483. mast: TARGB absolute master;
  1484. curr: TARGB absolute current;
  1485. res: Cardinal;
  1486. begin
  1487. res := Sqr(mast.R - curr.R) + Sqr(mast.G - curr.G) + Sqr(mast.B - curr.B);
  1488. if res >= 65025 then result := 255
  1489. else result := Round(Sqrt(res));
  1490. end;
  1491. //------------------------------------------------------------------------------
  1492. function CompareAlphaEx(master, current: TColor32): Byte;
  1493. var
  1494. mast: TARGB absolute master;
  1495. curr: TARGB absolute current;
  1496. begin
  1497. Result := abs(mast.A - curr.A);
  1498. end;
  1499. //------------------------------------------------------------------------------
  1500. // Miscellaneous functions ...
  1501. //------------------------------------------------------------------------------
  1502. function IsAlphaChar(c: Char): Boolean;
  1503. begin
  1504. Result := ((c >= 'A') and (c <= 'Z')) or ((c >= 'a') and (c <= 'z'));
  1505. end;
  1506. //------------------------------------------------------------------------------
  1507. procedure RectWidthHeight(const rec: TRect; out width, height: Integer);
  1508. begin
  1509. width := rec.Right - rec.Left;
  1510. height := rec.Bottom - rec.Top;
  1511. end;
  1512. //------------------------------------------------------------------------------
  1513. procedure RectWidthHeight(const rec: TRectD; out width, height: double);
  1514. begin
  1515. width := rec.Right - rec.Left;
  1516. height := rec.Bottom - rec.Top;
  1517. end;
  1518. //------------------------------------------------------------------------------
  1519. function RectWidth(const rec: TRect): Integer;
  1520. begin
  1521. Result := rec.Right - rec.Left;
  1522. end;
  1523. //------------------------------------------------------------------------------
  1524. function RectHeight(const rec: TRect): Integer;
  1525. begin
  1526. Result := rec.Bottom - rec.Top;
  1527. end;
  1528. //------------------------------------------------------------------------------
  1529. function IsEmptyRect(const rec: TRect): Boolean;
  1530. begin
  1531. Result := (rec.Right <= rec.Left) or (rec.Bottom <= rec.Top);
  1532. end;
  1533. //------------------------------------------------------------------------------
  1534. function IsEmptyRect(const rec: TRectD): Boolean;
  1535. begin
  1536. Result := (rec.Right <= rec.Left) or (rec.Bottom <= rec.Top);
  1537. end;
  1538. //------------------------------------------------------------------------------
  1539. function InvertColor(color: TColor32): TColor32;
  1540. begin
  1541. Result := color xor $00FFFFFF;
  1542. end;
  1543. //------------------------------------------------------------------------------
  1544. function GetAlpha(color: TColor32): Byte;
  1545. begin
  1546. Result := Byte(color shr 24);
  1547. end;
  1548. //------------------------------------------------------------------------------
  1549. function RGBColor(color: TColor32): Cardinal;
  1550. var
  1551. c : TARGB absolute color;
  1552. res: TARGB absolute Result;
  1553. begin
  1554. res.R := c.B; res.G := c.G; res.B := c.R; res.A := 0;
  1555. end;
  1556. //------------------------------------------------------------------------------
  1557. function Color32(a, r, g, b: Byte): TColor32;
  1558. var
  1559. res: TARGB absolute Result;
  1560. begin
  1561. res.A := a; res.R := r; res.G := g; res.B := b;
  1562. end;
  1563. //------------------------------------------------------------------------------
  1564. {$IFDEF MSWINDOWS}
  1565. function Color32(rgbColor: Integer): TColor32;
  1566. var
  1567. res: TARGB absolute Result;
  1568. begin
  1569. if rgbColor < 0 then
  1570. result := GetSysColor(rgbColor and $FFFFFF) else
  1571. result := rgbColor;
  1572. res.A := res.B; res.B := res.R; res.R := res.A; //byte swap
  1573. res.A := 255;
  1574. end;
  1575. //------------------------------------------------------------------------------
  1576. procedure FixPalette(p: PARGB; count: integer);
  1577. var
  1578. i: integer;
  1579. begin
  1580. for i := 1 to count do
  1581. begin
  1582. p.Color := SwapRedBlue(p.Color);
  1583. p.A := 255;
  1584. inc(p);
  1585. end;
  1586. end;
  1587. //------------------------------------------------------------------------------
  1588. function Get32bitBitmapInfoHeader(width, height: Integer): TBitmapInfoHeader;
  1589. begin
  1590. FillChar(Result, sizeof(Result), #0);
  1591. Result.biSize := sizeof(TBitmapInfoHeader);
  1592. Result.biWidth := width;
  1593. Result.biHeight := height;
  1594. Result.biPlanes := 1;
  1595. Result.biBitCount := 32;
  1596. Result.biSizeImage := width * Abs(height) * SizeOf(TColor32);
  1597. Result.biCompression := BI_RGB;
  1598. end;
  1599. //------------------------------------------------------------------------------
  1600. {$ENDIF}
  1601. function DPIAware(val: Integer): Integer;
  1602. begin
  1603. result := Round(val * DpiAwareOne);
  1604. end;
  1605. //------------------------------------------------------------------------------
  1606. function DPIAware(val: double): double;
  1607. begin
  1608. result := val * DpiAwareOne;
  1609. end;
  1610. //------------------------------------------------------------------------------
  1611. function DPIAware(const pt: TPoint): TPoint;
  1612. begin
  1613. result.X := Round(pt.X * DpiAwareOne);
  1614. result.Y := Round(pt.Y * DpiAwareOne);
  1615. end;
  1616. //------------------------------------------------------------------------------
  1617. function DPIAware(const pt: TPointD): TPointD;
  1618. begin
  1619. result.X := pt.X * DpiAwareOne;
  1620. result.Y := pt.Y * DpiAwareOne;
  1621. end;
  1622. //------------------------------------------------------------------------------
  1623. function DPIAware(const rec: TRect): TRect;
  1624. begin
  1625. result.Left := Round(rec.Left * DpiAwareOne);
  1626. result.Top := Round(rec.Top * DpiAwareOne);
  1627. result.Right := Round(rec.Right * DpiAwareOne);
  1628. result.Bottom := Round(rec.Bottom * DpiAwareOne);
  1629. end;
  1630. //------------------------------------------------------------------------------
  1631. function DPIAware(const rec: TRectD): TRectD;
  1632. begin
  1633. result.Left := rec.Left * DpiAwareOne;
  1634. result.Top := rec.Top * DpiAwareOne;
  1635. result.Right := rec.Right * DpiAwareOne;
  1636. result.Bottom := rec.Bottom * DpiAwareOne;
  1637. end;
  1638. //------------------------------------------------------------------------------
  1639. function GrayScale(color: TColor32): TColor32;
  1640. var
  1641. c: TARGB absolute color;
  1642. r: TARGB absolute result;
  1643. g: Byte;
  1644. begin
  1645. //https://www.w3.org/TR/AERT/#color-contrast
  1646. g := ClampByte(0.299 * c.R + 0.587 * c.G + 0.114 * c.B);
  1647. r.A := c.A;
  1648. r.R := g; r.G := g; r.B := g;
  1649. end;
  1650. //------------------------------------------------------------------------------
  1651. function ClampRange(val, min, max: Integer): Integer;
  1652. begin
  1653. if val < min then result := min
  1654. else if val > max then result := max
  1655. else result := val;
  1656. end;
  1657. //------------------------------------------------------------------------------
  1658. function ClampRange(val, min, max: double): double;
  1659. begin
  1660. if val < min then result := min
  1661. else if val > max then result := max
  1662. else result := val;
  1663. end;
  1664. //------------------------------------------------------------------------------
  1665. procedure ScaleRect(var rec: TRect; x,y: double);
  1666. begin
  1667. rec.Right := rec.Left + Round((rec.Right - rec.Left) * x);
  1668. rec.Bottom := rec.Top + Round((rec.Bottom - rec.Top) * y);
  1669. end;
  1670. //------------------------------------------------------------------------------
  1671. function IncPColor32(pc: Pointer; cnt: Integer): PColor32;
  1672. begin
  1673. result := PColor32(PByte(pc) + cnt * SizeOf(TColor32));
  1674. end;
  1675. //------------------------------------------------------------------------------
  1676. function PointD(const X, Y: Double): TPointD;
  1677. begin
  1678. Result.X := X;
  1679. Result.Y := Y;
  1680. end;
  1681. //------------------------------------------------------------------------------
  1682. function PointD(const pt: TPoint): TPointD;
  1683. begin
  1684. Result.X := pt.X;
  1685. Result.Y := pt.Y;
  1686. end;
  1687. //------------------------------------------------------------------------------
  1688. function GetBoolMask(img: TImage32; reference: TColor32;
  1689. compareFunc: TCompareFunction; tolerance: Integer): TArrayOfByte;
  1690. var
  1691. i: integer;
  1692. pa: PByte;
  1693. pc: PColor32;
  1694. begin
  1695. result := nil;
  1696. if not assigned(img) or img.IsEmpty then Exit;
  1697. if not Assigned(compareFunc) then compareFunc := CompareRGB;
  1698. NewByteArray(Result, img.Width * img.Height, True);
  1699. pa := @Result[0];
  1700. pc := img.PixelBase;
  1701. for i := 0 to img.Width * img.Height -1 do
  1702. begin
  1703. if compareFunc(reference, pc^, tolerance) then
  1704. {$IFDEF PBYTE}
  1705. pa^ := 1 else
  1706. pa^ := 0;
  1707. {$ELSE}
  1708. pa^ := #1 else
  1709. pa^ := #0;
  1710. {$ENDIF}
  1711. inc(pc); inc(pa);
  1712. end;
  1713. end;
  1714. //------------------------------------------------------------------------------
  1715. function GetColorMask(img: TImage32; reference: TColor32;
  1716. compareFunc: TCompareFunction; tolerance: Integer): TArrayOfColor32;
  1717. var
  1718. i: integer;
  1719. pDstPxl: PColor32;
  1720. pSrcPxl: PColor32;
  1721. begin
  1722. result := nil;
  1723. if not assigned(img) or img.IsEmpty then Exit;
  1724. if not Assigned(compareFunc) then compareFunc := CompareRGB;
  1725. NewColor32Array(Result, img.Width * img.Height, True);
  1726. pDstPxl := @Result[0];
  1727. pSrcPxl := img.PixelBase;
  1728. for i := 0 to img.Width * img.Height -1 do
  1729. begin
  1730. if compareFunc(reference, pSrcPxl^, tolerance) then
  1731. pDstPxl^ := clWhite32 else
  1732. pDstPxl^ := clBlack32;
  1733. inc(pSrcPxl); inc(pDstPxl);
  1734. end;
  1735. end;
  1736. //------------------------------------------------------------------------------
  1737. function GetAlphaEx(master, current: TColor32): Byte;
  1738. {$IFDEF INLINE} inline; {$ENDIF}
  1739. var
  1740. curr: TARGB absolute current;
  1741. begin
  1742. result := curr.A; //nb: 'master' is ignored
  1743. end;
  1744. //------------------------------------------------------------------------------
  1745. function GetByteMask(img: TImage32; reference: TColor32;
  1746. compareFunc: TCompareFunctionEx): TArrayOfByte;
  1747. var
  1748. i: integer;
  1749. pa: PByte;
  1750. pc: PColor32;
  1751. begin
  1752. result := nil;
  1753. if not assigned(img) or img.IsEmpty then Exit;
  1754. if not Assigned(compareFunc) then compareFunc := GetAlphaEx;
  1755. NewByteArray(Result, img.Width * img.Height, True);
  1756. pa := @Result[0];
  1757. pc := img.PixelBase;
  1758. for i := 0 to img.Width * img.Height -1 do
  1759. begin
  1760. {$IFDEF PBYTE}
  1761. pa^ := compareFunc(reference, pc^);
  1762. {$ELSE}
  1763. pa^ := Char(compareFunc(reference, pc^));
  1764. {$ENDIF}
  1765. inc(pc); inc(pa);
  1766. end;
  1767. end;
  1768. //------------------------------------------------------------------------------
  1769. function RgbToHsl(color: TColor32): THsl;
  1770. var
  1771. rgba: TARGB absolute color;
  1772. hsl: THsl absolute result;
  1773. r,g,b: byte;
  1774. maxRGB, minRGB, mAdd, mSub: Integer;
  1775. begin
  1776. //https://en.wikipedia.org/wiki/HSL_and_HSV and
  1777. //http://en.wikipedia.org/wiki/HSL_color_space
  1778. {$IF DEFINED(ANDROID)}
  1779. color := SwapRedBlue(color);
  1780. {$IFEND}
  1781. r := rgba.R; g := rgba.G; b := rgba.B;
  1782. maxRGB := Max(r, Max(g, b));
  1783. minRGB := Min(r, Min(g, b));
  1784. mAdd := maxRGB + minRGB;
  1785. hsl.lum := mAdd shr 1;
  1786. hsl.alpha := rgba.A;
  1787. if maxRGB = minRGB then
  1788. begin
  1789. hsl.hue := 0; //hsl.hue is undefined when gray
  1790. hsl.sat := 0;
  1791. Exit;
  1792. end;
  1793. mSub := maxRGB - minRGB;
  1794. if mAdd <= 255 then
  1795. hsl.sat := DivTable[mSub, mAdd] else
  1796. hsl.sat := DivTable[mSub, 511 - mAdd];
  1797. mSub := mSub * 6;
  1798. if r = maxRGB then
  1799. begin
  1800. if g >= b then
  1801. hsl.hue := (g - b) * 255 div mSub else
  1802. hsl.hue := 255 - ((b - g) * 255 div mSub);
  1803. end
  1804. else if G = maxRGB then
  1805. begin
  1806. if b > r then
  1807. hsl.hue := 85 + (b - r) * 255 div mSub else
  1808. hsl.hue := 85 - (r - b) * 255 div mSub;
  1809. end else
  1810. begin
  1811. if r > g then
  1812. hsl.hue := 170 + (r - g) * 255 div mSub else
  1813. hsl.hue := 170 - (g - r) * 255 div mSub;
  1814. end;
  1815. end;
  1816. //------------------------------------------------------------------------------
  1817. function HslToRgb(hslColor: THsl): TColor32;
  1818. var
  1819. rgba: TARGB absolute result;
  1820. hsl: THsl absolute hslColor;
  1821. c, x, m, a: Integer;
  1822. begin
  1823. //formula from https://www.rapidtables.com/convert/color/hsl-to-rgb.html
  1824. c := ((255 - abs(2 * hsl.lum - 255)) * hsl.sat) shr 8;
  1825. a := 252 - (hsl.hue mod 85) * 6;
  1826. x := (c * (255 - abs(a))) shr 8;
  1827. m := hsl.lum - c shr 1{div 2}; // Delphi's 64bit compiler can't optimize this
  1828. rgba.A := hsl.alpha;
  1829. case (hsl.hue * 6) shr 8 of
  1830. 0: begin rgba.R := c + m; rgba.G := x + m; rgba.B := 0 + m; end;
  1831. 1: begin rgba.R := x + m; rgba.G := c + m; rgba.B := 0 + m; end;
  1832. 2: begin rgba.R := 0 + m; rgba.G := c + m; rgba.B := x + m; end;
  1833. 3: begin rgba.R := 0 + m; rgba.G := x + m; rgba.B := c + m; end;
  1834. 4: begin rgba.R := x + m; rgba.G := 0 + m; rgba.B := c + m; end;
  1835. 5: begin rgba.R := c + m; rgba.G := 0 + m; rgba.B := x + m; end;
  1836. end;
  1837. {$IF DEFINED(ANDROID)}
  1838. Result := SwapRedBlue(Result);
  1839. {$IFEND}
  1840. end;
  1841. //------------------------------------------------------------------------------
  1842. function AdjustHue(color: TColor32; percent: Integer): TColor32;
  1843. var
  1844. hsl: THsl;
  1845. begin
  1846. percent := percent mod 100;
  1847. if percent < 0 then inc(percent, 100);
  1848. hsl := RgbToHsl(color);
  1849. hsl.hue := (hsl.hue + Round(percent*255/100)) mod 256;
  1850. result := HslToRgb(hsl);
  1851. end;
  1852. //------------------------------------------------------------------------------
  1853. function ArrayOfColor32ToArrayHSL(const clr32Arr: TArrayOfColor32): TArrayofHSL;
  1854. var
  1855. i, len: Integer;
  1856. begin
  1857. len := length(clr32Arr);
  1858. setLength(result, len);
  1859. for i := 0 to len -1 do
  1860. result[i] := RgbToHsl(clr32Arr[i]);
  1861. end;
  1862. //------------------------------------------------------------------------------
  1863. function ArrayOfHSLToArrayColor32(const hslArr: TArrayofHSL): TArrayOfColor32;
  1864. var
  1865. i, len: Integer;
  1866. begin
  1867. len := length(hslArr);
  1868. NewColor32Array(result, len, True);
  1869. for i := 0 to len -1 do
  1870. result[i] := HslToRgb(hslArr[i]);
  1871. end;
  1872. //------------------------------------------------------------------------------
  1873. function NameToId(Name: PChar): Longint;
  1874. begin
  1875. if Name < Pointer(30) then
  1876. begin
  1877. Result := Longint(Name)
  1878. end else
  1879. begin
  1880. if Name^ = '#' then inc(Name);
  1881. Result := StrToIntDef(Name, 0);
  1882. if Result > 65535 then Result := 0;
  1883. end;
  1884. end;
  1885. //------------------------------------------------------------------------------
  1886. function CreateResourceStream(const resName: string;
  1887. resType: PChar): TResourceStream;
  1888. var
  1889. nameId, typeId: Cardinal;
  1890. begin
  1891. Result := nil;
  1892. typeId := NameToId(resType);
  1893. if (typeId > 0) then resType := PChar(typeId)
  1894. else if (resType = 'BMP') then resType := RT_BITMAP;
  1895. nameId := NameToId(PChar(resName));
  1896. if nameId > 0 then
  1897. begin
  1898. if FindResource(hInstance, PChar(nameId), resType) <> 0 then
  1899. Result := TResourceStream.CreateFromID(hInstance, nameId, resType);
  1900. end else
  1901. begin
  1902. if FindResource(hInstance, PChar(resName), resType) <> 0 then
  1903. Result := TResourceStream.Create(hInstance, PChar(resName), resType);
  1904. end;
  1905. end;
  1906. //------------------------------------------------------------------------------
  1907. // TRectD methods (and helpers)
  1908. //------------------------------------------------------------------------------
  1909. function TRectD.IsEmpty: Boolean;
  1910. begin
  1911. result := (right <= left) or (bottom <= top);
  1912. end;
  1913. //------------------------------------------------------------------------------
  1914. function TRectD.Width: double;
  1915. begin
  1916. result := Max(0, right - left);
  1917. end;
  1918. //------------------------------------------------------------------------------
  1919. function TRectD.Height: double;
  1920. begin
  1921. result := Max(0, bottom - top);
  1922. end;
  1923. //------------------------------------------------------------------------------
  1924. function TRectD.MidPoint: TPointD;
  1925. begin
  1926. Result.X := (Right + Left)/2;
  1927. Result.Y := (Bottom + Top)/2;
  1928. end;
  1929. //------------------------------------------------------------------------------
  1930. {$IFNDEF RECORD_METHODS}
  1931. function TRectD.TopLeft: TPointD;
  1932. begin
  1933. Result.X := Left;
  1934. Result.Y := Top;
  1935. end;
  1936. //------------------------------------------------------------------------------
  1937. function TRectD.BottomRight: TPointD;
  1938. begin
  1939. Result.X := Right;
  1940. Result.Y := Bottom;
  1941. end;
  1942. //------------------------------------------------------------------------------
  1943. {$ENDIF}
  1944. function TRectD.Normalize: Boolean;
  1945. var
  1946. d: double;
  1947. begin
  1948. Result := false;
  1949. if Left > Right then
  1950. begin
  1951. d := Left;
  1952. Left := Right;
  1953. Right := d;
  1954. Result := True;
  1955. end;
  1956. if Top > Bottom then
  1957. begin
  1958. d := Top;
  1959. Top := Bottom;
  1960. Bottom := d;
  1961. Result := True;
  1962. end;
  1963. end;
  1964. //------------------------------------------------------------------------------
  1965. function TRectD.Contains(const Pt: TPoint): Boolean;
  1966. begin
  1967. Result := (pt.X >= Left) and (pt.X < Right) and
  1968. (pt.Y >= Top) and (pt.Y < Bottom);
  1969. end;
  1970. //------------------------------------------------------------------------------
  1971. function TRectD.Contains(const Pt: TPointD): Boolean;
  1972. begin
  1973. Result := (pt.X >= Left) and (pt.X < Right) and
  1974. (pt.Y >= Top) and (pt.Y < Bottom);
  1975. end;
  1976. //------------------------------------------------------------------------------
  1977. function RectD(left, top, right, bottom: double): TRectD;
  1978. begin
  1979. result.Left := left;
  1980. result.Top := top;
  1981. result.Right := right;
  1982. result.Bottom := bottom;
  1983. end;
  1984. //------------------------------------------------------------------------------
  1985. function RectD(const rec: TRect): TRectD;
  1986. begin
  1987. with rec do
  1988. begin
  1989. result.Left := left;
  1990. result.Top := top;
  1991. result.Right := right;
  1992. result.Bottom := bottom;
  1993. end;
  1994. end;
  1995. //------------------------------------------------------------------------------
  1996. // TImage32 methods
  1997. //------------------------------------------------------------------------------
  1998. constructor TImage32.Create(width: Integer; height: Integer);
  1999. begin
  2000. fAntiAliased := true;
  2001. fResampler := DefaultResampler;
  2002. fwidth := Max(0, width);
  2003. fheight := Max(0, height);
  2004. NewColor32Array(fPixels, fwidth * fheight);
  2005. end;
  2006. //------------------------------------------------------------------------------
  2007. constructor TImage32.Create(const src: TArrayOfColor32; width: Integer; height: Integer);
  2008. begin
  2009. fAntiAliased := true;
  2010. fResampler := DefaultResampler;
  2011. width := Max(0, width);
  2012. height := Max(0, height);
  2013. if Length(src) <> width * height then
  2014. raise Exception.Create(rsInvalidImageArrayData);
  2015. fWidth := width;
  2016. fHeight := height;
  2017. fPixels := src;
  2018. end;
  2019. //------------------------------------------------------------------------------
  2020. constructor TImage32.Create(src: TImage32);
  2021. begin
  2022. Assign(src);
  2023. end;
  2024. //------------------------------------------------------------------------------
  2025. constructor TImage32.Create(src: TImage32; const srcRec: TRect);
  2026. var
  2027. rec: TRect;
  2028. begin
  2029. fAntiAliased := src.AntiAliased;
  2030. fResampler := src.fResampler;
  2031. types.IntersectRect(rec, src.Bounds, srcRec);
  2032. RectWidthHeight(rec, fWidth, fHeight);
  2033. if (fWidth = 0) or (fheight = 0) then Exit;
  2034. fPixels := src.CopyPixels(rec);
  2035. end;
  2036. //------------------------------------------------------------------------------
  2037. destructor TImage32.Destroy;
  2038. begin
  2039. fPixels := nil;
  2040. inherited;
  2041. end;
  2042. //------------------------------------------------------------------------------
  2043. class function TImage32.IsRegisteredFormat(const ext: string): Boolean;
  2044. begin
  2045. result := Assigned(TImage32.GetImageFormatClass(ext));
  2046. end;
  2047. //------------------------------------------------------------------------------
  2048. class procedure TImage32.RegisterImageFormatClass(ext: string;
  2049. bm32ExClass: TImageFormatClass; clipPriority: TClipboardPriority);
  2050. var
  2051. i: Integer;
  2052. imgFmtRec: PImgFmtRec;
  2053. isNewFormat: Boolean;
  2054. begin
  2055. if not Assigned(ImageFormatClassList) then CreateImageFormatList;
  2056. if (ext = '') or (ext = '.') then Exit;
  2057. if (ext[1] = '.') then Delete(ext, 1,1);
  2058. if not IsAlphaChar(ext[1]) then Exit;
  2059. isNewFormat := true;
  2060. // avoid duplicates but still allow overriding
  2061. for i := 0 to imageFormatClassList.count -1 do
  2062. begin
  2063. imgFmtRec := PImgFmtRec(imageFormatClassList[i]);
  2064. if SameText(imgFmtRec.Fmt, ext) then
  2065. begin
  2066. imgFmtRec.Obj := bm32ExClass; // replace prior class
  2067. if imgFmtRec.SortOrder = clipPriority then
  2068. Exit; // re-sorting isn't required
  2069. imgFmtRec.SortOrder := clipPriority;
  2070. isNewFormat := false;
  2071. Break;
  2072. end;
  2073. end;
  2074. if isNewFormat then
  2075. begin
  2076. new(imgFmtRec);
  2077. imgFmtRec.Fmt := ext;
  2078. imgFmtRec.SortOrder := clipPriority;
  2079. imgFmtRec.Obj := bm32ExClass;
  2080. ImageFormatClassList.Add(imgFmtRec);
  2081. end;
  2082. // Sort with lower priority before higher.
  2083. // Sorting here is arguably inefficient but, with so few
  2084. // entries, this inefficiency will be inconsequential.
  2085. {$IFDEF XPLAT_GENERICS}
  2086. ImageFormatClassList.Sort(TComparer<PImgFmtRec>.Construct(
  2087. function(const imgFmtRec1, imgFmtRec2: PImgFmtRec): Integer
  2088. begin
  2089. Result := Integer(imgFmtRec1.SortOrder) - Integer(imgFmtRec2.SortOrder);
  2090. end));
  2091. {$ELSE}
  2092. ImageFormatClassList.Sort(ImageFormatClassListSort);
  2093. {$ENDIF}
  2094. end;
  2095. //------------------------------------------------------------------------------
  2096. class function TImage32.GetImageFormatClass(const ext: string): TImageFormatClass;
  2097. var
  2098. i: Integer;
  2099. pattern: string;
  2100. imgFmtRec: PImgFmtRec;
  2101. begin
  2102. Result := nil;
  2103. pattern := ext;
  2104. if (pattern = '') or (pattern = '.') then Exit;
  2105. if pattern[1] = '.' then Delete(pattern, 1,1);
  2106. //try for highest priority first
  2107. for i := imageFormatClassList.count -1 downto 0 do
  2108. begin
  2109. imgFmtRec := PImgFmtRec(imageFormatClassList[i]);
  2110. if not SameText(imgFmtRec.Fmt, pattern) then Continue;
  2111. Result := imgFmtRec.Obj;
  2112. break;
  2113. end;
  2114. end;
  2115. //------------------------------------------------------------------------------
  2116. class function TImage32.GetImageFormatClass(stream: TStream): TImageFormatClass;
  2117. var
  2118. i: integer;
  2119. begin
  2120. Result := nil;
  2121. for i := 0 to imageFormatClassList.count -1 do
  2122. with PImgFmtRec(imageFormatClassList[i])^ do
  2123. if Obj.IsValidImageStream(stream) then
  2124. begin
  2125. Result := Obj;
  2126. break;
  2127. end;
  2128. end;
  2129. //------------------------------------------------------------------------------
  2130. procedure TImage32.Assign(src: TImage32);
  2131. begin
  2132. if assigned(src) then
  2133. src.AssignTo(self);
  2134. end;
  2135. //------------------------------------------------------------------------------
  2136. procedure TImage32.AssignTo(dst: TImage32);
  2137. begin
  2138. if dst = self then Exit;
  2139. dst.BeginUpdate;
  2140. try
  2141. dst.AssignSettings(Self);
  2142. try
  2143. dst.fPixels := System.Copy(fPixels, 0, Length(fPixels));
  2144. dst.fWidth := fWidth;
  2145. dst.fHeight := fHeight;
  2146. dst.Resized;
  2147. except
  2148. dst.SetSize(0,0);
  2149. end;
  2150. finally
  2151. dst.EndUpdate;
  2152. end;
  2153. dst.fColorCount := fColorCount; // dst.EndUpdate called ResetColorCount
  2154. end;
  2155. //------------------------------------------------------------------------------
  2156. procedure TImage32.AssignSettings(src: TImage32);
  2157. begin
  2158. if assigned(src) and (src <> Self) then
  2159. begin
  2160. BeginUpdate;
  2161. try
  2162. fResampler := src.fResampler;
  2163. fIsPremultiplied := src.fIsPremultiplied;
  2164. fAntiAliased := src.fAntiAliased;
  2165. ResetColorCount;
  2166. finally
  2167. EndUpdate;
  2168. end;
  2169. end;
  2170. end;
  2171. //------------------------------------------------------------------------------
  2172. procedure TImage32.AssignPixelArray(const src: TArrayOfColor32; width: Integer; height: Integer);
  2173. var
  2174. wasResized: Boolean;
  2175. begin
  2176. width := Max(0, width);
  2177. height := Max(0, height);
  2178. if Length(src) <> width * height then
  2179. raise Exception.Create(rsInvalidImageArrayData);
  2180. wasResized := (fWidth <> width) or (fHeight <> height);
  2181. BeginUpdate;
  2182. try
  2183. fWidth := width;
  2184. fHeight := height;
  2185. fPixels := src;
  2186. finally
  2187. EndUpdate;
  2188. end;
  2189. if wasResized then
  2190. Resized;
  2191. end;
  2192. //------------------------------------------------------------------------------
  2193. procedure TImage32.Changed;
  2194. begin
  2195. if fUpdateCnt <> 0 then Exit;
  2196. ResetColorCount;
  2197. if Assigned(fOnChange) then fOnChange(Self);
  2198. end;
  2199. //------------------------------------------------------------------------------
  2200. procedure TImage32.Resized;
  2201. begin
  2202. if fUpdateCnt <> 0 then Exit
  2203. else if Assigned(fOnResize) then fOnResize(Self)
  2204. else Changed;
  2205. end;
  2206. //------------------------------------------------------------------------------
  2207. function TImage32.SetPixels(const newPixels: TArrayOfColor32): Boolean;
  2208. var
  2209. len: integer;
  2210. begin
  2211. len := Length(newPixels);
  2212. Result := (len > 0)and (len = Width * height);
  2213. if Result then fPixels := System.Copy(newPixels, 0, len);
  2214. end;
  2215. //------------------------------------------------------------------------------
  2216. procedure TImage32.BeginUpdate;
  2217. begin
  2218. if fNotifyBlockCnt > 0 then Exit;
  2219. inc(fUpdateCnt);
  2220. end;
  2221. //------------------------------------------------------------------------------
  2222. procedure TImage32.EndUpdate;
  2223. begin
  2224. if fNotifyBlockCnt > 0 then Exit;
  2225. dec(fUpdateCnt);
  2226. if fUpdateCnt = 0 then Changed;
  2227. end;
  2228. //------------------------------------------------------------------------------
  2229. procedure TImage32.BlockNotify;
  2230. begin
  2231. inc(fNotifyBlockCnt);
  2232. inc(fUpdateCnt);
  2233. end;
  2234. //------------------------------------------------------------------------------
  2235. procedure TImage32.UnblockNotify;
  2236. begin
  2237. dec(fNotifyBlockCnt);
  2238. dec(fUpdateCnt);
  2239. end;
  2240. //------------------------------------------------------------------------------
  2241. procedure TImage32.SetBackgroundColor(bgColor: TColor32);
  2242. var
  2243. i: Integer;
  2244. pc: PColor32;
  2245. begin
  2246. pc := Pixelbase;
  2247. for i := 0 to high(fPixels) do
  2248. begin
  2249. pc^ := BlendToOpaque(bgColor, pc^);
  2250. inc(pc);
  2251. end;
  2252. Changed;
  2253. end;
  2254. //------------------------------------------------------------------------------
  2255. procedure TImage32.Clear(color: TColor32);
  2256. var
  2257. i: Integer;
  2258. pc: PColor32;
  2259. begin
  2260. fIsPremultiplied := false;
  2261. if IsEmpty then Exit;
  2262. if color = clNone32 then
  2263. FillChar(fPixels[0], Width * Height * SizeOf(TColor32), 0)
  2264. else
  2265. begin
  2266. pc := PixelBase;
  2267. for i := 0 to Width * Height -1 do
  2268. begin
  2269. pc^ := color;
  2270. inc(pc);
  2271. end;
  2272. end;
  2273. Changed;
  2274. end;
  2275. //------------------------------------------------------------------------------
  2276. procedure TImage32.Clear(const rec: TRect; color: TColor32 = 0);
  2277. begin
  2278. FillRect(rec, color);
  2279. end;
  2280. //------------------------------------------------------------------------------
  2281. procedure TImage32.FillRect(const rec: TRect; color: TColor32);
  2282. var
  2283. i,j, rw, w: Integer;
  2284. c: PColor32;
  2285. r: TRect;
  2286. begin
  2287. Types.IntersectRect(r, rec, bounds);
  2288. if IsEmptyRect(r) then Exit;
  2289. rw := RectWidth(r);
  2290. w := Width;
  2291. c := @Pixels[r.Top * w + r.Left];
  2292. if (color = 0) and (w = rw) then
  2293. FillChar(c^, (r.Bottom - r.Top) * rw * SizeOf(TColor32), 0)
  2294. else if rw = 1 then
  2295. begin
  2296. for i := r.Top to r.Bottom -1 do
  2297. begin
  2298. c^ := color;
  2299. inc(c, w);
  2300. end;
  2301. end
  2302. else if (color = 0) and (rw > 15) then
  2303. begin
  2304. for i := r.Top to r.Bottom -1 do
  2305. begin
  2306. FillChar(c^, rw * SizeOf(TColor32), 0);
  2307. inc(c, w);
  2308. end;
  2309. end
  2310. else
  2311. begin
  2312. for i := r.Top to r.Bottom -1 do
  2313. begin
  2314. for j := 1 to rw do
  2315. begin
  2316. c^ := color;
  2317. inc(c);
  2318. end;
  2319. inc(c, w - rw);
  2320. end;
  2321. end;
  2322. Changed;
  2323. end;
  2324. //------------------------------------------------------------------------------
  2325. procedure TImage32.ResetColorCount;
  2326. begin
  2327. fColorCount := 0;
  2328. end;
  2329. //------------------------------------------------------------------------------
  2330. {$RANGECHECKS OFF} // negative array index is used
  2331. function TImage32.RectHasTransparency(const rec: TRect): Boolean;
  2332. var
  2333. i, j, rw: Integer;
  2334. lineByteOffset: nativeint;
  2335. c: PARGB;
  2336. r: TRect;
  2337. begin
  2338. Result := True;
  2339. Types.IntersectRect(r, rec, bounds);
  2340. if IsEmptyRect(r) then Exit;
  2341. rw := RectWidth(r);
  2342. c := @Pixels[r.Top * Width + r.Left];
  2343. if rw = Width then // we can use one loop
  2344. begin
  2345. i := (r.Bottom - r.Top) * rw;
  2346. inc(c, i);
  2347. i := -i;
  2348. while i < 0 do
  2349. begin
  2350. if PARGBArray(c)[i].A < 254 then Exit;
  2351. inc(i);
  2352. end;
  2353. end
  2354. else
  2355. begin
  2356. lineByteOffset := (Width - rw) * SizeOf(TColor32);
  2357. for i := r.Top to r.Bottom -1 do
  2358. begin
  2359. for j := 1 to rw do
  2360. begin
  2361. if c.A < 254 then Exit;
  2362. inc(c);
  2363. end;
  2364. inc(PByte(c), lineByteOffset);
  2365. end;
  2366. end;
  2367. Result := False;
  2368. end;
  2369. {$IFDEF RANGECHECKS_ENABLED}
  2370. {$RANGECHECKS ON}
  2371. {$ENDIF}
  2372. //------------------------------------------------------------------------------
  2373. procedure CheckBlendFill(pc: PColor32; color: TColor32);
  2374. {$IFDEF INLINE} inline; {$ENDIF}
  2375. begin
  2376. if not assigned(pc) then Exit;
  2377. pc^ := BlendToAlpha(pc^, color);
  2378. end;
  2379. //------------------------------------------------------------------------------
  2380. function TImage32.CopyPixels(const rec: TRect): TArrayOfColor32;
  2381. var
  2382. i, clipW, w,h: Integer;
  2383. pSrc, pDst, pDst2: PColor32;
  2384. recClipped: TRect;
  2385. begin
  2386. RectWidthHeight(rec, w,h);
  2387. NewColor32Array(result, w * h, True);
  2388. if w * h = 0 then Exit;
  2389. Types.IntersectRect(recClipped, rec, Bounds);
  2390. //if recClipped is wholely outside the bounds of the image ...
  2391. if IsEmptyRect(recClipped) then
  2392. begin
  2393. //rec is considered valid even when completely outside the image bounds,
  2394. //and so when that happens we simply return a fully transparent image ...
  2395. FillChar(Result[0], w * h * SizeOf(TColor32), 0);
  2396. Exit;
  2397. end;
  2398. //if recClipped is wholely within the bounds of the image ...
  2399. if RectsEqual(recClipped, rec) then
  2400. begin
  2401. pDst := @Result[0];
  2402. pSrc := @fPixels[recClipped.Top * Width + rec.Left];
  2403. for i := recClipped.Top to recClipped.Bottom -1 do
  2404. begin
  2405. Move(pSrc^, pDst^, w * SizeOf(TColor32));
  2406. inc(pSrc, Width); inc(pDst, w);
  2407. end;
  2408. Exit;
  2409. end;
  2410. //a part of 'rec' must be outside the bounds of the image ...
  2411. pDst := @Result[0];
  2412. for i := rec.Top to -1 do
  2413. begin
  2414. FillChar(pDst^, w * SizeOf(TColor32), 0);
  2415. inc(pDst, w);
  2416. end;
  2417. pSrc := @fPixels[recClipped.Top * Width + Max(0,rec.Left)];
  2418. if (rec.Left < 0) or (rec.Right > Width) then
  2419. begin
  2420. clipW := RectWidth(recClipped);
  2421. pDst2 := IncPColor32(pDst, -Min(0, rec.Left));
  2422. for i := recClipped.Top to recClipped.Bottom -1 do
  2423. begin
  2424. //when rec.left < 0 or rec.right > width it's simplest to
  2425. //start with a prefilled row of transparent pixels
  2426. FillChar(pDst^, w * SizeOf(TColor32), 0);
  2427. Move(pSrc^, pDst2^, clipW * SizeOf(TColor32));
  2428. inc(pDst, w); inc(pDst2, w); inc(pSrc, Width);
  2429. end;
  2430. end else
  2431. begin
  2432. //things are simpler when there's no part of 'rec' is
  2433. //outside the image, at least not on the left or right sides ...
  2434. for i := recClipped.Top to recClipped.Bottom -1 do
  2435. begin
  2436. Move(pSrc^, pDst^, w * SizeOf(TColor32));
  2437. inc(pSrc, Width); inc(pDst, w);
  2438. end;
  2439. end;
  2440. for i := Height to rec.Bottom -1 do
  2441. begin
  2442. FillChar(pDst^, w * SizeOf(TColor32), 0);
  2443. inc(pDst, w);
  2444. end;
  2445. end;
  2446. //------------------------------------------------------------------------------
  2447. procedure TImage32.Crop(const rec: TRect);
  2448. var
  2449. newPixels: TArrayOfColor32;
  2450. w,h: integer;
  2451. begin
  2452. RectWidthHeight(rec, w, h);
  2453. if (w = Width) and (h = Height) then Exit;
  2454. newPixels := CopyPixels(rec); // get pixels **before** resizing
  2455. BlockNotify;
  2456. try
  2457. SetSize(w, h);
  2458. if not IsEmptyRect(rec) then
  2459. fPixels := newPixels;
  2460. finally
  2461. UnblockNotify;
  2462. end;
  2463. Resized;
  2464. end;
  2465. //------------------------------------------------------------------------------
  2466. function TImage32.GetBounds: TRect;
  2467. begin
  2468. result := Types.Rect(0, 0, Width, Height);
  2469. end;
  2470. //------------------------------------------------------------------------------
  2471. function TImage32.GetMidPoint: TPointD;
  2472. begin
  2473. Result := PointD(fWidth * 0.5, fHeight * 0.5);
  2474. end;
  2475. //------------------------------------------------------------------------------
  2476. procedure TImage32.SetSize(newWidth, newHeight: Integer; color: TColor32);
  2477. begin
  2478. //very large images are usually due to a bug
  2479. if (newWidth > 20000) or (newHeight > 20000) then
  2480. raise Exception.Create(rsImageTooLarge);
  2481. fwidth := Max(0, newWidth);
  2482. fheight := Max(0, newHeight);
  2483. fPixels := nil; //forces a blank image
  2484. NewColor32Array(fPixels, fwidth * fheight, True);
  2485. fIsPremultiplied := false;
  2486. BlockNotify;
  2487. Clear(color);
  2488. UnblockNotify;
  2489. Resized;
  2490. end;
  2491. //------------------------------------------------------------------------------
  2492. procedure TImage32.Resize(newWidth, newHeight: Integer);
  2493. begin
  2494. ResizeTo(Self, newWidth, newHeight);
  2495. end;
  2496. //------------------------------------------------------------------------------
  2497. procedure TImage32.ResizeTo(targetImg: TImage32; newWidth, newHeight: Integer);
  2498. begin
  2499. if (newWidth <= 0) or (newHeight <= 0) then
  2500. begin
  2501. targetImg.SetSize(0, 0);
  2502. Exit;
  2503. end
  2504. else if (newWidth = fwidth) and (newHeight = fheight) then
  2505. begin
  2506. if targetImg <> Self then targetImg.Assign(Self);
  2507. Exit
  2508. end
  2509. else if IsEmpty then
  2510. begin
  2511. targetImg.SetSize(newWidth, newHeight);
  2512. Exit;
  2513. end;
  2514. targetImg.BlockNotify;
  2515. try
  2516. if targetImg.fResampler <= rNearestResampler then
  2517. NearestNeighborResize(Self, targetImg, newWidth, newHeight)
  2518. else
  2519. ResamplerResize(Self, targetImg, newWidth, newHeight);
  2520. finally
  2521. targetImg.UnblockNotify;
  2522. end;
  2523. targetImg.Resized;
  2524. end;
  2525. //------------------------------------------------------------------------------
  2526. procedure TImage32.Scale(s: double);
  2527. begin
  2528. Scale(s, s);
  2529. end;
  2530. //------------------------------------------------------------------------------
  2531. procedure TImage32.ScaleTo(targetImg: TImage32; s: double);
  2532. begin
  2533. ScaleTo(targetImg, s, s);
  2534. end;
  2535. //------------------------------------------------------------------------------
  2536. procedure TImage32.Scale(sx, sy: double);
  2537. begin
  2538. if (sx > 0) and (sy > 0) then
  2539. Resize(Round(width * sx), Round(height * sy));
  2540. end;
  2541. //------------------------------------------------------------------------------
  2542. procedure TImage32.ScaleTo(targetImg: TImage32; sx, sy: double);
  2543. begin
  2544. if (sx > 0) and (sy > 0) then
  2545. ResizeTo(targetImg, Round(width * sx), Round(height * sy));
  2546. end;
  2547. //------------------------------------------------------------------------------
  2548. procedure TImage32.ScaleToFit(width, height: integer);
  2549. var
  2550. sx, sy: double;
  2551. begin
  2552. if IsEmpty or (width < 2) or (height < 2) then Exit;
  2553. sx := width / self.Width;
  2554. sy := height / self.Height;
  2555. if sx <= sy then
  2556. Scale(sx) else
  2557. Scale(sy);
  2558. end;
  2559. //------------------------------------------------------------------------------
  2560. procedure TImage32.ScaleToFitCentered(const rect: TRect);
  2561. begin
  2562. ScaleToFitCentered(RectWidth(rect), RectHeight(rect));
  2563. end;
  2564. //------------------------------------------------------------------------------
  2565. procedure TImage32.ScaleToFitCentered(width, height: integer);
  2566. var
  2567. sx, sy: double;
  2568. tmp: TImage32;
  2569. rec2: TRect;
  2570. begin
  2571. if IsEmpty or (width <= 0) or (height <= 0) or
  2572. ((width = self.Width) and (height = self.Height)) then Exit;
  2573. sx := width / self.Width;
  2574. sy := height / self.Height;
  2575. BlockNotify;
  2576. try
  2577. if sx <= sy then
  2578. begin
  2579. Scale(sx);
  2580. if height = self.Height then Exit;
  2581. rec2 := Bounds;
  2582. TranslateRect(rec2, 0, (height - self.Height) div 2);
  2583. tmp := TImage32.Create(self);
  2584. try
  2585. SetSize(width, height);
  2586. CopyInternal(tmp, tmp.Bounds, rec2, nil);
  2587. finally
  2588. tmp.Free;
  2589. end;
  2590. end else
  2591. begin
  2592. Scale(sy);
  2593. if width = self.Width then Exit;
  2594. rec2 := Bounds;
  2595. TranslateRect(rec2, (width - self.Width) div 2, 0);
  2596. tmp := TImage32.Create(self);
  2597. try
  2598. SetSize(width, height);
  2599. CopyInternal(tmp, tmp.Bounds, rec2, nil);
  2600. finally
  2601. tmp.Free;
  2602. end;
  2603. end;
  2604. finally
  2605. UnblockNotify;
  2606. end;
  2607. Resized;
  2608. end;
  2609. //------------------------------------------------------------------------------
  2610. procedure TImage32.RotateLeft90;
  2611. var
  2612. x,y, xx: Integer;
  2613. src, dst: PColor32;
  2614. tmp: TImage32;
  2615. begin
  2616. if IsEmpty then Exit;
  2617. BeginUpdate;
  2618. tmp := TImage32.create(Self);
  2619. try
  2620. SetSize(Height, Width);
  2621. xx := (width - 1) * Height;
  2622. dst := PixelBase;
  2623. for y := 0 to Height -1 do
  2624. begin
  2625. src := @tmp.Pixels[xx + y];
  2626. for x := 0 to Width -1 do
  2627. begin
  2628. dst^ := src^;
  2629. inc(dst); dec(src, Height);
  2630. end;
  2631. end;
  2632. finally
  2633. tmp.Free;
  2634. EndUpdate;
  2635. end;
  2636. end;
  2637. //------------------------------------------------------------------------------
  2638. procedure TImage32.RotateRight90;
  2639. var
  2640. x,y: Integer;
  2641. src, dst: PColor32;
  2642. tmp: TImage32;
  2643. begin
  2644. if IsEmpty then Exit;
  2645. BeginUpdate;
  2646. tmp := TImage32.create(Self);
  2647. try
  2648. SetSize(Height, Width);
  2649. dst := PixelBase;
  2650. for y := 0 to Height -1 do
  2651. begin
  2652. src := @tmp.Pixels[Height -1 - y];
  2653. for x := 0 to Width -1 do
  2654. begin
  2655. dst^ := src^;
  2656. inc(dst); inc(src, Height);
  2657. end;
  2658. end;
  2659. finally
  2660. tmp.Free;
  2661. EndUpdate;
  2662. end;
  2663. end;
  2664. //------------------------------------------------------------------------------
  2665. procedure TImage32.Rotate180;
  2666. var
  2667. x,y: Integer;
  2668. src, dst: PColor32;
  2669. tmp: TImage32;
  2670. begin
  2671. if IsEmpty then Exit;
  2672. tmp := TImage32.create(Self);
  2673. try
  2674. dst := PixelBase;
  2675. src := @tmp.Pixels[Width * Height -1];
  2676. for y := 0 to Height -1 do
  2677. begin
  2678. for x := 0 to Width -1 do
  2679. begin
  2680. dst^ := src^;
  2681. inc(dst); dec(src);
  2682. end;
  2683. end;
  2684. finally
  2685. tmp.Free;
  2686. end;
  2687. Changed;
  2688. end;
  2689. //------------------------------------------------------------------------------
  2690. function TImage32.GetColorCount: Integer;
  2691. var
  2692. allColors: PByteArray;
  2693. i: Integer;
  2694. c: PColor32;
  2695. const
  2696. cube256 = 256 * 256 * 256;
  2697. begin
  2698. result := 0;
  2699. if IsEmpty then Exit;
  2700. if fColorCount > 0 then
  2701. begin
  2702. result := fColorCount;
  2703. Exit;
  2704. end;
  2705. //because 'allColors' uses quite a chunk of memory, it's
  2706. //allocated on the heap rather than the stack
  2707. allColors := AllocMem(cube256); //nb: zero initialized
  2708. try
  2709. c := PixelBase;
  2710. for i := 0 to Width * Height -1 do
  2711. begin
  2712. //ignore colors with signifcant transparency
  2713. if GetAlpha(c^) > $80 then
  2714. allColors[c^ and $FFFFFF] := 1;
  2715. inc(c);
  2716. end;
  2717. for i := 0 to cube256 -1 do
  2718. if allColors[i] = 1 then inc(Result);
  2719. finally
  2720. FreeMem(allColors);
  2721. end;
  2722. fColorCount := Result; //avoids repeating the above unnecessarily
  2723. end;
  2724. //------------------------------------------------------------------------------
  2725. function TImage32.GetHasTransparency: Boolean;
  2726. var
  2727. i: Integer;
  2728. pc: PARGB;
  2729. begin
  2730. result := true;
  2731. If IsEmpty then Exit;
  2732. pc := PARGB(PixelBase);
  2733. for i := 0 to Width * Height -1 do
  2734. begin
  2735. if pc.A < 128 then Exit;
  2736. inc(pc);
  2737. end;
  2738. result := false;
  2739. end;
  2740. //------------------------------------------------------------------------------
  2741. function TImage32.SaveToFile(filename: string; compressionQuality: integer): Boolean;
  2742. var
  2743. fileFormatClass: TImageFormatClass;
  2744. begin
  2745. result := false;
  2746. if IsEmpty or (length(filename) < 5) then Exit;
  2747. //use the process's current working directory if no path supplied ...
  2748. if ExtractFilePath(filename) = '' then
  2749. filename := GetCurrentDir + '\'+ filename;
  2750. fileFormatClass := GetImageFormatClass(ExtractFileExt(filename));
  2751. if assigned(fileFormatClass) then
  2752. with fileFormatClass.Create do
  2753. try
  2754. result := SaveToFile(filename, self, compressionQuality);
  2755. finally
  2756. free;
  2757. end;
  2758. end;
  2759. //------------------------------------------------------------------------------
  2760. function TImage32.SaveToStream(stream: TStream;
  2761. const FmtExt: string; compressionQuality: integer): Boolean;
  2762. var
  2763. fileFormatClass: TImageFormatClass;
  2764. begin
  2765. result := false;
  2766. fileFormatClass := GetImageFormatClass(FmtExt);
  2767. if assigned(fileFormatClass) then
  2768. with fileFormatClass.Create do
  2769. try
  2770. SaveToStream(stream, self, compressionQuality);
  2771. result := true;
  2772. finally
  2773. free;
  2774. end;
  2775. end;
  2776. //------------------------------------------------------------------------------
  2777. function TImage32.LoadFromFile(const filename: string): Boolean;
  2778. var
  2779. stream: TFileStream;
  2780. begin
  2781. Result := false;
  2782. if not FileExists(filename) then Exit;
  2783. stream := TFileStream.Create(filename, fmOpenRead or fmShareDenyNone);
  2784. try
  2785. result := LoadFromStream(stream);
  2786. finally
  2787. stream.Free;
  2788. end;
  2789. end;
  2790. //------------------------------------------------------------------------------
  2791. function TImage32.LoadFromStream(stream: TStream; imgIdx: integer): Boolean;
  2792. var
  2793. ifc: TImageFormatClass;
  2794. begin
  2795. ifc := GetImageFormatClass(stream);
  2796. Result := Assigned(ifc);
  2797. if not Result then Exit;
  2798. with ifc.Create do
  2799. try
  2800. result := LoadFromStream(stream, self, imgIdx);
  2801. finally
  2802. free;
  2803. end;
  2804. end;
  2805. //------------------------------------------------------------------------------
  2806. function TImage32.GetPixel(x, y: Integer): TColor32;
  2807. begin
  2808. if (x < 0) or (x >= Width) or (y < 0) or (y >= Height) then
  2809. result := clNone32 else
  2810. result := fPixels[y * width + x];
  2811. end;
  2812. //------------------------------------------------------------------------------
  2813. procedure TImage32.SetPixel(x,y: Integer; color: TColor32);
  2814. begin
  2815. if (x < 0) or (x >= Width) or (y < 0) or (y >= Height) then Exit;
  2816. fPixels[y * width + x] := color;
  2817. //nb: no notify event here
  2818. end;
  2819. //------------------------------------------------------------------------------
  2820. function TImage32.GetIsBlank: Boolean;
  2821. var
  2822. i: integer;
  2823. pc: PARGB;
  2824. begin
  2825. result := IsEmpty;
  2826. if result then Exit;
  2827. pc := PARGB(PixelBase);
  2828. for i := 0 to width * height -1 do
  2829. begin
  2830. if pc.A > 0 then Exit;
  2831. inc(pc);
  2832. end;
  2833. result := true;
  2834. end;
  2835. //------------------------------------------------------------------------------
  2836. function TImage32.GetIsEmpty: Boolean;
  2837. begin
  2838. result := fPixels = nil;
  2839. end;
  2840. //------------------------------------------------------------------------------
  2841. function TImage32.GetPixelBase: PColor32;
  2842. begin
  2843. if IsEmpty then result := nil
  2844. else result := @fPixels[0];
  2845. end;
  2846. //------------------------------------------------------------------------------
  2847. function TImage32.GetPixelRow(row: Integer): PColor32;
  2848. begin
  2849. if IsEmpty then result := nil
  2850. else result := @fPixels[row * Width];
  2851. end;
  2852. //------------------------------------------------------------------------------
  2853. procedure TImage32.CopyInternal(src: TImage32;
  2854. const srcRec, dstRec: TRect; blendFunc: TBlendFunction);
  2855. var
  2856. i, j: integer;
  2857. srcRecWidth, srcRecHeight: nativeint;
  2858. srcWidth, dstWidth: nativeint;
  2859. s, d: PColor32;
  2860. begin
  2861. // occasionally, due to rounding, srcRec and dstRec
  2862. // don't have exactly the same widths and heights, so ...
  2863. srcRecWidth :=
  2864. Min(srcRec.Right - srcRec.Left, dstRec.Right - dstRec.Left);
  2865. srcRecHeight :=
  2866. Min(srcRec.Bottom - srcRec.Top, dstRec.Bottom - dstRec.Top);
  2867. srcWidth := src.Width;
  2868. dstWidth := Width;
  2869. s := @src.Pixels[srcRec.Top * srcWidth + srcRec.Left];
  2870. d := @Pixels[dstRec.top * dstWidth + dstRec.Left];
  2871. if assigned(blendFunc) then
  2872. begin
  2873. srcWidth := (srcWidth - srcRecWidth) * SizeOf(TColor32);
  2874. dstWidth := (dstWidth - srcRecWidth) * SizeOf(TColor32);
  2875. for i := 1 to srcRecHeight do
  2876. begin
  2877. for j := 1 to srcRecWidth do
  2878. begin
  2879. d^ := blendFunc(d^, s^);
  2880. inc(s); inc(d);
  2881. end;
  2882. inc(PByte(s), srcWidth); // byte offset to the next s line
  2883. inc(PByte(d), dstWidth); // byte offset to the next d line
  2884. end;
  2885. end
  2886. //simply overwrite src with dst (ie without blending)
  2887. else if (srcRecWidth = dstWidth) and (srcWidth = dstWidth) then
  2888. move(s^, d^, srcRecWidth * srcRecHeight * SizeOf(TColor32))
  2889. else
  2890. begin
  2891. srcWidth := srcWidth * SizeOf(TColor32);
  2892. dstWidth := dstWidth * SizeOf(TColor32);
  2893. srcRecWidth := srcRecWidth * SizeOf(TColor32);
  2894. for i := 1 to srcRecHeight do
  2895. begin
  2896. move(s^, d^, srcRecWidth);
  2897. inc(PByte(s), srcWidth); // srcWidth is in bytes
  2898. inc(PByte(d), dstWidth); // dstWidth is in bytes
  2899. end;
  2900. end;
  2901. end;
  2902. //------------------------------------------------------------------------------
  2903. procedure TImage32.CopyInternalLine(src: TImage32;
  2904. const srcRec, dstRec: TRect; blendLineFunc: TBlendLineFunction);
  2905. var
  2906. i: integer;
  2907. srcRecWidth, srcRecHeight: nativeint;
  2908. srcWidth, dstWidth: nativeint;
  2909. s, d: PColor32;
  2910. begin
  2911. if not Assigned(blendLineFunc) then
  2912. begin
  2913. CopyInternal(src, srcRec, dstRec, nil);
  2914. Exit;
  2915. end;
  2916. // occasionally, due to rounding, srcRec and dstRec
  2917. // don't have exactly the same widths and heights, so ...
  2918. srcRecWidth :=
  2919. Min(srcRec.Right - srcRec.Left, dstRec.Right - dstRec.Left);
  2920. srcRecHeight :=
  2921. Min(srcRec.Bottom - srcRec.Top, dstRec.Bottom - dstRec.Top);
  2922. srcWidth := src.Width;
  2923. dstWidth := Width;
  2924. s := @src.Pixels[srcRec.Top * srcWidth + srcRec.Left];
  2925. d := @Pixels[dstRec.top * dstWidth + dstRec.Left];
  2926. if (srcRecWidth = dstWidth) and (srcWidth = dstWidth) then
  2927. blendLineFunc(d, s, srcRecWidth * srcRecHeight)
  2928. else
  2929. begin
  2930. srcWidth := srcWidth * SizeOf(TColor32);
  2931. dstWidth := dstWidth * SizeOf(TColor32);
  2932. for i := 1 to srcRecHeight do
  2933. begin
  2934. blendLineFunc(d, s, srcRecWidth);
  2935. inc(PByte(s), srcWidth); // srcWidth is in bytes
  2936. inc(PByte(d), dstWidth); // dstWidth is in bytes
  2937. end;
  2938. end;
  2939. end;
  2940. //------------------------------------------------------------------------------
  2941. function TImage32.Copy(src: TImage32; srcRec, dstRec: TRect): Boolean;
  2942. begin
  2943. Result := CopyBlendInternal(src, srcRec, dstRec, nil, nil);
  2944. end;
  2945. //------------------------------------------------------------------------------
  2946. function TImage32.CopyBlend(src: TImage32; const srcRec, dstRec: TRect;
  2947. blendFunc: TBlendFunction): Boolean;
  2948. begin
  2949. Result := CopyBlendInternal(src, srcRec, dstRec, blendFunc, nil);
  2950. end;
  2951. //------------------------------------------------------------------------------
  2952. function TImage32.CopyBlend(src: TImage32; const srcRec, dstRec: TRect;
  2953. blendLineFunc: TBlendLineFunction): Boolean;
  2954. begin
  2955. Result := CopyBlendInternal(src, srcRec, dstRec, nil, blendLineFunc);
  2956. end;
  2957. //------------------------------------------------------------------------------
  2958. function TImage32.CopyBlendInternal(src: TImage32; const srcRec: TRect; dstRec: TRect;
  2959. blendFunc: TBlendFunction; blendLineFunc: TBlendLineFunction): Boolean;
  2960. var
  2961. tmp: TImage32;
  2962. srcRecClipped, dstRecClipped, r: TRect;
  2963. scaleX, scaleY: double;
  2964. w,h, dstW,dstH, srcW,srcH: integer;
  2965. begin
  2966. result := false;
  2967. if IsEmptyRect(srcRec) or IsEmptyRect(dstRec) then Exit;
  2968. Types.IntersectRect(srcRecClipped, srcRec, src.Bounds);
  2969. //get the scaling amount (if any) before
  2970. //dstRec might be adjusted due to clipping ...
  2971. RectWidthHeight(dstRec, dstW, dstH);
  2972. RectWidthHeight(srcRec, srcW, srcH);
  2973. //watching out for insignificant scaling
  2974. if Abs(dstW - srcW) < 2 then
  2975. scaleX := 1 else
  2976. scaleX := dstW / srcW;
  2977. if Abs(dstH - srcH) < 2 then
  2978. scaleY := 1 else
  2979. scaleY := dstH / srcH;
  2980. //check if the source rec has been clipped ...
  2981. if not RectsEqual(srcRecClipped, srcRec) then
  2982. begin
  2983. if IsEmptyRect(srcRecClipped) then Exit;
  2984. //the source has been clipped so clip the destination too ...
  2985. RectWidthHeight(srcRecClipped, w, h);
  2986. RectWidthHeight(srcRec, srcW, srcH);
  2987. ScaleRect(dstRec, w / srcW, h / srcH);
  2988. TranslateRect(dstRec,
  2989. srcRecClipped.Left - srcRec.Left,
  2990. srcRecClipped.Top - srcRec.Top);
  2991. end;
  2992. if (scaleX <> 1.0) or (scaleY <> 1.0) then
  2993. begin
  2994. //scale source (tmp) to the destination then call CopyBlend() again ...^
  2995. tmp := TImage32.Create;
  2996. try
  2997. tmp.AssignSettings(src);
  2998. src.ScaleTo(tmp, scaleX, scaleY);
  2999. ScaleRect(srcRecClipped, scaleX, scaleY);
  3000. result := CopyBlendInternal(tmp, srcRecClipped, dstRec, blendFunc, blendLineFunc);
  3001. finally
  3002. tmp.Free;
  3003. end;
  3004. Exit;
  3005. end;
  3006. Types.IntersectRect(dstRecClipped, dstRec, Bounds);
  3007. if IsEmptyRect(dstRecClipped) then Exit;
  3008. //there's no scaling if we get here, but further clipping may be needed if
  3009. //the destination rec is partially outside the destination image's bounds
  3010. if not RectsEqual(dstRecClipped, dstRec) then
  3011. begin
  3012. //the destination rec has been clipped so clip the source too ...
  3013. RectWidthHeight(dstRecClipped, w, h);
  3014. RectWidthHeight(dstRec, dstW, dstH);
  3015. ScaleRect(srcRecClipped, w / dstW, h / dstH);
  3016. TranslateRect(srcRecClipped,
  3017. dstRecClipped.Left - dstRec.Left,
  3018. dstRecClipped.Top - dstRec.Top);
  3019. end;
  3020. //when copying to self and srcRec & dstRec overlap then
  3021. //copy srcRec to a temporary image and use it as the source ...
  3022. if (src = self) and Types.IntersectRect(r, srcRecClipped, dstRecClipped) then
  3023. begin
  3024. tmp := TImage32.Create(self, srcRecClipped);
  3025. try
  3026. result := src.CopyBlendInternal(tmp, tmp.Bounds, dstRecClipped, blendFunc, blendLineFunc);
  3027. finally
  3028. tmp.Free;
  3029. end;
  3030. Exit;
  3031. end;
  3032. if Assigned(blendLineFunc) then
  3033. CopyInternalLine(src, srcRecClipped, dstRecClipped, blendLineFunc)
  3034. else
  3035. CopyInternal(src, srcRecClipped, dstRecClipped, blendFunc);
  3036. result := true;
  3037. Changed;
  3038. end;
  3039. //------------------------------------------------------------------------------
  3040. function TImage32.LoadFromResource(const resName: string; resType: PChar): Boolean;
  3041. var
  3042. resStream: TResourceStream;
  3043. begin
  3044. resStream := CreateResourceStream(resName, resType);
  3045. try
  3046. Result := assigned(resStream) and
  3047. LoadFromStream(resStream);
  3048. finally
  3049. resStream.Free;
  3050. end;
  3051. end;
  3052. //------------------------------------------------------------------------------
  3053. {$IF DEFINED (MSWINDOWS)}
  3054. procedure TImage32.CopyFromDC(srcDc: HDC; const srcRect: TRect);
  3055. var
  3056. bi: TBitmapInfoHeader;
  3057. bm, oldBm: HBitmap;
  3058. dc, memDc: HDC;
  3059. pixels: Pointer;
  3060. w,h: integer;
  3061. begin
  3062. BeginUpdate;
  3063. try
  3064. RectWidthHeight(srcRect, w,h);
  3065. SetSize(w, h);
  3066. bi := Get32bitBitmapInfoHeader(w, -h); // -h => avoids need to flip image
  3067. dc := GetDC(0);
  3068. memDc := CreateCompatibleDC(dc);
  3069. try
  3070. bm := CreateDIBSection(dc,
  3071. PBITMAPINFO(@bi)^, DIB_RGB_COLORS, pixels, 0, 0);
  3072. if bm = 0 then Exit;
  3073. try
  3074. oldBm := SelectObject(memDc, bm);
  3075. BitBlt(memDc, 0, 0, w, h, srcDc, srcRect.Left,srcRect.Top, SRCCOPY);
  3076. Move(pixels^, fPixels[0], w * h * sizeOf(TColor32));
  3077. SelectObject(memDc, oldBm);
  3078. finally
  3079. DeleteObject(bm);
  3080. end;
  3081. finally
  3082. DeleteDc(memDc);
  3083. ReleaseDc(0, dc);
  3084. end;
  3085. if IsBlank then SetAlpha(255);
  3086. //FlipVertical;
  3087. finally
  3088. EndUpdate;
  3089. end;
  3090. end;
  3091. //------------------------------------------------------------------------------
  3092. procedure TImage32.CopyToDc(dstDc: HDC; x,y: Integer; transparent: Boolean);
  3093. begin
  3094. CopyToDc(Bounds, Types.Rect(x,y, x+Width, y+Height),
  3095. dstDc, transparent);
  3096. end;
  3097. //------------------------------------------------------------------------------
  3098. procedure TImage32.CopyToDc(const srcRect: TRect; dstDc: HDC;
  3099. x: Integer = 0; y: Integer = 0; transparent: Boolean = true);
  3100. var
  3101. recW, recH: integer;
  3102. begin
  3103. RectWidthHeight(srcRect, recW, recH);
  3104. CopyToDc(srcRect, Types.Rect(x,y, x+recW, y+recH), dstDc, transparent);
  3105. end;
  3106. //------------------------------------------------------------------------------
  3107. procedure TImage32.CopyToDc(const srcRect, dstRect: TRect;
  3108. dstDc: HDC; transparent: Boolean = true);
  3109. var
  3110. i, x,y, wSrc ,hSrc, wDest, hDest, wBytes: integer;
  3111. rec: TRect;
  3112. bi: TBitmapInfoHeader;
  3113. bm, oldBm: HBitmap;
  3114. dibBits: Pointer;
  3115. pDst, pSrc: PARGB;
  3116. memDc: HDC;
  3117. isTransparent: Boolean;
  3118. bf: BLENDFUNCTION;
  3119. oldStretchBltMode: integer;
  3120. begin
  3121. Types.IntersectRect(rec, srcRect, Bounds);
  3122. if IsEmpty or IsEmptyRect(rec) or IsEmptyRect(dstRect) then Exit;
  3123. RectWidthHeight(rec, wSrc, hSrc);
  3124. RectWidthHeight(dstRect, wDest, hDest);
  3125. x := dstRect.Left;
  3126. y := dstRect.Top;
  3127. inc(x, rec.Left - srcRect.Left);
  3128. inc(y, rec.Top - srcRect.Top);
  3129. bi := Get32bitBitmapInfoHeader(wSrc, hSrc);
  3130. isTransparent := transparent and RectHasTransparency(srcRect);
  3131. memDc := CreateCompatibleDC(dstDc);
  3132. try
  3133. bm := CreateDIBSection(memDc, PBITMAPINFO(@bi)^,
  3134. DIB_RGB_COLORS, dibBits, 0, 0);
  3135. if bm = 0 then Exit;
  3136. try
  3137. //copy Image to dibBits (with vertical flip)
  3138. wBytes := wSrc * SizeOf(TColor32);
  3139. pDst := dibBits;
  3140. pSrc := PARGB(PixelRow[rec.Bottom -1]);
  3141. inc(pSrc, rec.Left);
  3142. if isTransparent and not IsPremultiplied then
  3143. begin
  3144. //premultiplied alphas are required when alpha blending
  3145. for i := rec.Bottom -1 downto rec.Top do
  3146. begin
  3147. PremultiplyAlpha(pSrc, pDst, wSrc);
  3148. dec(pSrc, Width);
  3149. inc(pDst, wSrc);
  3150. end;
  3151. end
  3152. else
  3153. begin
  3154. for i := rec.Bottom -1 downto rec.Top do
  3155. begin
  3156. Move(pSrc^, pDst^, wBytes);
  3157. dec(pSrc, Width);
  3158. inc(pDst, wSrc);
  3159. end;
  3160. end;
  3161. oldBm := SelectObject(memDC, bm);
  3162. if isTransparent then
  3163. begin
  3164. //premultiplied alphas are required when alpha blending
  3165. bf.BlendOp := AC_SRC_OVER;
  3166. bf.BlendFlags := 0;
  3167. bf.SourceConstantAlpha := 255;
  3168. bf.AlphaFormat := AC_SRC_ALPHA;
  3169. AlphaBlend(dstDc, x,y, wDest,hDest, memDC, 0,0, wSrc,hSrc, bf);
  3170. end
  3171. else if (wDest = wSrc) and (hDest = hSrc) then
  3172. begin
  3173. BitBlt(dstDc, x,y, wSrc, hSrc, memDc, 0,0, SRCCOPY)
  3174. end else
  3175. begin
  3176. oldStretchBltMode := SetStretchBltMode(dstDc, COLORONCOLOR);
  3177. StretchBlt(dstDc, x,y, wDest, hDest, memDc, 0,0, wSrc,hSrc, SRCCOPY);
  3178. if oldStretchBltMode <> COLORONCOLOR then // restore mode
  3179. SetStretchBltMode(dstDc, oldStretchBltMode);
  3180. end;
  3181. SelectObject(memDC, oldBm);
  3182. finally
  3183. DeleteObject(bm);
  3184. end;
  3185. finally
  3186. DeleteDc(memDc);
  3187. end;
  3188. end;
  3189. {$IFEND}
  3190. //------------------------------------------------------------------------------
  3191. {$IF DEFINED(USING_VCL_LCL)}
  3192. procedure TImage32.CopyFromBitmap(bmp: TBitmap);
  3193. var
  3194. ms: TMemoryStream;
  3195. bmpFormat: TImageFormat_BMP;
  3196. begin
  3197. ms := TMemoryStream.create;
  3198. bmpFormat := TImageFormat_BMP.Create;
  3199. try
  3200. bmp.SaveToStream(ms);
  3201. ms.Position := 0;
  3202. bmpFormat.LoadFromStream(ms, self);
  3203. finally
  3204. ms.Free;
  3205. bmpFormat.Free;
  3206. end;
  3207. end;
  3208. //------------------------------------------------------------------------------
  3209. procedure TImage32.CopyToBitmap(bmp: TBitmap);
  3210. var
  3211. ms: TMemoryStream;
  3212. bmpFormat: TImageFormat_BMP;
  3213. begin
  3214. ms := TMemoryStream.create;
  3215. bmpFormat := TImageFormat_BMP.Create;
  3216. try
  3217. bmpFormat.IncludeFileHeaderInSaveStream := true;
  3218. bmpFormat.SaveToStream(ms, self);
  3219. ms.Position := 0;
  3220. bmp.PixelFormat := pf32bit;
  3221. {$IF DEFINED(USING_VCL) AND DEFINED(ALPHAFORMAT)}
  3222. bmp.AlphaFormat := afDefined;
  3223. {$IFEND}
  3224. bmp.LoadFromStream(ms);
  3225. finally
  3226. ms.Free;
  3227. bmpFormat.Free;
  3228. end;
  3229. end;
  3230. //------------------------------------------------------------------------------
  3231. {$IFEND}
  3232. function TImage32.CopyToClipBoard: Boolean;
  3233. var
  3234. i: Integer;
  3235. formatClass: TImageFormatClass;
  3236. begin
  3237. //Sadly with CF_DIB (and even CF_DIBV5) clipboard formats, transparency is
  3238. //usually lost, so we'll copy all available formats including CF_PNG, that
  3239. //is if it's registered.
  3240. result := not IsEmpty;
  3241. if not result then Exit;
  3242. result := false;
  3243. for i := ImageFormatClassList.Count -1 downto 0 do
  3244. begin
  3245. formatClass := PImgFmtRec(ImageFormatClassList[i]).Obj;
  3246. if not formatClass.CanCopyToClipboard then Continue;
  3247. with formatClass.Create do
  3248. try
  3249. result := CopyToClipboard(self);
  3250. finally
  3251. free;
  3252. end;
  3253. end;
  3254. end;
  3255. //------------------------------------------------------------------------------
  3256. class function TImage32.CanPasteFromClipBoard: Boolean;
  3257. var
  3258. i: Integer;
  3259. formatClass: TImageFormatClass;
  3260. begin
  3261. result := false;
  3262. for i := ImageFormatClassList.Count -1 downto 0 do
  3263. begin
  3264. formatClass := PImgFmtRec(ImageFormatClassList[i]).Obj;
  3265. if formatClass.CanPasteFromClipboard then
  3266. begin
  3267. result := true;
  3268. Exit;
  3269. end;
  3270. end;
  3271. end;
  3272. //------------------------------------------------------------------------------
  3273. function TImage32.PasteFromClipBoard: Boolean;
  3274. var
  3275. i: Integer;
  3276. formatClass: TImageFormatClass;
  3277. begin
  3278. result := false;
  3279. for i := ImageFormatClassList.Count -1 downto 0 do
  3280. begin
  3281. formatClass := PImgFmtRec(ImageFormatClassList[i]).Obj;
  3282. if not formatClass.CanPasteFromClipboard then Continue;
  3283. with formatClass.Create do
  3284. try
  3285. result := PasteFromClipboard(self);
  3286. if not Result then Continue;
  3287. finally
  3288. free;
  3289. end;
  3290. Changed;
  3291. Break;
  3292. end;
  3293. end;
  3294. //------------------------------------------------------------------------------
  3295. procedure TImage32.ConvertToBoolMask(reference: TColor32; tolerance: integer;
  3296. colorFunc: TCompareFunction; maskBg: TColor32; maskFg: TColor32);
  3297. var
  3298. i: Integer;
  3299. mask: TArrayOfByte;
  3300. c: PColor32;
  3301. b: PByte;
  3302. begin
  3303. if IsEmpty then Exit;
  3304. mask := GetBoolMask(self, reference, colorFunc, tolerance);
  3305. c := PixelBase;
  3306. b := @mask[0];
  3307. for i := 0 to Width * Height -1 do
  3308. begin
  3309. {$IFDEF PBYTE}
  3310. if b^ = 0 then c^ := maskBg else c^ := maskFg;
  3311. {$ELSE}
  3312. if b^ = #0 then c^ := maskBg else c^ := maskFg;
  3313. {$ENDIF}
  3314. inc(c); inc(b);
  3315. end;
  3316. Changed;
  3317. end;
  3318. //------------------------------------------------------------------------------
  3319. procedure TImage32.ConvertToAlphaMask(reference: TColor32;
  3320. colorFunc: TCompareFunctionEx);
  3321. var
  3322. i: Integer;
  3323. mask: TArrayOfByte;
  3324. c: PColor32;
  3325. b: PByte;
  3326. begin
  3327. if IsEmpty then Exit;
  3328. mask := GetByteMask(self, reference, colorFunc);
  3329. c := PixelBase;
  3330. b := @mask[0];
  3331. for i := 0 to Width * Height -1 do
  3332. begin
  3333. {$IFDEF PBYTE}
  3334. c^ := b^ shl 24;
  3335. {$ELSE}
  3336. c^ := Ord(b^) shl 24;
  3337. {$ENDIF}
  3338. inc(c); inc(b);
  3339. end;
  3340. Changed;
  3341. end;
  3342. //------------------------------------------------------------------------------
  3343. procedure TImage32.FlipVertical;
  3344. var
  3345. i: Integer;
  3346. a: TArrayOfColor32;
  3347. src, dst: PColor32;
  3348. begin
  3349. if IsEmpty then Exit;
  3350. NewColor32Array(a, fWidth * fHeight, True);
  3351. src := @fPixels[(height-1) * width];
  3352. dst := @a[0];
  3353. for i := 0 to fHeight -1 do
  3354. begin
  3355. move(src^, dst^, fWidth * SizeOf(TColor32));
  3356. dec(src, fWidth); inc(dst, fWidth);
  3357. end;
  3358. fPixels := a;
  3359. Changed;
  3360. end;
  3361. //------------------------------------------------------------------------------
  3362. procedure TImage32.FlipHorizontal;
  3363. var
  3364. i,j, widthLess1: Integer;
  3365. a: TArrayOfColor32;
  3366. row: PColor32;
  3367. begin
  3368. if IsEmpty then Exit;
  3369. NewColor32Array(a, fWidth, True);
  3370. widthLess1 := fWidth -1;
  3371. row := @fPixels[(height-1) * width]; //top row
  3372. for i := 0 to fHeight -1 do
  3373. begin
  3374. move(row^, a[0], fWidth * SizeOf(TColor32));
  3375. for j := 0 to widthLess1 do
  3376. begin
  3377. row^ := a[widthLess1 - j];
  3378. inc(row);
  3379. end;
  3380. dec(row, fWidth *2);
  3381. end;
  3382. Changed;
  3383. end;
  3384. //------------------------------------------------------------------------------
  3385. procedure TImage32.PreMultiply;
  3386. begin
  3387. if IsEmpty or fIsPremultiplied then Exit;
  3388. fIsPremultiplied := true;
  3389. PremultiplyAlpha(PARGB(PixelBase), PARGB(PixelBase), Width * Height);
  3390. //nb: no OnChange notify event here
  3391. end;
  3392. //------------------------------------------------------------------------------
  3393. procedure TImage32.SetRGB(rgbColor: TColor32);
  3394. var
  3395. i: Integer;
  3396. pc: PColor32;
  3397. c: TColor32;
  3398. begin
  3399. //this method leaves the alpha channel untouched
  3400. if IsEmpty then Exit;
  3401. pc := PixelBase;
  3402. rgbColor := rgbColor and $00FFFFFF;
  3403. for i := 0 to Width * Height - 1 do
  3404. begin
  3405. c := pc^;
  3406. if c and $FF000000 = 0 then
  3407. pc^ := 0 else
  3408. pc^ := c and $FF000000 or rgbColor;
  3409. inc(pc);
  3410. end;
  3411. Changed;
  3412. end;
  3413. //------------------------------------------------------------------------------
  3414. procedure TImage32.SetRGB(rgbColor: TColor32; rec: TRect);
  3415. var
  3416. i,j, dx: Integer;
  3417. pc: PColor32;
  3418. begin
  3419. Types.IntersectRect(rec, rec, bounds);
  3420. if IsEmptyRect(rec) then Exit;
  3421. rgbColor := rgbColor and $00FFFFFF;
  3422. pc := PixelBase;
  3423. inc(pc, rec.Left);
  3424. dx := Width - RectWidth(rec);
  3425. for i := rec.Top to rec.Bottom -1 do
  3426. begin
  3427. for j := rec.Left to rec.Right -1 do
  3428. begin
  3429. pc^ := pc^ and $FF000000 or rgbColor;
  3430. inc(pc);
  3431. end;
  3432. inc(pc, dx);
  3433. end;
  3434. Changed;
  3435. end;
  3436. //------------------------------------------------------------------------------
  3437. procedure TImage32.SetAlpha(alpha: Byte);
  3438. var
  3439. i: Integer;
  3440. c: PARGB;
  3441. begin
  3442. //this method only changes the alpha channel
  3443. if IsEmpty then Exit;
  3444. c := PARGB(PixelBase);
  3445. for i := 0 to Width * Height -1 do
  3446. begin
  3447. c.A := alpha;
  3448. inc(c);
  3449. end;
  3450. Changed;
  3451. end;
  3452. //------------------------------------------------------------------------------
  3453. procedure TImage32.ReduceOpacity(opacity: Byte);
  3454. var
  3455. i: Integer;
  3456. c: PARGB;
  3457. a: Byte;
  3458. begin
  3459. if opacity = 255 then Exit;
  3460. c := PARGB(PixelBase);
  3461. for i := 0 to Width * Height -1 do
  3462. begin
  3463. a := c.A;
  3464. if a <> 0 then
  3465. c.A := MulTable[a, opacity];
  3466. inc(c);
  3467. end;
  3468. Changed;
  3469. end;
  3470. //------------------------------------------------------------------------------
  3471. procedure TImage32.ReduceOpacity(opacity: Byte; rec: TRect);
  3472. var
  3473. i,j, rw: Integer;
  3474. c: PARGB;
  3475. a: Byte;
  3476. lineOffsetInBytes: integer;
  3477. begin
  3478. Types.IntersectRect(rec, rec, bounds);
  3479. if IsEmptyRect(rec) then Exit;
  3480. rw := RectWidth(rec);
  3481. c := @Pixels[rec.Top * Width + rec.Left];
  3482. lineOffsetInBytes := (Width - rw) * SizeOf(TARGB);
  3483. for i := rec.Top to rec.Bottom - 1 do
  3484. begin
  3485. for j := 1 to rw do
  3486. begin
  3487. a := c.A;
  3488. if a <> 0 then
  3489. c.A := MulTable[a, opacity];
  3490. inc(c);
  3491. end;
  3492. inc(PByte(c), lineOffsetInBytes);
  3493. end;
  3494. Changed;
  3495. end;
  3496. //------------------------------------------------------------------------------
  3497. procedure TImage32.Grayscale(mode: TGrayscaleMode;
  3498. linearAmountPercentage: double);
  3499. var
  3500. i: SizeInt;
  3501. cLinear: double;
  3502. c, lastC, grayC: TColor32;
  3503. p: PColor32Array;
  3504. amountCalc: Boolean;
  3505. oneMinusAmount: double;
  3506. begin
  3507. if mode = gsmSaturation then
  3508. begin
  3509. // linearAmountPercentage has no effect here
  3510. AdjustSaturation(-100);
  3511. Exit;
  3512. end;
  3513. // Colorimetric (perceptual luminance-preserving) conversion to grayscale
  3514. // See https://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
  3515. if IsEmpty then Exit;
  3516. if linearAmountPercentage <= 0.0 then Exit;
  3517. amountCalc := linearAmountPercentage < 1.0;
  3518. oneMinusAmount := 1.0 - linearAmountPercentage;
  3519. p := PColor32Array(PixelBase);
  3520. lastC := 0;
  3521. grayC := 0;
  3522. for i := 0 to high(fPixels) do
  3523. begin
  3524. c := p[i] and $00FFFFFF;
  3525. if c <> 0 then
  3526. begin
  3527. if c <> lastC then // only do the calculation if the color channels changed
  3528. begin
  3529. lastC := c;
  3530. {$IF DEFINED(ANDROID)}
  3531. c := SwapRedBlue(c);
  3532. {$IFEND}
  3533. // We don't divide by 255 here, so can skip some division and multiplications.
  3534. // That means cLinear is actually "cLinear * 255"
  3535. cLinear := (0.2126 * Byte(c shr 16)) + (0.7152 * Byte(c shr 8)) + (0.0722 * Byte(c));
  3536. //cLinear := (0.2126 * TARGB(c).R) + (0.7152 * TARGB(c).G) + (0.0722 * TARGB(c).B);
  3537. if mode = gsmLinear then
  3538. c := ClampByte(cLinear)
  3539. else //if mode = gsmColorimetric then
  3540. begin
  3541. if cLinear <= (0.0031308 * 255) then // adjust for cLinear being "cLiniear * 255"
  3542. c := ClampByte(Integer(Round(12.92 * cLinear)))
  3543. else // for Power we must divide by 255 and then later multipy by 255
  3544. //c := ClampByte(Integer(Round((1.055 * 255) * Power(cLinear / 255, 1/2.4) - (0.055 * 255))));
  3545. end;
  3546. if not amountCalc then
  3547. grayC := (c shl 16) or (c shl 8) or c
  3548. else
  3549. begin
  3550. cLinear := c * linearAmountPercentage;
  3551. grayC := ClampByte(Integer(Round(Byte(lastC shr 16) * oneMinusAmount + cLinear))) shl 16 or
  3552. ClampByte(Integer(Round(Byte(lastC shr 8) * oneMinusAmount + cLinear))) shl 8 or
  3553. ClampByte(Integer(Round(Byte(lastC ) * oneMinusAmount + cLinear)));
  3554. end;
  3555. {$IF DEFINED(ANDROID)}
  3556. grayC := SwapRedBlue(grayC);
  3557. {$IFEND}
  3558. end;
  3559. p[i] := (p[i] and $FF000000) or grayC;
  3560. end;
  3561. end;
  3562. Changed;
  3563. end;
  3564. //------------------------------------------------------------------------------
  3565. procedure TImage32.InvertColors;
  3566. var
  3567. pc: PColor32Array;
  3568. i: SizeInt;
  3569. begin
  3570. pc := PColor32Array(PixelBase);
  3571. for i := 0 to Width * Height -1 do
  3572. pc[i] := pc[i] xor $00FFFFFF; // keep the alpha channel untouched
  3573. Changed;
  3574. end;
  3575. //------------------------------------------------------------------------------
  3576. procedure TImage32.InvertAlphas;
  3577. var
  3578. pc: PColor32Array;
  3579. i: SizeInt;
  3580. begin
  3581. pc := PColor32Array(PixelBase);
  3582. for i := 0 to Width * Height -1 do
  3583. pc[i] := pc[i] xor $FF000000; // keep the color channels untouched
  3584. Changed;
  3585. end;
  3586. //------------------------------------------------------------------------------
  3587. procedure TImage32.AdjustHue(percent: Integer);
  3588. var
  3589. i: SizeInt;
  3590. hsl: THsl;
  3591. lut: array [byte] of byte;
  3592. c, lastC, newC: TColor32;
  3593. p: PColor32Array;
  3594. begin
  3595. percent := percent mod 100;
  3596. if percent < 0 then inc(percent, 100);
  3597. percent := Round(percent * 255 / 100);
  3598. if (percent = 0) or IsEmpty then Exit;
  3599. for i := 0 to 255 do lut[i] := (i + percent) mod 255;
  3600. lastC := 0;
  3601. newC := 0;
  3602. p := PColor32Array(fPixels);
  3603. for i := 0 to high(fPixels) do
  3604. begin
  3605. c := p[i];
  3606. c := c and $00FFFFFF;
  3607. if c <> 0 then
  3608. begin
  3609. if c <> lastC then // only do the calculation if the color channels changed
  3610. begin
  3611. lastC := C;
  3612. hsl := RgbToHsl(c);
  3613. hsl.hue := lut[hsl.hue];
  3614. newC := HslToRgb(hsl);
  3615. end;
  3616. p[i] := (p[i] and $FF000000) or newC; // keep the alpha channel
  3617. end;
  3618. end;
  3619. Changed;
  3620. end;
  3621. //------------------------------------------------------------------------------
  3622. procedure TImage32.AdjustLuminance(percent: Integer);
  3623. var
  3624. i: SizeInt;
  3625. hsl: THsl;
  3626. pc: double;
  3627. lut: array [byte] of byte;
  3628. c, lastC, newC: TColor32;
  3629. p: PColor32Array;
  3630. begin
  3631. if (percent = 0) or IsEmpty then Exit;
  3632. percent := percent mod 101;
  3633. pc := percent / 100;
  3634. if pc > 0 then
  3635. for i := 0 to 255 do lut[i] := Round(i + (255 - i) * pc)
  3636. else
  3637. for i := 0 to 255 do lut[i] := Round(i + (i * pc));
  3638. lastC := 0;
  3639. newC := 0;
  3640. p := PColor32Array(fPixels);
  3641. for i := 0 to high(fPixels) do
  3642. begin
  3643. c := p[i];
  3644. c := c and $00FFFFFF;
  3645. if c <> 0 then
  3646. begin
  3647. if c <> lastC then // only do the calculation if the color channels changed
  3648. begin
  3649. lastC := C;
  3650. hsl := RgbToHsl(c);
  3651. hsl.lum := lut[hsl.lum];
  3652. newC := HslToRgb(hsl);
  3653. end;
  3654. p[i] := (p[i] and $FF000000) or newC; // keep the alpha channel
  3655. end;
  3656. end;
  3657. Changed;
  3658. end;
  3659. //------------------------------------------------------------------------------
  3660. procedure TImage32.AdjustSaturation(percent: Integer);
  3661. var
  3662. i: SizeInt;
  3663. hsl: THsl;
  3664. lut: array [byte] of byte;
  3665. pc: double;
  3666. c, lastC, newC: TColor32;
  3667. p: PColor32Array;
  3668. begin
  3669. if (percent = 0) or IsEmpty then Exit;
  3670. percent := percent mod 101;
  3671. pc := percent / 100;
  3672. if pc > 0 then
  3673. for i := 0 to 255 do lut[i] := Round(i + (255 - i) * pc)
  3674. else
  3675. for i := 0 to 255 do lut[i] := Round(i + (i * pc));
  3676. lastC := 0;
  3677. newC := 0;
  3678. p := PColor32Array(fPixels);
  3679. for i := 0 to high(fPixels) do
  3680. begin
  3681. c := p[i];
  3682. c := c and $00FFFFFF;
  3683. if c <> 0 then
  3684. begin
  3685. if c <> lastC then // only do the calculation if the color channels changed
  3686. begin
  3687. lastC := C;
  3688. hsl := RgbToHsl(c);
  3689. hsl.sat := lut[hsl.sat];
  3690. newC := HslToRgb(hsl);
  3691. end;
  3692. p[i] := (p[i] and $FF000000) or newC; // keep the alpha channel
  3693. end;
  3694. end;
  3695. Changed;
  3696. end;
  3697. //------------------------------------------------------------------------------
  3698. function TImage32.GetOpaqueBounds: TRect;
  3699. var
  3700. x,y, x1,x2,y1,y2: Integer;
  3701. found: Boolean;
  3702. begin
  3703. y1 := 0; y2 := 0;
  3704. found := false;
  3705. Result := NullRect;
  3706. for y := 0 to Height -1 do
  3707. begin
  3708. for x := 0 to Width -1 do
  3709. if TARGB(fPixels[y * Width + x]).A > 0 then
  3710. begin
  3711. y1 := y;
  3712. found := true;
  3713. break;
  3714. end;
  3715. if found then break;
  3716. end;
  3717. if not found then
  3718. Exit;
  3719. found := false;
  3720. for y := Height -1 downto 0 do
  3721. begin
  3722. for x := 0 to Width -1 do
  3723. if TARGB(fPixels[y * Width + x]).A > 0 then
  3724. begin
  3725. y2 := y;
  3726. found := true;
  3727. break;
  3728. end;
  3729. if found then break;
  3730. end;
  3731. x1 := Width; x2 := 0;
  3732. for y := y1 to y2 do
  3733. for x := 0 to Width -1 do
  3734. if TARGB(fPixels[y * Width + x]).A > 0 then
  3735. begin
  3736. if x < x1 then x1 := x;
  3737. if x > x2 then x2 := x;
  3738. end;
  3739. Result := Types.Rect(x1, y1, x2+1, y2+1);
  3740. end;
  3741. //------------------------------------------------------------------------------
  3742. function TImage32.CropTransparentPixels: TRect;
  3743. begin
  3744. Result := GetOpaqueBounds;
  3745. if IsEmptyRect(Result) then
  3746. SetSize(0,0) else
  3747. Crop(Result);
  3748. end;
  3749. //------------------------------------------------------------------------------
  3750. procedure TImage32.Rotate(angleRads: double);
  3751. var
  3752. mat: TMatrixD;
  3753. begin
  3754. {$IFDEF CLOCKWISE_ROTATION_WITH_NEGATIVE_ANGLES}
  3755. angleRads := -angleRads;
  3756. {$ENDIF}
  3757. //nb: There's no point rotating about a specific point
  3758. //since the rotated image will be recentered.
  3759. NormalizeAngle(angleRads);
  3760. if IsEmpty or (angleRads = 0) then Exit;
  3761. if angleRads = angle180 then
  3762. begin
  3763. Rotate180; //because we've excluded 0 & 360 deg angles
  3764. end
  3765. else if angleRads = angle90 then
  3766. begin
  3767. RotateRight90;
  3768. end
  3769. else if angleRads = -angle90 then
  3770. begin
  3771. RotateLeft90;
  3772. end else
  3773. begin
  3774. mat := IdentityMatrix;
  3775. // the rotation point isn't important
  3776. // because AffineTransformImage() will
  3777. // will resize and recenter the image
  3778. MatrixRotate(mat, NullPointD, angleRads);
  3779. AffineTransformImage(self, mat);
  3780. end;
  3781. end;
  3782. //------------------------------------------------------------------------------
  3783. procedure TImage32.RotateRect(const rec: TRect;
  3784. angleRads: double; eraseColor: TColor32 = 0);
  3785. var
  3786. tmp: TImage32;
  3787. rec2: TRect;
  3788. recWidth, recHeight: integer;
  3789. begin
  3790. recWidth := rec.Right - rec.Left;
  3791. recHeight := rec.Bottom - rec.Top;
  3792. //create a tmp image with a copy of the pixels inside rec ...
  3793. tmp := TImage32.Create(self, rec);
  3794. try
  3795. tmp.Rotate(angleRads);
  3796. //since rotating also resizes, get a centered
  3797. //(clipped) rect of the rotated pixels ...
  3798. rec2.Left := (tmp.Width - recWidth) div 2;
  3799. rec2.Top := (tmp.Height - recHeight) div 2;
  3800. rec2.Right := rec2.Left + recWidth;
  3801. rec2.Bottom := rec2.Top + recHeight;
  3802. //finally move the rotated rec back to the image ...
  3803. FillRect(rec, eraseColor);
  3804. CopyBlend(tmp, rec2, rec);
  3805. finally
  3806. tmp.Free;
  3807. end;
  3808. end;
  3809. //------------------------------------------------------------------------------
  3810. procedure TImage32.Skew(dx,dy: double);
  3811. var
  3812. mat: TMatrixD;
  3813. begin
  3814. if IsEmpty or ((dx = 0) and (dy = 0)) then Exit;
  3815. //limit skewing to twice the image's width and/or height
  3816. dx := ClampRange(dx, -2.0, 2.0);
  3817. dy := ClampRange(dy, -2.0, 2.0);
  3818. mat := IdentityMatrix;
  3819. MatrixSkew(mat, dx, dy);
  3820. AffineTransformImage(self, mat);
  3821. end;
  3822. //------------------------------------------------------------------------------
  3823. procedure TImage32.ScaleAlpha(scale: double);
  3824. var
  3825. i: Integer;
  3826. pb: PARGB;
  3827. begin
  3828. pb := PARGB(PixelBase);
  3829. for i := 0 to Width * Height - 1 do
  3830. begin
  3831. pb.A := ClampByte(Integer(Round(pb.A * scale)));
  3832. inc(pb);
  3833. end;
  3834. Changed;
  3835. end;
  3836. //------------------------------------------------------------------------------
  3837. // TImageList32
  3838. //------------------------------------------------------------------------------
  3839. constructor TImageList32.Create;
  3840. begin
  3841. {$IFDEF XPLAT_GENERICS}
  3842. fList := TList<TImage32>.Create;
  3843. {$ELSE}
  3844. fList := TList.Create;
  3845. {$ENDIF}
  3846. fIsImageOwner := true;
  3847. end;
  3848. //------------------------------------------------------------------------------
  3849. destructor TImageList32.Destroy;
  3850. begin
  3851. Clear;
  3852. fList.Free;
  3853. inherited;
  3854. end;
  3855. //------------------------------------------------------------------------------
  3856. function TImageList32.Count: integer;
  3857. begin
  3858. result := fList.Count;
  3859. end;
  3860. //------------------------------------------------------------------------------
  3861. procedure TImageList32.Clear;
  3862. var
  3863. i: integer;
  3864. begin
  3865. if IsImageOwner then
  3866. for i := 0 to fList.Count -1 do
  3867. TImage32(fList[i]).Free;
  3868. fList.Clear;
  3869. end;
  3870. //------------------------------------------------------------------------------
  3871. function TImageList32.GetImage(index: integer): TImage32;
  3872. begin
  3873. result := TImage32(fList[index]);
  3874. end;
  3875. //------------------------------------------------------------------------------
  3876. procedure TImageList32.SetImage(index: integer; img: TIMage32);
  3877. begin
  3878. if fIsImageOwner then TImage32(fList[index]).Free;
  3879. fList[index] := img;
  3880. end;
  3881. //------------------------------------------------------------------------------
  3882. function TImageList32.GetLast: TImage32;
  3883. begin
  3884. if Count = 0 then Result := nil
  3885. else Result := TImage32(fList[Count -1]);
  3886. end;
  3887. //------------------------------------------------------------------------------
  3888. procedure TImageList32.Add(image: TImage32);
  3889. begin
  3890. fList.Add(image);
  3891. end;
  3892. //------------------------------------------------------------------------------
  3893. function TImageList32.Add(width, height: integer): TImage32;
  3894. begin
  3895. Result := TImage32.create(width, height);
  3896. fList.Add(Result);
  3897. end;
  3898. //------------------------------------------------------------------------------
  3899. procedure TImageList32.Insert(index: integer; image: TImage32);
  3900. begin
  3901. fList.Insert(index, image);
  3902. end;
  3903. //------------------------------------------------------------------------------
  3904. procedure TImageList32.Move(currentIndex, newIndex: integer);
  3905. begin
  3906. fList.Move(currentIndex, newIndex);
  3907. end;
  3908. //------------------------------------------------------------------------------
  3909. procedure TImageList32.Delete(index: integer);
  3910. begin
  3911. if fIsImageOwner then TImage32(fList[index]).Free;
  3912. fList.Delete(index);
  3913. end;
  3914. //------------------------------------------------------------------------------
  3915. // TImageFormat methods
  3916. //------------------------------------------------------------------------------
  3917. function TImageFormat.LoadFromFile(const filename: string;
  3918. img32: TImage32): Boolean;
  3919. var
  3920. fs: TFileStream;
  3921. begin
  3922. result := FileExists(filename);
  3923. if not result then Exit;
  3924. fs := TFileStream.Create(filename, fmOpenRead or fmShareDenyWrite);
  3925. try
  3926. Result := LoadFromStream(fs, img32);
  3927. finally
  3928. fs.Free;
  3929. end;
  3930. end;
  3931. //------------------------------------------------------------------------------
  3932. function TImageFormat.SaveToFile(const filename: string;
  3933. img32: TImage32; quality: integer): Boolean;
  3934. var
  3935. fs: TFileStream;
  3936. begin
  3937. result := (pos('.', filename) = 1) or
  3938. DirectoryExists(ExtractFilePath(filename));
  3939. if not result then Exit;
  3940. fs := TFileStream.Create(filename, fmCreate);
  3941. try
  3942. SaveToStream(fs, img32, quality);
  3943. finally
  3944. fs.Free;
  3945. end;
  3946. end;
  3947. //------------------------------------------------------------------------------
  3948. class function TImageFormat.CanCopyToClipboard: Boolean;
  3949. begin
  3950. Result := false;
  3951. end;
  3952. //------------------------------------------------------------------------------
  3953. class function TImageFormat.GetImageCount(stream: TStream): integer;
  3954. begin
  3955. Result := 1;
  3956. end;
  3957. //------------------------------------------------------------------------------
  3958. // TInterfacedObj
  3959. //------------------------------------------------------------------------------
  3960. {$IFDEF FPC}
  3961. function TInterfacedObj._AddRef: Integer;
  3962. {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
  3963. begin
  3964. Result := -1;
  3965. end;
  3966. //------------------------------------------------------------------------------
  3967. function TInterfacedObj._Release: Integer;
  3968. {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
  3969. begin
  3970. Result := -1;
  3971. end;
  3972. //------------------------------------------------------------------------------
  3973. function TInterfacedObj.QueryInterface(
  3974. {$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} iid : tguid;
  3975. out obj) : longint;
  3976. begin
  3977. if GetInterface(IID, Obj) then Result := 0
  3978. else Result := E_NOINTERFACE;
  3979. end;
  3980. {$ELSE}
  3981. function TInterfacedObj._AddRef: Integer; stdcall;
  3982. begin
  3983. Result := -1;
  3984. end;
  3985. //------------------------------------------------------------------------------
  3986. function TInterfacedObj._Release: Integer; stdcall;
  3987. begin
  3988. Result := -1;
  3989. end;
  3990. //------------------------------------------------------------------------------
  3991. function TInterfacedObj.QueryInterface(const IID: TGUID;
  3992. out Obj): HResult;
  3993. begin
  3994. if GetInterface(IID, Obj) then Result := 0
  3995. else Result := E_NOINTERFACE;
  3996. end;
  3997. {$ENDIF}
  3998. //------------------------------------------------------------------------------
  3999. // Initialization and Finalization functions
  4000. //------------------------------------------------------------------------------
  4001. procedure MakeBlendTables;
  4002. var
  4003. i,j: Integer;
  4004. begin
  4005. for j := 0 to 255 do MulTable[0, j] := 0;
  4006. for i := 0 to 255 do MulTable[i, 0] := 0;
  4007. for j := 0 to 255 do DivTable[0, j] := 0;
  4008. for i := 0 to 255 do DivTable[i, 0] := 0;
  4009. for i := 1 to 255 do
  4010. begin
  4011. for j := 1 to 255 do
  4012. begin
  4013. MulTable[i, j] := Round(i * j * div255);
  4014. if i >= j then
  4015. DivTable[i, j] := 255 else
  4016. DivTable[i, j] := Round(i * $FF / j);
  4017. end;
  4018. end;
  4019. Sigmoid[128] := 128;
  4020. for i := 1 to 127 do
  4021. Sigmoid[128+i] := 128 + Round(127 * sin(angle90 * i/127));
  4022. for i := 0 to 127 do
  4023. Sigmoid[i] := 255- Sigmoid[255-i];
  4024. end;
  4025. //------------------------------------------------------------------------------
  4026. {$IFDEF MSWINDOWS}
  4027. procedure GetScreenScale;
  4028. var
  4029. dc: HDC;
  4030. ScreenPixelsY: integer;
  4031. begin
  4032. dc := GetDC(0);
  4033. try
  4034. ScreenPixelsY := GetDeviceCaps(dc, LOGPIXELSY);
  4035. DpiAwareOne := ScreenPixelsY / 96;
  4036. finally
  4037. ReleaseDC(0, dc);
  4038. end;
  4039. dpiAware1 := Round(DpiAwareOne);
  4040. end;
  4041. {$ENDIF}
  4042. //------------------------------------------------------------------------------
  4043. procedure CleanUpImageFormatClassList;
  4044. var
  4045. i: integer;
  4046. begin
  4047. for i := ImageFormatClassList.Count -1 downto 0 do
  4048. Dispose(PImgFmtRec(ImageFormatClassList[i]));
  4049. ImageFormatClassList.Free;
  4050. end;
  4051. //------------------------------------------------------------------------------
  4052. //------------------------------------------------------------------------------
  4053. procedure CreateResamplerList;
  4054. begin
  4055. {$IFDEF XPLAT_GENERICS}
  4056. ResamplerList := TList<TResamplerObj>.Create;
  4057. {$ELSE}
  4058. ResamplerList := TList.Create;
  4059. {$ENDIF}
  4060. end;
  4061. //------------------------------------------------------------------------------
  4062. function GetResampler(id: integer): TResamplerFunction;
  4063. var
  4064. i: integer;
  4065. begin
  4066. result := nil;
  4067. if not Assigned(ResamplerList) then Exit;
  4068. for i := ResamplerList.Count -1 downto 0 do
  4069. if TResamplerObj(ResamplerList[i]).id = id then
  4070. begin
  4071. Result := TResamplerObj(ResamplerList[i]).func;
  4072. Break;
  4073. end;
  4074. end;
  4075. //------------------------------------------------------------------------------
  4076. function RegisterResampler(func: TResamplerFunction; const name: string): integer;
  4077. var
  4078. resampleObj: TResamplerObj;
  4079. begin
  4080. if not Assigned(ResamplerList) then
  4081. CreateResamplerList;
  4082. resampleObj := TResamplerObj.Create;
  4083. Result := ResamplerList.Add(resampleObj) +1;
  4084. resampleObj.id := Result;
  4085. resampleObj.name := name;
  4086. resampleObj.func := func;
  4087. end;
  4088. //------------------------------------------------------------------------------
  4089. procedure GetResamplerList(stringList: TStringList);
  4090. var
  4091. i: integer;
  4092. resampleObj: TResamplerObj;
  4093. begin
  4094. stringList.Clear;
  4095. stringList.Capacity := ResamplerList.Count;
  4096. for i := 0 to ResamplerList.Count -1 do
  4097. begin
  4098. resampleObj := ResamplerList[i];
  4099. stringList.AddObject(resampleObj.name, resampleObj);
  4100. end;
  4101. end;
  4102. //------------------------------------------------------------------------------
  4103. procedure CleanUpResamplerClassList;
  4104. var
  4105. i: integer;
  4106. begin
  4107. if not Assigned(ResamplerList) then Exit;
  4108. for i := ResamplerList.Count -1 downto 0 do
  4109. TResamplerObj(ResamplerList[i]).Free;
  4110. ResamplerList.Free;
  4111. end;
  4112. //------------------------------------------------------------------------------
  4113. initialization
  4114. CreateImageFormatList;
  4115. MakeBlendTables;
  4116. {$IFDEF MSWINDOWS}
  4117. GetScreenScale;
  4118. {$ENDIF}
  4119. finalization
  4120. CleanUpImageFormatClassList;
  4121. CleanUpResamplerClassList;
  4122. end.