| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693 |
- unit GIFImage;
- ////////////////////////////////////////////////////////////////////////////////
- // //
- // Project: GIF Graphics Object //
- // Module: gifimage //
- // Description: TGraphic implementation of the GIF89a graphics format //
- // Version: 2.2 //
- // Release: 5 //
- // Date: 23-MAY-1999 //
- // Target: Win32, Delphi 2, 3, 4 & 5, C++ Builder 3 & 4 //
- // Author(s): anme: Anders Melander, anders@melander.dk //
- // fila: Filip Larsen //
- // rps: Reinier Sterkenburg //
- // Copyright: (c) 1997-99 Anders Melander. //
- // All rights reserved. //
- // Formatting: 2 space indent, 8 space tabs, 80 columns. //
- // //
- ////////////////////////////////////////////////////////////////////////////////
- // Changed 2001.07.23 by Finn Tolderlund: //
- // Changed according to e-mail from "Rolf Frei" <rolf@eicom.ch> //
- // on 2001.07.23 so that it works in Delphi 6. //
- // //
- // Changed 2002.07.07 by Finn Tolderlund: //
- // Incorporated additional modifications by Alexey Barkovoy (clootie@reactor.ru)
- // found in his Delphi 6 GifImage.pas (from 22-Dec-2001). //
- // Alexey Barkovoy's Delphi 6 gifimage.pas can be downloaded from //
- // http://clootie.narod.ru/delphi/download_vcl.html //
- // These changes made showing of animated gif files more stable. The code //
- // from 2001.07.23 could crash sometimes with an Execption EAccessViolation. //
- // //
- // Changed 2002.10.06 by Finn Tolderlund: //
- // Delphi 7 compatible. //
- // //
- // Changed 2003-03-06 by Finn Tolderlund: //
- // Changes made as a result of postings in borland.public.delphi.graphics //
- // from 2003-02-28 to 2003-03-05 where white (255,255,255) in a bitmap //
- // was converted to (254,254,254) in the gif. //
- // The doCreateOptimizedPaletteFromSingleBitmap function and //
- // the CreateOptimizedPaletteFromManyBitmaps function is changed so that //
- // the correct offset 246 is used instead of 245. //
- // The ReduceColors function is changed according to Anders Melander's post //
- // so that a colour get converted to the precise colour if that colour is //
- // present in the palette when using ColorReduction rmQuantize. //
- // //
- // Changed 2003-03-09 by Finn Tolderlund: //
- // Delphi 7 version is now assumed if unknown compiler version is unknown //
- // for better compatibility with future Delphi versions. //
- // Hopefully this code is now compatible with future Delphi versions, //
- // unless Borland makes some changes that breaks existing code. //
- // //
- // Changed 2003-08-04 by Finn Tolderlund: //
- // Changed procedure AddMaskOnly so that it doesn't leak a GDI HBitmap-object //
- // and it doesn't release the handle of the source bitmap which //
- // is used to assign to the GIF object as in gif.assign(bm); //
- // These changes were made as a result of a news post made by Renate Schaaf //
- // with the subject "TGifImage HBitmap leak on assign?" //
- // in borland.public.delphi.graphics on Mon 28 Jul 2003 and Sun 03 Aug 2003. //
- // //
- // Changed 2004.03.09 by Finn Tolderlund: //
- // Added a ForceFrame property to the TGIFImage class. //
- // The ForceFrame property can be used to make TGIFImage display a apecific //
- // sub frame from an animated gif. //
- // How to use: Set the Animate property to False and set the ForceFrame //
- // property to a desired frame number (0-N) //
- // Normal display: Set the ForceFrame property to -1 and set Animate to True. //
- // If ForceFrame is negative TGIFImage behaves just as before this change. //
- // Note that if the sub frame in the gif only contains part of the image //
- // (i.e. only the changes from previous frames) the result is unpredictable. //
- // The result is best if each sub frame contains a whole image. //
- // If the sub frame is transparent the background is not automatically //
- // restored, you must do so yourself if you want that. //
- // If you are using a TImage to display the gif you can use //
- // Image.Parent.Invalidate or Image.Parent.Refresh to restore the background. //
- // This change was made as a result of a email correspondance with //
- // Tineke Kosmis (http://www.classe.nl/) which requested such a property. //
- // //
- ////////////////////////////////////////////////////////////////////////////////
- // //
- // Please read the "Conditions of use" in the release notes. //
- // //
- ////////////////////////////////////////////////////////////////////////////////
- // Known problems:
- //
- // * The combination of buffered, tiled and transparent draw will display the
- // background incorrectly (scaled).
- // If this is a problem for you, use non-buffered (goDirectDraw) drawing
- // instead.
- //
- // * The combination of non-buffered, transparent and stretched draw is
- // sometimes distorted with a pattern effect when the image is displayed
- // smaller than the real size (shrinked).
- //
- // * Buffered display flickers when TGIFImage is used by a transparent TImage
- // component.
- // This is a problem with TImage caused by the fact that TImage was designed
- // with static images in mind. Not much I can do about it.
- //
- ////////////////////////////////////////////////////////////////////////////////
- // To do (in rough order of priority):
- // { TODO -oanme -cFeature : TImage hook for destroy notification. }
- // { TODO -oanme -cFeature : TBitmap pool to limit resource consumption on Win95/98. }
- // { TODO -oanme -cImprovement : Make BitsPerPixel property writable. }
- // { TODO -oanme -cFeature : Visual GIF component. }
- // { TODO -oanme -cImprovement : Easier method to determine DrawPainter status. }
- // { TODO -oanme -cFeature : Import to 256+ color GIF. }
- // { TODO -oanme -cFeature : Make some of TGIFImage's properties persistent (DrawOptions etc). }
- // { TODO -oanme -cFeature : Add TGIFImage.Persistent property. Should save published properties in application extension when this options is set. }
- // { TODO -oanme -cBugFix : Solution for background buffering in scrollbox. }
- //
- //////////////////////////////////////////////////////////////////////////////////
- {$ifdef BCB}
- {$ObjExportAll On}
- {$endif}
- interface
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Conditional Compiler Symbols
- //
- ////////////////////////////////////////////////////////////////////////////////
- (*
- DEBUG Must be defined if any of the DEBUG_xxx
- symbols are defined.
- If the symbol is defined the source will not be
- optimized and overflow- and range checks will be
- enabled.
- DEBUG_HASHPERFORMANCE Calculates hash table performance data.
- DEBUG_HASHFILLFACTOR Calculates fill factor of hash table -
- Interferes with DEBUG_HASHPERFORMANCE.
- DEBUG_COMPRESSPERFORMANCE Calculates LZW compressor performance data.
- DEBUG_DECOMPRESSPERFORMANCE Calculates LZW decompressor performance data.
- DEBUG_DITHERPERFORMANCE Calculates color reduction performance data.
- DEBUG_DRAWPERFORMANCE Calculates low level drawing performance data.
- The performance data for DEBUG_DRAWPERFORMANCE
- will be displayed when you press the Ctrl key.
- DEBUG_RENDERPERFORMANCE Calculates performance data for the GIF to
- bitmap converter.
- The performance data for DEBUG_DRAWPERFORMANCE
- will be displayed when you press the Ctrl key.
- GIF_NOSAFETY Define this symbol to disable overflow- and
- range checks.
- Ignored if the DEBUG symbol is defined.
- STRICT_MOZILLA Define to mimic Mozilla as closely as possible.
- If not defined, a slightly more "optimal"
- implementation is used (IMHO).
- FAST_AS_HELL Define this symbol to use strictly GIF compliant
- (but too fast) animation timing.
- Since our paint routines are much faster and
- more precise timed than Mozilla's, the standard
- GIF and Mozilla values causes animations to loop
- faster than they would in Mozilla.
- If the symbol is _not_ defined, an alternative
- set of tweaked timing values will be used.
- The tweaked values are not optimal but are based
- on tests performed on my reference system:
- - Windows 95
- - 133 MHz Pentium
- - 64Mb RAM
- - Diamond Stealth64/V3000
- - 1600*1200 in 256 colors
- The alternate values can be modified if you are
- not satisfied with my defaults (they can be
- found a few pages down).
- REGISTER_TGIFIMAGE Define this symbol to register TGIFImage with
- the TPicture class and integrate with TImage.
- This is required to be able to display GIFs in
- the TImage component.
- The symbol is defined by default.
- Undefine if you use another GIF library to
- provide GIF support for TImage.
- PIXELFORMAT_TOO_SLOW When this symbol is defined, the internal
- PixelFormat routines are used in some places
- instead of TBitmap.PixelFormat.
- The current implementation (Delphi4, Builder 3)
- of TBitmap.PixelFormat can in some situation
- degrade performance.
- The symbol is defined by default.
- CREATEDIBSECTION_SLOW If this symbol is defined, TDIBWriter will
- use global memory as scanline storage, instead
- of a DIB section.
- Benchmarks have shown that a DIB section is
- twice as slow as global memory.
- The symbol is defined by default.
- The symbol requires that PIXELFORMAT_TOO_SLOW
- is defined.
- SERIALIZE_RENDER Define this symbol to serialize threaded
- GIF to bitmap rendering.
- When a GIF is displayed with the goAsync option
- (the default), the GIF to bitmap rendering is
- executed in the context of the draw thread.
- If more than one thread is drawing the same GIF
- or the GIF is being modified while it is
- animating, the GIF to bitmap rendering should be
- serialized to guarantee that the bitmap isn't
- modified by more than one thread at a time. If
- SERIALIZE_RENDER is defined, the draw threads
- uses TThread.Synchronize to serialize GIF to
- bitmap rendering.
- *)
- {$DEFINE REGISTER_TGIFIMAGE}
- {$DEFINE PIXELFORMAT_TOO_SLOW}
- {$DEFINE CREATEDIBSECTION_SLOW}
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Determine Delphi and C++ Builder version
- //
- ////////////////////////////////////////////////////////////////////////////////
- // Delphi 1.x
- {$IFDEF VER80}
- 'Error: TGIFImage does not support Delphi 1.x'
- {$ENDIF}
- // Delphi 2.x
- {$IFDEF VER90}
- {$DEFINE VER9x}
- {$ENDIF}
- // C++ Builder 1.x
- {$IFDEF VER93}
- // Good luck...
- {$DEFINE VER9x}
- {$ENDIF}
- // Delphi 3.x
- {$IFDEF VER100}
- {$DEFINE VER10_PLUS}
- {$DEFINE D3_BCB3}
- {$ENDIF}
- // C++ Builder 3.x
- {$IFDEF VER110}
- {$DEFINE VER10_PLUS}
- {$DEFINE VER11_PLUS}
- {$DEFINE D3_BCB3}
- {$DEFINE BAD_STACK_ALIGNMENT}
- {$ENDIF}
- // Delphi 4.x
- {$IFDEF VER120}
- {$DEFINE VER10_PLUS}
- {$DEFINE VER11_PLUS}
- {$DEFINE VER12_PLUS}
- {$DEFINE BAD_STACK_ALIGNMENT}
- {$ENDIF}
- // C++ Builder 4.x
- {$IFDEF VER125}
- {$DEFINE VER10_PLUS}
- {$DEFINE VER11_PLUS}
- {$DEFINE VER12_PLUS}
- {$DEFINE VER125_PLUS}
- {$DEFINE BAD_STACK_ALIGNMENT}
- {$ENDIF}
- // Delphi 5.x
- {$IFDEF VER130}
- {$DEFINE VER10_PLUS}
- {$DEFINE VER11_PLUS}
- {$DEFINE VER12_PLUS}
- {$DEFINE VER125_PLUS}
- {$DEFINE VER13_PLUS}
- {$DEFINE BAD_STACK_ALIGNMENT}
- {$ENDIF}
- // Delphi 6.x
- {$IFDEF VER140}
- {$WARN SYMBOL_PLATFORM OFF}
- {$DEFINE VER10_PLUS}
- {$DEFINE VER11_PLUS}
- {$DEFINE VER12_PLUS}
- {$DEFINE VER125_PLUS}
- {$DEFINE VER13_PLUS}
- {$DEFINE VER14_PLUS}
- {$DEFINE BAD_STACK_ALIGNMENT}
- {$ENDIF}
- // Delphi 7.x
- {$IFDEF VER150}
- {$WARN SYMBOL_PLATFORM OFF}
- {$DEFINE VER10_PLUS}
- {$DEFINE VER11_PLUS}
- {$DEFINE VER12_PLUS}
- {$DEFINE VER125_PLUS}
- {$DEFINE VER13_PLUS}
- {$DEFINE VER14_PLUS}
- {$DEFINE VER15_PLUS}
- {$DEFINE BAD_STACK_ALIGNMENT}
- {$ENDIF}
- // 2003.03.09 ->
- // Unknown compiler version - assume D4 compatible
- //{$IFNDEF VER9x}
- // {$IFNDEF VER10_PLUS}
- // {$DEFINE VER10_PLUS}
- // {$DEFINE VER11_PLUS}
- // {$DEFINE VER12_PLUS}
- // {$DEFINE BAD_STACK_ALIGNMENT}
- // {$ENDIF}
- //{$ENDIF}
- // 2003.03.09 <-
- // 2003.03.09 ->
- // Unknown compiler version - assume D7 compatible
- {$IFNDEF VER9x}
- {$IFNDEF VER10_PLUS}
- {$WARN SYMBOL_PLATFORM OFF}
- {$DEFINE VER10_PLUS}
- {$DEFINE VER11_PLUS}
- {$DEFINE VER12_PLUS}
- {$DEFINE VER125_PLUS}
- {$DEFINE VER13_PLUS}
- {$DEFINE VER14_PLUS}
- {$DEFINE VER15_PLUS}
- {$DEFINE BAD_STACK_ALIGNMENT}
- {$ENDIF}
- {$ENDIF}
- // 2003.03.09 <-
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Compiler Options required to compile this library
- //
- ////////////////////////////////////////////////////////////////////////////////
- {$A+,B-,H+,J+,K-,M-,T-,X+}
- // Debug control - You can safely change these settings
- {$IFDEF DEBUG}
- {$C+} // ASSERTIONS
- {$O-} // OPTIMIZATION
- {$Q+} // OVERFLOWCHECKS
- {$R+} // RANGECHECKS
- {$ELSE}
- {$C-} // ASSERTIONS
- {$IFDEF GIF_NOSAFETY}
- {$Q-}// OVERFLOWCHECKS
- {$R-}// RANGECHECKS
- {$ENDIF}
- {$ENDIF}
- // Special options for Time2Help parser
- {$ifdef TIME2HELP}
- {$UNDEF PIXELFORMAT_TOO_SLOW}
- {$endif}
- ////////////////////////////////////////////////////////////////////////////////
- //
- // External dependecies
- //
- ////////////////////////////////////////////////////////////////////////////////
- uses
- sysutils,
- Windows,
- Graphics,
- Classes;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFImage library version
- //
- ////////////////////////////////////////////////////////////////////////////////
- const
- GIFVersion = $0202;
- GIFVersionMajor = 2;
- GIFVersionMinor = 2;
- GIFVersionRelease = 5;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Misc constants and support types
- //
- ////////////////////////////////////////////////////////////////////////////////
- const
- GIFMaxColors = 256; // Max number of colors supported by GIF
- // Don't bother changing this value!
- BitmapAllocationThreshold = 500000; // Bitmap pixel count limit at which
- // a newly allocated bitmap will be
- // converted to 1 bit format before
- // being resized and converted to 8 bit.
- var
- {$IFDEF FAST_AS_HELL}
- GIFDelayExp: integer = 10; // Delay multiplier in mS.
- {$ELSE}
- GIFDelayExp: integer = 12; // Delay multiplier in mS. Tweaked.
- {$ENDIF}
- // * GIFDelayExp:
- // The following delay values should all
- // be multiplied by this value to
- // calculate the effective time (in mS).
- // According to the GIF specs, this
- // value should be 10.
- // Since our paint routines are much
- // faster than Mozilla's, you might need
- // to increase this value if your
- // animations loops too fast. The
- // optimal value is impossible to
- // determine since it depends on the
- // speed of the CPU, the viceo card,
- // memory and many other factors.
- GIFDefaultDelay: integer = 10; // * GIFDefaultDelay:
- // Default animation delay.
- // This value is used if no GCE is
- // defined.
- // (10 = 100 mS)
- {$IFDEF FAST_AS_HELL}
- GIFMinimumDelay: integer = 1; // Minimum delay (from Mozilla source).
- // (1 = 10 mS)
- {$ELSE}
- GIFMinimumDelay: integer = 3; // Minimum delay - Tweaked.
- {$ENDIF}
- // * GIFMinimumDelay:
- // The minumum delay used in the Mozilla
- // source is 10mS. This corresponds to a
- // value of 1. However, since our paint
- // routines are much faster than
- // Mozilla's, a value of 3 or 4 gives
- // better results.
- GIFMaximumDelay: integer = 1000; // * GIFMaximumDelay:
- // Maximum delay when painter is running
- // in main thread (goAsync is not set).
- // This value guarantees that a very
- // long and slow GIF does not hang the
- // system.
- // (1000 = 10000 mS = 10 Seconds)
- type
- TGIFVersion = (gvUnknown, gv87a, gv89a);
- TGIFVersionRec = array[0..2] of char;
- const
- GIFVersions : array[gv87a..gv89a] of TGIFVersionRec = ('87a', '89a');
- type
- // TGIFImage mostly throws exceptions of type GIFException
- GIFException = class(EInvalidGraphic);
- // Severity level as indicated in the Warning methods and the OnWarning event
- TGIFSeverity = (gsInfo, gsWarning, gsError);
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Delphi 2.x support
- //
- ////////////////////////////////////////////////////////////////////////////////
- {$IFDEF VER9x}
- // Delphi 2 doesn't support TBitmap.PixelFormat
- {$DEFINE PIXELFORMAT_TOO_SLOW}
- type
- // TThreadList from Delphi 3 classes.pas
- TThreadList = class
- private
- FList: TList;
- FLock: TRTLCriticalSection;
- public
- constructor Create;
- destructor Destroy; override;
- procedure Add(Item: Pointer);
- procedure Clear;
- function LockList: TList;
- procedure Remove(Item: Pointer);
- procedure UnlockList;
- end;
- // From Delphi 3 sysutils.pas
- EOutOfMemory = class(Exception);
- // From Delphi 3 classes.pas
- EOutOfResources = class(EOutOfMemory);
- // From Delphi 3 windows.pas
- PMaxLogPalette = ^TMaxLogPalette;
- TMaxLogPalette = packed record
- palVersion: Word;
- palNumEntries: Word;
- palPalEntry: array [Byte] of TPaletteEntry;
- end; { TMaxLogPalette }
- // From Delphi 3 graphics.pas. Used by the D3 TGraphic class.
- TProgressStage = (psStarting, psRunning, psEnding);
- TProgressEvent = procedure (Sender: TObject; Stage: TProgressStage;
- PercentDone: Byte; RedrawNow: Boolean; const R: TRect; const Msg: string) of object;
- // From Delphi 3 windows.pas
- PRGBTriple = ^TRGBTriple;
- {$ENDIF}
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Forward declarations
- //
- ////////////////////////////////////////////////////////////////////////////////
- type
- TGIFImage = class;
- TGIFSubImage = class;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFItem
- //
- ////////////////////////////////////////////////////////////////////////////////
- TGIFItem = class(TPersistent)
- private
- FGIFImage: TGIFImage;
- protected
- function GetVersion: TGIFVersion; virtual;
- procedure Warning(Severity: TGIFSeverity; Message: string); virtual;
- public
- constructor Create(GIFImage: TGIFImage); virtual;
- procedure SaveToStream(Stream: TStream); virtual; abstract;
- procedure LoadFromStream(Stream: TStream); virtual; abstract;
- procedure SaveToFile(const Filename: string); virtual;
- procedure LoadFromFile(const Filename: string); virtual;
- property Version: TGIFVersion read GetVersion;
- property Image: TGIFImage read FGIFImage;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFList
- //
- ////////////////////////////////////////////////////////////////////////////////
- TGIFList = class(TPersistent)
- private
- FItems: TList;
- FImage: TGIFImage;
- protected
- function GetItem(Index: Integer): TGIFItem;
- procedure SetItem(Index: Integer; Item: TGIFItem);
- function GetCount: Integer;
- procedure Warning(Severity: TGIFSeverity; Message: string); virtual;
- public
- constructor Create(Image: TGIFImage);
- destructor Destroy; override;
- function Add(Item: TGIFItem): Integer;
- procedure Clear;
- procedure Delete(Index: Integer);
- procedure Exchange(Index1, Index2: Integer);
- function First: TGIFItem;
- function IndexOf(Item: TGIFItem): Integer;
- procedure Insert(Index: Integer; Item: TGIFItem);
- function Last: TGIFItem;
- procedure Move(CurIndex, NewIndex: Integer);
- function Remove(Item: TGIFItem): Integer;
- procedure SaveToStream(Stream: TStream); virtual;
- procedure LoadFromStream(Stream: TStream; Parent: TObject); virtual; abstract;
- property Items[Index: Integer]: TGIFItem read GetItem write SetItem; default;
- property Count: Integer read GetCount;
- property List: TList read FItems;
- property Image: TGIFImage read FImage;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFColorMap
- //
- ////////////////////////////////////////////////////////////////////////////////
- // One way to do it:
- // TBaseColor = (bcRed, bcGreen, bcBlue);
- // TGIFColor = array[bcRed..bcBlue] of BYTE;
- // Another way:
- TGIFColor = packed record
- Red: byte;
- Green: byte;
- Blue: byte;
- end;
- TColorMap = packed array[0..GIFMaxColors-1] of TGIFColor;
- PColorMap = ^TColorMap;
- TUsageCount = record
- Count : integer; // # of pixels using color index
- Index : integer; // Color index
- end;
- TColormapHistogram = array[0..255] of TUsageCount;
- TColormapReverse = array[0..255] of byte;
- TGIFColorMap = class(TPersistent)
- private
- FColorMap : PColorMap;
- FCount : integer;
- FCapacity : integer;
- FOptimized : boolean;
- protected
- function GetColor(Index: integer): TColor;
- procedure SetColor(Index: integer; Value: TColor);
- function GetBitsPerPixel: integer;
- function DoOptimize: boolean;
- procedure SetCapacity(Size: integer);
- procedure Warning(Severity: TGIFSeverity; Message: string); virtual; abstract;
- procedure BuildHistogram(var Histogram: TColormapHistogram); virtual; abstract;
- procedure MapImages(var Map: TColormapReverse); virtual; abstract;
- public
- constructor Create;
- destructor Destroy; override;
- class function Color2RGB(Color: TColor): TGIFColor;
- class function RGB2Color(Color: TGIFColor): TColor;
- procedure SaveToStream(Stream: TStream);
- procedure LoadFromStream(Stream: TStream; Count: integer);
- procedure Assign(Source: TPersistent); override;
- function IndexOf(Color: TColor): integer;
- function Add(Color: TColor): integer;
- function AddUnique(Color: TColor): integer;
- procedure Delete(Index: integer);
- procedure Clear;
- function Optimize: boolean; virtual; abstract;
- procedure Changed; virtual; abstract;
- procedure ImportPalette(Palette: HPalette);
- procedure ImportColorTable(Pal: pointer; Count: integer);
- procedure ImportDIBColors(Handle: HDC);
- procedure ImportColorMap(Map: TColorMap; Count: integer);
- function ExportPalette: HPalette;
- property Colors[Index: integer]: TColor read GetColor write SetColor; default;
- property Data: PColorMap read FColorMap;
- property Count: integer read FCount;
- property Optimized: boolean read FOptimized write FOptimized;
- property BitsPerPixel: integer read GetBitsPerPixel;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFHeader
- //
- ////////////////////////////////////////////////////////////////////////////////
- TLogicalScreenDescriptor = packed record
- ScreenWidth: word; { logical screen width }
- ScreenHeight: word; { logical screen height }
- PackedFields: byte; { packed fields }
- BackgroundColorIndex: byte; { index to global color table }
- AspectRatio: byte; { actual ratio = (AspectRatio + 15) / 64 }
- end;
- TGIFHeader = class(TGIFItem)
- private
- FLogicalScreenDescriptor: TLogicalScreenDescriptor;
- FColorMap : TGIFColorMap;
- procedure Prepare;
- protected
- function GetVersion: TGIFVersion; override;
- function GetBackgroundColor: TColor;
- procedure SetBackgroundColor(Color: TColor);
- procedure SetBackgroundColorIndex(Index: BYTE);
- function GetBitsPerPixel: integer;
- function GetColorResolution: integer;
- public
- constructor Create(GIFImage: TGIFImage); override;
- destructor Destroy; override;
- procedure Assign(Source: TPersistent); override;
- procedure SaveToStream(Stream: TStream); override;
- procedure LoadFromStream(Stream: TStream); override;
- procedure Clear;
- property Version: TGIFVersion read GetVersion;
- property Width: WORD read FLogicalScreenDescriptor.ScreenWidth
- write FLogicalScreenDescriptor.ScreenWidth;
- property Height: WORD read FLogicalScreenDescriptor.ScreenHeight
- write FLogicalScreenDescriptor.Screenheight;
- property BackgroundColorIndex: BYTE read FLogicalScreenDescriptor.BackgroundColorIndex
- write SetBackgroundColorIndex;
- property BackgroundColor: TColor read GetBackgroundColor
- write SetBackgroundColor;
- property AspectRatio: BYTE read FLogicalScreenDescriptor.AspectRatio
- write FLogicalScreenDescriptor.AspectRatio;
- property ColorMap: TGIFColorMap read FColorMap;
- property BitsPerPixel: integer read GetBitsPerPixel;
- property ColorResolution: integer read GetColorResolution;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFExtension
- //
- ////////////////////////////////////////////////////////////////////////////////
- TGIFExtensionType = BYTE;
- TGIFExtension = class;
- TGIFExtensionClass = class of TGIFExtension;
- TGIFGraphicControlExtension = class;
- TGIFExtension = class(TGIFItem)
- private
- FSubImage: TGIFSubImage;
- protected
- function GetExtensionType: TGIFExtensionType; virtual; abstract;
- function GetVersion: TGIFVersion; override;
- function DoReadFromStream(Stream: TStream): TGIFExtensionType;
- class procedure RegisterExtension(elabel: BYTE; eClass: TGIFExtensionClass);
- class function FindExtension(Stream: TStream): TGIFExtensionClass;
- class function FindSubExtension(Stream: TStream): TGIFExtensionClass; virtual;
- public
- // Ignore compiler warning about hiding base class constructor
- constructor Create(ASubImage: TGIFSubImage); {$IFDEF VER12_PLUS} reintroduce; {$ENDIF} virtual;
- destructor Destroy; override;
- procedure SaveToStream(Stream: TStream); override;
- procedure LoadFromStream(Stream: TStream); override;
- property ExtensionType: TGIFExtensionType read GetExtensionType;
- property SubImage: TGIFSubImage read FSubImage;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFSubImage
- //
- ////////////////////////////////////////////////////////////////////////////////
- TGIFExtensionList = class(TGIFList)
- protected
- function GetExtension(Index: Integer): TGIFExtension;
- procedure SetExtension(Index: Integer; Extension: TGIFExtension);
- public
- procedure LoadFromStream(Stream: TStream; Parent: TObject); override;
- property Extensions[Index: Integer]: TGIFExtension read GetExtension write SetExtension; default;
- end;
- TImageDescriptor = packed record
- Separator: byte; { fixed value of ImageSeparator }
- Left: word; { Column in pixels in respect to left edge of logical screen }
- Top: word; { row in pixels in respect to top of logical screen }
- Width: word; { width of image in pixels }
- Height: word; { height of image in pixels }
- PackedFields: byte; { Bit fields }
- end;
- TGIFSubImage = class(TGIFItem)
- private
- FBitmap : TBitmap;
- FMask : HBitmap;
- FNeedMask : boolean;
- FLocalPalette : HPalette;
- FData : PChar;
- FDataSize : integer;
- FColorMap : TGIFColorMap;
- FImageDescriptor : TImageDescriptor;
- FExtensions : TGIFExtensionList;
- FTransparent : boolean;
- FGCE : TGIFGraphicControlExtension;
- procedure Prepare;
- procedure Compress(Stream: TStream);
- procedure Decompress(Stream: TStream);
- protected
- function GetVersion: TGIFVersion; override;
- function GetInterlaced: boolean;
- procedure SetInterlaced(Value: boolean);
- function GetColorResolution: integer;
- function GetBitsPerPixel: integer;
- procedure AssignTo(Dest: TPersistent); override;
- function DoGetBitmap: TBitmap;
- function DoGetDitherBitmap: TBitmap;
- function GetBitmap: TBitmap;
- procedure SetBitmap(Value: TBitmap);
- procedure FreeMask;
- function GetEmpty: Boolean;
- function GetPalette: HPALETTE;
- procedure SetPalette(Value: HPalette);
- function GetActiveColorMap: TGIFColorMap;
- function GetBoundsRect: TRect;
- procedure SetBoundsRect(const Value: TRect);
- procedure DoSetBounds(ALeft, ATop, AWidth, AHeight: integer);
- function GetClientRect: TRect;
- function GetPixel(x, y: integer): BYTE;
- function GetScanline(y: integer): pointer;
- procedure NewBitmap;
- procedure FreeBitmap;
- procedure NewImage;
- procedure FreeImage;
- procedure NeedImage;
- function ScaleRect(DestRect: TRect): TRect;
- function HasMask: boolean;
- function GetBounds(Index: integer): WORD;
- procedure SetBounds(Index: integer; Value: WORD);
- function GetHasBitmap: boolean;
- procedure SetHasBitmap(Value: boolean);
- public
- constructor Create(GIFImage: TGIFImage); override;
- destructor Destroy; override;
- procedure Clear;
- procedure SaveToStream(Stream: TStream); override;
- procedure LoadFromStream(Stream: TStream); override;
- procedure Assign(Source: TPersistent); override;
- procedure Draw(ACanvas: TCanvas; const Rect: TRect;
- DoTransparent, DoTile: boolean);
- procedure StretchDraw(ACanvas: TCanvas; const Rect: TRect;
- DoTransparent, DoTile: boolean);
- procedure Crop;
- procedure Merge(Previous: TGIFSubImage);
- property HasBitmap: boolean read GetHasBitmap write SetHasBitmap;
- property Left: WORD index 1 read GetBounds write SetBounds;
- property Top: WORD index 2 read GetBounds write SetBounds;
- property Width: WORD index 3 read GetBounds write SetBounds;
- property Height: WORD index 4 read GetBounds write SetBounds;
- property BoundsRect: TRect read GetBoundsRect write SetBoundsRect;
- property ClientRect: TRect read GetClientRect;
- property Interlaced: boolean read GetInterlaced write SetInterlaced;
- property ColorMap: TGIFColorMap read FColorMap;
- property ActiveColorMap: TGIFColorMap read GetActiveColorMap;
- property Data: PChar read FData;
- property DataSize: integer read FDataSize;
- property Extensions: TGIFExtensionList read FExtensions;
- property Version: TGIFVersion read GetVersion;
- property ColorResolution: integer read GetColorResolution;
- property BitsPerPixel: integer read GetBitsPerPixel;
- property Bitmap: TBitmap read GetBitmap write SetBitmap;
- property Mask: HBitmap read FMask;
- property Palette: HPALETTE read GetPalette write SetPalette;
- property Empty: boolean read GetEmpty;
- property Transparent: boolean read FTransparent;
- property GraphicControlExtension: TGIFGraphicControlExtension read FGCE;
- property Pixels[x, y: integer]: BYTE read GetPixel;
- property Scanline[y: integer]: pointer read GetScanline;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFTrailer
- //
- ////////////////////////////////////////////////////////////////////////////////
- TGIFTrailer = class(TGIFItem)
- procedure SaveToStream(Stream: TStream); override;
- procedure LoadFromStream(Stream: TStream); override;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFGraphicControlExtension
- //
- ////////////////////////////////////////////////////////////////////////////////
- // Graphic Control Extension block a.k.a GCE
- TGIFGCERec = packed record
- BlockSize: byte; { should be 4 }
- PackedFields: Byte;
- DelayTime: Word; { in centiseconds }
- TransparentColorIndex: Byte;
- Terminator: Byte;
- end;
- TDisposalMethod = (dmNone, dmNoDisposal, dmBackground, dmPrevious);
- TGIFGraphicControlExtension = class(TGIFExtension)
- private
- FGCExtension: TGIFGCERec;
- protected
- function GetExtensionType: TGIFExtensionType; override;
- function GetTransparent: boolean;
- procedure SetTransparent(Value: boolean);
- function GetTransparentColor: TColor;
- procedure SetTransparentColor(Color: TColor);
- function GetTransparentColorIndex: BYTE;
- procedure SetTransparentColorIndex(Value: BYTE);
- function GetDelay: WORD;
- procedure SetDelay(Value: WORD);
- function GetUserInput: boolean;
- procedure SetUserInput(Value: boolean);
- function GetDisposal: TDisposalMethod;
- procedure SetDisposal(Value: TDisposalMethod);
- public
- constructor Create(ASubImage: TGIFSubImage); override;
- destructor Destroy; override;
- procedure SaveToStream(Stream: TStream); override;
- procedure LoadFromStream(Stream: TStream); override;
- property Delay: WORD read GetDelay write SetDelay;
- property Transparent: boolean read GetTransparent write SetTransparent;
- property TransparentColorIndex: BYTE read GetTransparentColorIndex
- write SetTransparentColorIndex;
- property TransparentColor: TColor read GetTransparentColor write SetTransparentColor;
- property UserInput: boolean read GetUserInput write SetUserInput;
- property Disposal: TDisposalMethod read GetDisposal write SetDisposal;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFTextExtension
- //
- ////////////////////////////////////////////////////////////////////////////////
- TGIFPlainTextExtensionRec = packed record
- BlockSize: byte; { should be 12 }
- Left, Top, Width, Height: Word;
- CellWidth, CellHeight: Byte;
- TextFGColorIndex,
- TextBGColorIndex: Byte;
- end;
- TGIFTextExtension = class(TGIFExtension)
- private
- FText : TStrings;
- FPlainTextExtension : TGIFPlainTextExtensionRec;
- protected
- function GetExtensionType: TGIFExtensionType; override;
- function GetForegroundColor: TColor;
- procedure SetForegroundColor(Color: TColor);
- function GetBackgroundColor: TColor;
- procedure SetBackgroundColor(Color: TColor);
- function GetBounds(Index: integer): WORD;
- procedure SetBounds(Index: integer; Value: WORD);
- function GetCharWidthHeight(Index: integer): BYTE;
- procedure SetCharWidthHeight(Index: integer; Value: BYTE);
- function GetColorIndex(Index: integer): BYTE;
- procedure SetColorIndex(Index: integer; Value: BYTE);
- public
- constructor Create(ASubImage: TGIFSubImage); override;
- destructor Destroy; override;
- procedure SaveToStream(Stream: TStream); override;
- procedure LoadFromStream(Stream: TStream); override;
- property Left: WORD index 1 read GetBounds write SetBounds;
- property Top: WORD index 2 read GetBounds write SetBounds;
- property GridWidth: WORD index 3 read GetBounds write SetBounds;
- property GridHeight: WORD index 4 read GetBounds write SetBounds;
- property CharWidth: BYTE index 1 read GetCharWidthHeight write SetCharWidthHeight;
- property CharHeight: BYTE index 2 read GetCharWidthHeight write SetCharWidthHeight;
- property ForegroundColorIndex: BYTE index 1 read GetColorIndex write SetColorIndex;
- property ForegroundColor: TColor read GetForegroundColor;
- property BackgroundColorIndex: BYTE index 2 read GetColorIndex write SetColorIndex;
- property BackgroundColor: TColor read GetBackgroundColor;
- property Text: TStrings read FText write FText;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFCommentExtension
- //
- ////////////////////////////////////////////////////////////////////////////////
- TGIFCommentExtension = class(TGIFExtension)
- private
- FText : TStrings;
- protected
- function GetExtensionType: TGIFExtensionType; override;
- public
- constructor Create(ASubImage: TGIFSubImage); override;
- destructor Destroy; override;
- procedure SaveToStream(Stream: TStream); override;
- procedure LoadFromStream(Stream: TStream); override;
- property Text: TStrings read FText;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFApplicationExtension
- //
- ////////////////////////////////////////////////////////////////////////////////
- TGIFIdentifierCode = array[0..7] of char;
- TGIFAuthenticationCode = array[0..2] of char;
- TGIFApplicationRec = packed record
- Identifier : TGIFIdentifierCode;
- Authentication : TGIFAuthenticationCode;
- end;
- TGIFApplicationExtension = class;
- TGIFAppExtensionClass = class of TGIFApplicationExtension;
- TGIFApplicationExtension = class(TGIFExtension)
- private
- FIdent : TGIFApplicationRec;
- function GetAuthentication: string;
- function GetIdentifier: string;
- protected
- function GetExtensionType: TGIFExtensionType; override;
- procedure SetAuthentication(const Value: string);
- procedure SetIdentifier(const Value: string);
- procedure SaveData(Stream: TStream); virtual; abstract;
- procedure LoadData(Stream: TStream); virtual; abstract;
- public
- constructor Create(ASubImage: TGIFSubImage); override;
- destructor Destroy; override;
- procedure SaveToStream(Stream: TStream); override;
- procedure LoadFromStream(Stream: TStream); override;
- class procedure RegisterExtension(eIdent: TGIFApplicationRec; eClass: TGIFAppExtensionClass);
- class function FindSubExtension(Stream: TStream): TGIFExtensionClass; override;
- property Identifier: string read GetIdentifier write SetIdentifier;
- property Authentication: string read GetAuthentication write SetAuthentication;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFUnknownAppExtension
- //
- ////////////////////////////////////////////////////////////////////////////////
- TGIFBlock = class(TObject)
- private
- FSize : BYTE;
- FData : pointer;
- public
- constructor Create(ASize: integer);
- destructor Destroy; override;
- procedure SaveToStream(Stream: TStream);
- procedure LoadFromStream(Stream: TStream);
- property Size: BYTE read FSize;
- property Data: pointer read FData;
- end;
- TGIFUnknownAppExtension = class(TGIFApplicationExtension)
- private
- FBlocks : TList;
- protected
- procedure SaveData(Stream: TStream); override;
- procedure LoadData(Stream: TStream); override;
- public
- constructor Create(ASubImage: TGIFSubImage); override;
- destructor Destroy; override;
- property Blocks: TList read FBlocks;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFAppExtNSLoop
- //
- ////////////////////////////////////////////////////////////////////////////////
- TGIFAppExtNSLoop = class(TGIFApplicationExtension)
- private
- FLoops : WORD;
- FBufferSize : DWORD;
- protected
- procedure SaveData(Stream: TStream); override;
- procedure LoadData(Stream: TStream); override;
- public
- constructor Create(ASubImage: TGIFSubImage); override;
- property Loops: WORD read FLoops write FLoops;
- property BufferSize: DWORD read FBufferSize write FBufferSize;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFImage
- //
- ////////////////////////////////////////////////////////////////////////////////
- TGIFImageList = class(TGIFList)
- protected
- function GetImage(Index: Integer): TGIFSubImage;
- procedure SetImage(Index: Integer; SubImage: TGIFSubImage);
- public
- procedure LoadFromStream(Stream: TStream; Parent: TObject); override;
- procedure SaveToStream(Stream: TStream); override;
- property SubImages[Index: Integer]: TGIFSubImage read GetImage write SetImage; default;
- end;
- // Compression algorithms
- TGIFCompression =
- (gcLZW, // Normal LZW compression
- gcRLE // GIF compatible RLE compression
- );
- // Color reduction methods
- TColorReduction =
- (rmNone, // Do not perform color reduction
- rmWindows20, // Reduce to the Windows 20 color system palette
- rmWindows256, // Reduce to the Windows 256 color halftone palette (Only works in 256 color display mode)
- rmWindowsGray, // Reduce to the Windows 4 grayscale colors
- rmMonochrome, // Reduce to a black/white monochrome palette
- rmGrayScale, // Reduce to a uniform 256 shade grayscale palette
- rmNetscape, // Reduce to the Netscape 216 color palette
- rmQuantize, // Reduce to optimal 2^n color palette
- rmQuantizeWindows, // Reduce to optimal 256 color windows palette
- rmPalette // Reduce to custom palette
- );
- TDitherMode =
- (dmNearest, // Nearest color matching w/o error correction
- dmFloydSteinberg, // Floyd Steinberg Error Diffusion dithering
- dmStucki, // Stucki Error Diffusion dithering
- dmSierra, // Sierra Error Diffusion dithering
- dmJaJuNI, // Jarvis, Judice & Ninke Error Diffusion dithering
- dmSteveArche, // Stevenson & Arche Error Diffusion dithering
- dmBurkes // Burkes Error Diffusion dithering
- // dmOrdered, // Ordered dither
- );
- // Optimization options
- TGIFOptimizeOption =
- (ooCrop, // Crop animated GIF frames
- ooMerge, // Merge pixels of same color
- ooCleanup, // Remove comments and application extensions
- ooColorMap, // Sort color map by usage and remove unused entries
- ooReduceColors // Reduce color depth ***NOT IMPLEMENTED***
- );
- TGIFOptimizeOptions = set of TGIFOptimizeOption;
- TGIFDrawOption =
- (goAsync, // Asyncronous draws (paint in thread)
- goTransparent, // Transparent draws
- goAnimate, // Animate draws
- goLoop, // Loop animations
- goLoopContinously, // Ignore loop count and loop forever
- goValidateCanvas, // Validate canvas in threaded paint ***NOT IMPLEMENTED***
- goDirectDraw, // Draw() directly on canvas
- goClearOnLoop, // Clear animation on loop
- goTile, // Tiled display
- goDither, // Dither to Netscape palette
- goAutoDither // Only dither on 256 color systems
- );
- TGIFDrawOptions = set of TGIFDrawOption;
- // Note: if goAsync is not set then goDirectDraw should be set. Otherwise
- // the image will not be displayed.
- PGIFPainter = ^TGIFPainter;
- TGIFPainter = class(TThread)
- private
- FImage : TGIFImage; // The TGIFImage that owns this painter
- FCanvas : TCanvas; // Destination canvas
- FRect : TRect; // Destination rect
- FDrawOptions : TGIFDrawOptions;// Paint options
- FAnimationSpeed : integer; // Animation speed %
- FActiveImage : integer; // Current frame
- Disposal , // Used by synchronized paint
- OldDisposal : TDisposalMethod;// Used by synchronized paint
- BackupBuffer : TBitmap; // Used by synchronized paint
- FrameBuffer : TBitmap; // Used by synchronized paint
- Background : TBitmap; // Used by synchronized paint
- ValidateDC : HDC;
- DoRestart : boolean; // Flag used to restart animation
- FStarted : boolean; // Flag used to signal start of paint
- PainterRef : PGIFPainter; // Pointer to var referencing painter
- FEventHandle : THandle; // Animation delay event
- ExceptObject : Exception; // Eaten exception
- ExceptAddress : pointer; // Eaten exceptions address
- FEvent : TNotifyEvent; // Used by synchronized events
- FOnStartPaint : TNotifyEvent;
- FOnPaint : TNotifyEvent;
- FOnAfterPaint : TNotifyEvent;
- FOnLoop : TNotifyEvent;
- FOnEndPaint : TNotifyEvent;
- procedure DoOnTerminate(Sender: TObject);// Sync. shutdown procedure
- procedure DoSynchronize(Method: TThreadMethod);// Conditional sync stub
- {$ifdef SERIALIZE_RENDER}
- procedure PrefetchBitmap; // Sync. bitmap prefetch
- {$endif}
- procedure DoPaintFrame; // Sync. buffered paint procedure
- procedure DoPaint; // Sync. paint procedure
- procedure DoEvent;
- procedure SetActiveImage(const Value: integer);// Sync. event procedure
- protected
- procedure Execute; override;
- procedure SetAnimationSpeed(Value: integer);
- public
- constructor Create(AImage: TGIFImage; ACanvas: TCanvas; ARect: TRect;
- Options: TGIFDrawOptions);
- constructor CreateRef(Painter: PGIFPainter; AImage: TGIFImage; ACanvas: TCanvas; ARect: TRect;
- Options: TGIFDrawOptions);
- destructor Destroy; override;
- procedure Start;
- procedure Stop;
- procedure Restart;
- property Image: TGIFImage read FImage;
- property Canvas: TCanvas read FCanvas;
- property Rect: TRect read FRect write FRect;
- property DrawOptions: TGIFDrawOptions read FDrawOptions write FDrawOptions;
- property AnimationSpeed: integer read FAnimationSpeed write SetAnimationSpeed;
- property Started: boolean read FStarted;
- property ActiveImage: integer read FActiveImage write SetActiveImage;
- property OnStartPaint: TNotifyEvent read FOnStartPaint write FOnStartPaint;
- property OnPaint: TNotifyEvent read FOnPaint write FOnPaint;
- property OnAfterPaint: TNotifyEvent read FOnAfterPaint write FOnAfterPaint;
- property OnLoop: TNotifyEvent read FOnLoop write FOnLoop;
- property OnEndPaint : TNotifyEvent read FOnEndPaint write FOnEndPaint ;
- property EventHandle: THandle read FEventHandle;
- end;
- TGIFWarning = procedure(Sender: TObject; Severity: TGIFSeverity; Message: string) of object;
- TGIFImage = class(TGraphic)
- private
- IsDrawing : Boolean;
- IsInsideGetPalette : boolean;
- FImages : TGIFImageList;
- FHeader : TGIFHeader;
- FGlobalPalette : HPalette;
- FPainters : TThreadList;
- FDrawOptions : TGIFDrawOptions;
- FColorReduction : TColorReduction;
- FReductionBits : integer;
- FDitherMode : TDitherMode;
- FCompression : TGIFCompression;
- FOnWarning : TGIFWarning;
- FBitmap : TBitmap;
- FDrawPainter : TGIFPainter;
- FThreadPriority : TThreadPriority;
- FAnimationSpeed : integer;
- FForceFrame: Integer; // 2004.03.09
- FDrawBackgroundColor: TColor;
- FOnStartPaint : TNotifyEvent;
- FOnPaint : TNotifyEvent;
- FOnAfterPaint : TNotifyEvent;
- FOnLoop : TNotifyEvent;
- FOnEndPaint : TNotifyEvent;
- {$IFDEF VER9x}
- FPaletteModified : Boolean;
- FOnProgress : TProgressEvent;
- {$ENDIF}
- function GetAnimate: Boolean; // 2002.07.07
- procedure SetAnimate(const Value: Boolean); // 2002.07.07
- procedure SetForceFrame(const Value: Integer); // 2004.03.09
- protected
- // Obsolete: procedure Changed(Sender: TObject); {$IFDEF VER9x} virtual; {$ELSE} override; {$ENDIF}
- function GetHeight: Integer; override;
- procedure SetHeight(Value: Integer); override;
- function GetWidth: Integer; override;
- procedure SetWidth(Value: Integer); override;
- procedure AssignTo(Dest: TPersistent); override;
- function InternalPaint(Painter: PGIFPainter; ACanvas: TCanvas; const Rect: TRect; Options: TGIFDrawOptions): TGIFPainter;
- procedure Draw(ACanvas: TCanvas; const Rect: TRect); override;
- function Equals(Graphic: TGraphic): Boolean; override;
- function GetPalette: HPALETTE; {$IFDEF VER9x} virtual; {$ELSE} override; {$ENDIF}
- procedure SetPalette(Value: HPalette); {$IFDEF VER9x} virtual; {$ELSE} override; {$ENDIF}
- function GetEmpty: Boolean; override;
- procedure WriteData(Stream: TStream); override;
- function GetIsTransparent: Boolean;
- function GetVersion: TGIFVersion;
- function GetColorResolution: integer;
- function GetBitsPerPixel: integer;
- function GetBackgroundColorIndex: BYTE;
- procedure SetBackgroundColorIndex(const Value: BYTE);
- function GetBackgroundColor: TColor;
- procedure SetBackgroundColor(const Value: TColor);
- function GetAspectRatio: BYTE;
- procedure SetAspectRatio(const Value: BYTE);
- procedure SetDrawOptions(Value: TGIFDrawOptions);
- procedure SetAnimationSpeed(Value: integer);
- procedure SetReductionBits(Value: integer);
- procedure NewImage;
- function GetBitmap: TBitmap;
- function NewBitmap: TBitmap;
- procedure FreeBitmap;
- function GetColorMap: TGIFColorMap;
- function GetDoDither: boolean;
- property DrawPainter: TGIFPainter read FDrawPainter; // Extremely volatile
- property DoDither: boolean read GetDoDither;
- {$IFDEF VER9x}
- procedure Progress(Sender: TObject; Stage: TProgressStage;
- PercentDone: Byte; RedrawNow: Boolean; const R: TRect; const Msg: string); dynamic;
- {$ENDIF}
- public
- constructor Create; override;
- destructor Destroy; override;
- procedure SaveToStream(Stream: TStream); override;
- procedure LoadFromStream(Stream: TStream); override;
- procedure LoadFromResourceName(Instance: THandle; const ResName: String); // 2002.07.07
- function Add(Source: TPersistent): integer;
- procedure Pack;
- procedure OptimizeColorMap;
- procedure Optimize(Options: TGIFOptimizeOptions;
- ColorReduction: TColorReduction; DitherMode: TDitherMode;
- ReductionBits: integer);
- procedure Clear;
- procedure StopDraw;
- function Paint(ACanvas: TCanvas; const Rect: TRect; Options: TGIFDrawOptions): TGIFPainter;
- procedure PaintStart;
- procedure PaintPause;
- procedure PaintStop;
- procedure PaintResume;
- procedure PaintRestart;
- procedure Warning(Sender: TObject; Severity: TGIFSeverity; Message: string); virtual;
- procedure Assign(Source: TPersistent); override;
- procedure LoadFromClipboardFormat(AFormat: Word; AData: THandle;
- APalette: HPALETTE); override;
- procedure SaveToClipboardFormat(var AFormat: Word; var AData: THandle;
- var APalette: HPALETTE); override;
- property GlobalColorMap: TGIFColorMap read GetColorMap;
- property Version: TGIFVersion read GetVersion;
- property Images: TGIFImageList read FImages;
- property ColorResolution: integer read GetColorResolution;
- property BitsPerPixel: integer read GetBitsPerPixel;
- property BackgroundColorIndex: BYTE read GetBackgroundColorIndex write SetBackgroundColorIndex;
- property BackgroundColor: TColor read GetBackgroundColor write SetBackgroundColor;
- property AspectRatio: BYTE read GetAspectRatio write SetAspectRatio;
- property Header: TGIFHeader read FHeader; // ***OBSOLETE***
- property IsTransparent: boolean read GetIsTransparent;
- property DrawOptions: TGIFDrawOptions read FDrawOptions write SetDrawOptions;
- property DrawBackgroundColor: TColor read FDrawBackgroundColor write FDrawBackgroundColor;
- property ColorReduction: TColorReduction read FColorReduction write FColorReduction;
- property ReductionBits: integer read FReductionBits write SetReductionBits;
- property DitherMode: TDitherMode read FDitherMode write FDitherMode;
- property Compression: TGIFCompression read FCompression write FCompression;
- property AnimationSpeed: integer read FAnimationSpeed write SetAnimationSpeed;
- property Animate: Boolean read GetAnimate write SetAnimate; // 2002.07.07
- property ForceFrame: Integer read FForceFrame write SetForceFrame; // 2004.03.09
- property Painters: TThreadList read FPainters;
- property ThreadPriority: TThreadPriority read FThreadPriority write FThreadPriority;
- property Bitmap: TBitmap read GetBitmap; // Volatile - beware!
- property OnWarning: TGIFWarning read FOnWarning write FOnWarning;
- property OnStartPaint: TNotifyEvent read FOnStartPaint write FOnStartPaint;
- property OnPaint: TNotifyEvent read FOnPaint write FOnPaint;
- property OnAfterPaint: TNotifyEvent read FOnAfterPaint write FOnAfterPaint;
- property OnLoop: TNotifyEvent read FOnLoop write FOnLoop;
- property OnEndPaint : TNotifyEvent read FOnEndPaint write FOnEndPaint ;
- {$IFDEF VER9x}
- property Palette: HPALETTE read GetPalette write SetPalette;
- property PaletteModified: Boolean read FPaletteModified write FPaletteModified;
- property OnProgress: TProgressEvent read FOnProgress write FOnProgress;
- {$ENDIF}
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Utility routines
- //
- ////////////////////////////////////////////////////////////////////////////////
- // WebPalette creates a 216 color uniform palette a.k.a. the Netscape Palette
- function WebPalette: HPalette;
- // ReduceColors
- // Map colors in a bitmap to their nearest representation in a palette using
- // the methods specified by the ColorReduction and DitherMode parameters.
- // The ReductionBits parameter specifies the desired number of colors (bits
- // per pixel) when the reduction method is rmQuantize. The CustomPalette
- // specifies the palette when the rmPalette reduction method is used.
- function ReduceColors(Bitmap: TBitmap; ColorReduction: TColorReduction;
- DitherMode: TDitherMode; ReductionBits: integer; CustomPalette: hPalette): TBitmap;
- // CreateOptimizedPaletteFromManyBitmaps
- //: Performs Color Quantization on multiple bitmaps.
- // The Bitmaps parameter is a list of bitmaps. Returns an optimized palette.
- function CreateOptimizedPaletteFromManyBitmaps(Bitmaps: TList; Colors, ColorBits: integer;
- Windows: boolean): hPalette;
- {$IFDEF VER9x}
- // From Delphi 3 graphics.pas
- type
- TPixelFormat = (pfDevice, pf1bit, pf4bit, pf8bit, pf15bit, pf16bit, pf24bit, pf32bit, pfCustom);
- {$ENDIF}
- procedure InternalGetDIBSizes(Bitmap: HBITMAP; var InfoHeaderSize: Integer;
- var ImageSize: longInt; PixelFormat: TPixelFormat);
- function InternalGetDIB(Bitmap: HBITMAP; Palette: HPALETTE;
- var BitmapInfo; var Bits; PixelFormat: TPixelFormat): Boolean;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Global variables
- //
- ////////////////////////////////////////////////////////////////////////////////
- // GIF Clipboard format identifier for use by LoadFromClipboardFormat and
- // SaveToClipboardFormat.
- // Set in Initialization section.
- var
- CF_GIF: WORD;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Library defaults
- //
- ////////////////////////////////////////////////////////////////////////////////
- var
- //: Default options for TGIFImage.DrawOptions.
- GIFImageDefaultDrawOptions : TGIFDrawOptions =
- [goAsync, goLoop, goTransparent, goAnimate, goDither, goAutoDither
- {$IFDEF STRICT_MOZILLA}
- ,goClearOnLoop
- {$ENDIF}
- ];
- // WARNING! Do not use goAsync and goDirectDraw unless you have absolute
- // control of the destination canvas.
- // TGIFPainter will continue to write on the canvas even after the canvas has
- // been deleted, unless *you* prevent it.
- // The goValidateCanvas option will fix this problem if it is ever implemented.
- //: Default color reduction methods for bitmap import.
- // These are the fastest settings, but also the ones that gives the
- // worst result (in most cases).
- GIFImageDefaultColorReduction: TColorReduction = rmNetscape;
- GIFImageDefaultColorReductionBits: integer = 8; // Range 3 - 8
- GIFImageDefaultDitherMode: TDitherMode = dmNearest;
- //: Default encoder compression method.
- GIFImageDefaultCompression: TGIFCompression = gcLZW;
- //: Default painter thread priority
- GIFImageDefaultThreadPriority: TThreadPriority = tpNormal;
- //: Default animation speed in % of normal speed (range 0 - 1000)
- GIFImageDefaultAnimationSpeed: integer = 100;
- // DoAutoDither is set to True in the initializaion section if the desktop DC
- // supports 256 colors or less.
- // It can be modified in your application to disable/enable Auto Dithering
- DoAutoDither: boolean = False;
- // Palette is set to True in the initialization section if the desktop DC
- // supports 256 colors or less.
- // You should NOT modify it.
- PaletteDevice: boolean = False;
- // Set GIFImageRenderOnLoad to True to render (convert to bitmap) the
- // GIF frames as they are loaded instead of rendering them on-demand.
- // This might increase resource consumption and will increase load time,
- // but will cause animated GIFs to display more smoothly.
- GIFImageRenderOnLoad: boolean = False;
- // If GIFImageOptimizeOnStream is true, the GIF will be optimized
- // before it is streamed to the DFM file.
- // This will not affect TGIFImage.SaveToStream or SaveToFile.
- GIFImageOptimizeOnStream: boolean = False;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Design Time support
- //
- ////////////////////////////////////////////////////////////////////////////////
- // Dummy component registration for design time support of GIFs in TImage
- procedure Register;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Error messages
- //
- ////////////////////////////////////////////////////////////////////////////////
- {$ifndef VER9x}
- resourcestring
- {$else}
- const
- {$endif}
- // GIF Error messages
- sOutOfData = 'Premature end of data';
- sTooManyColors = 'Color table overflow';
- sBadColorIndex = 'Invalid color index';
- sBadVersion = 'Unsupported GIF version';
- sBadSignature = 'Invalid GIF signature';
- sScreenBadColorSize = 'Invalid number of colors specified in Screen Descriptor';
- sImageBadColorSize = 'Invalid number of colors specified in Image Descriptor';
- sUnknownExtension = 'Unknown extension type';
- sBadExtensionLabel = 'Invalid extension introducer';
- sOutOfMemDIB = 'Failed to allocate memory for GIF DIB';
- sDIBCreate = 'Failed to create DIB from Bitmap';
- sDecodeTooFewBits = 'Decoder bit buffer under-run';
- sDecodeCircular = 'Circular decoder table entry';
- sBadTrailer = 'Invalid Image trailer';
- sBadExtensionInstance = 'Internal error: Extension Instance does not match Extension Label';
- sBadBlockSize = 'Unsupported Application Extension block size';
- sBadBlock = 'Unknown GIF block type';
- sUnsupportedClass = 'Object type not supported for operation';
- sInvalidData = 'Invalid GIF data';
- sBadHeight = 'Image height too small for contained frames';
- sBadWidth = 'Image width too small for contained frames';
- {$IFNDEF REGISTER_TGIFIMAGE}
- sGIFToClipboard = 'Clipboard operations not supported for GIF objects';
- {$ELSE}
- sFailedPaste = 'Failed to store GIF on clipboard';
- {$IFDEF VER9x}
- sUnknownClipboardFormat= 'Unsupported clipboard format';
- {$ENDIF}
- {$ENDIF}
- sScreenSizeExceeded = 'Image exceeds Logical Screen size';
- sNoColorTable = 'No global or local color table defined';
- sBadPixelCoordinates = 'Invalid pixel coordinates';
- sUnsupportedBitmap = 'Unsupported bitmap format';
- sInvalidPixelFormat = 'Unsupported PixelFormat';
- sBadDimension = 'Invalid image dimensions';
- sNoDIB = 'Image has no DIB';
- sInvalidStream = 'Invalid stream operation';
- sInvalidColor = 'Color not in color table';
- sInvalidBitSize = 'Invalid Bits Per Pixel value';
- sEmptyColorMap = 'Color table is empty';
- sEmptyImage = 'Image is empty';
- sInvalidBitmapList = 'Invalid bitmap list';
- sInvalidReduction = 'Invalid reduction method';
- {$IFDEF VER9x}
- // From Delphi 3 consts.pas
- SOutOfResources = 'Out of system resources';
- SInvalidBitmap = 'Bitmap image is not valid';
- SScanLine = 'Scan line index out of range';
- {$ENDIF}
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Misc texts
- //
- ////////////////////////////////////////////////////////////////////////////////
- // File filter name
- sGIFImageFile = 'GIF Image';
- // Progress messages
- sProgressLoading = 'Loading...';
- sProgressSaving = 'Saving...';
- sProgressConverting = 'Converting...';
- sProgressRendering = 'Rendering...';
- sProgressCopying = 'Copying...';
- sProgressOptimizing = 'Optimizing...';
- ////////////////////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Implementation
- //
- ////////////////////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////////////////////
- implementation
- { This makes me long for the C preprocessor... }
- {$ifdef DEBUG}
- {$ifdef DEBUG_COMPRESSPERFORMANCE}
- {$define DEBUG_PERFORMANCE}
- {$else}
- {$ifdef DEBUG_DECOMPRESSPERFORMANCE}
- {$define DEBUG_PERFORMANCE}
- {$else}
- {$ifdef DEBUG_DITHERPERFORMANCE}
- {$define DEBUG_PERFORMANCE}
- {$else}
- {$ifdef DEBUG_DITHERPERFORMANCE}
- {$define DEBUG_PERFORMANCE}
- {$else}
- {$ifdef DEBUG_DRAWPERFORMANCE}
- {$define DEBUG_PERFORMANCE}
- {$else}
- {$ifdef DEBUG_RENDERPERFORMANCE}
- {$define DEBUG_PERFORMANCE}
- {$endif}
- {$endif}
- {$endif}
- {$endif}
- {$endif}
- {$endif}
- {$endif}
- uses
- {$ifdef DEBUG}
- dialogs,
- {$endif}
- mmsystem, // timeGetTime()
- messages,
- Consts;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Misc consts
- //
- ////////////////////////////////////////////////////////////////////////////////
- const
- { Extension/block label values }
- bsPlainTextExtension = $01;
- bsGraphicControlExtension = $F9;
- bsCommentExtension = $FE;
- bsApplicationExtension = $FF;
- bsImageDescriptor = Ord(',');
- bsExtensionIntroducer = Ord('!');
- bsTrailer = ord(';');
- // Thread messages - Used by TThread.Synchronize()
- CM_DESTROYWINDOW = $8FFE; // Defined in classes.pas
- CM_EXECPROC = $8FFF; // Defined in classes.pas
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Design Time support
- //
- ////////////////////////////////////////////////////////////////////////////////
- //: Dummy component registration to add design-time support of GIFs to TImage.
- // Since TGIFImage isn't a component there's nothing to register here, but
- // since Register is only called at design time we can set the design time
- // GIF paint options here (modify as you please):
- procedure Register;
- begin
- // Don't loop animations at design-time. Animated GIFs will animate once and
- // then stop thus not using CPU resources and distracting the developer.
- Exclude(GIFImageDefaultDrawOptions, goLoop);
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Utilities
- //
- ////////////////////////////////////////////////////////////////////////////////
- //: Creates a 216 color uniform non-dithering Netscape palette.
- function WebPalette: HPalette;
- type
- TLogWebPalette = packed record
- palVersion : word;
- palNumEntries : word;
- PalEntries : array[0..5,0..5,0..5] of TPaletteEntry;
- end;
- var
- r, g, b : byte;
- LogWebPalette : TLogWebPalette;
- LogPalette : TLogpalette absolute LogWebPalette; // Stupid typecast
- begin
- with LogWebPalette do
- begin
- palVersion:= $0300;
- palNumEntries:= 216;
- for r:=0 to 5 do
- for g:=0 to 5 do
- for b:=0 to 5 do
- begin
- with PalEntries[r,g,b] do
- begin
- peRed := 51 * r;
- peGreen := 51 * g;
- peBlue := 51 * b;
- peFlags := 0;
- end;
- end;
- end;
- Result := CreatePalette(Logpalette);
- end;
- (*
- ** GDI Error handling
- ** Adapted from graphics.pas
- *)
- {$IFOPT R+}
- {$DEFINE R_PLUS}
- {$RANGECHECKS OFF}
- {$ENDIF}
- {$ifdef D3_BCB3}
- function GDICheck(Value: Integer): Integer;
- {$else}
- function GDICheck(Value: Cardinal): Cardinal;
- {$endif}
- var
- ErrorCode : integer;
- Buf : array [byte] of char;
- function ReturnAddr: Pointer;
- // From classes.pas
- asm
- MOV EAX,[EBP+4] // sysutils.pas says [EBP-4], but this works !
- end;
- begin
- if (Value = 0) then
- begin
- ErrorCode := GetLastError;
- if (ErrorCode <> 0) and (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nil,
- ErrorCode, LOCALE_USER_DEFAULT, Buf, sizeof(Buf), nil) <> 0) then
- raise EOutOfResources.Create(Buf) at ReturnAddr
- else
- raise EOutOfResources.Create(SOutOfResources) at ReturnAddr;
- end;
- Result := Value;
- end;
- {$IFDEF R_PLUS}
- {$RANGECHECKS ON}
- {$UNDEF R_PLUS}
- {$ENDIF}
- (*
- ** Raise error condition
- *)
- procedure Error(msg: string);
- function ReturnAddr: Pointer;
- // From classes.pas
- asm
- MOV EAX,[EBP+4] // sysutils.pas says [EBP-4] !
- end;
- begin
- raise GIFException.Create(msg) at ReturnAddr;
- end;
- (*
- ** Return number bytes required to
- ** hold a given number of bits.
- *)
- function ByteAlignBit(Bits: Cardinal): Cardinal;
- begin
- Result := (Bits+7) SHR 3;
- end;
- // Rounded up to nearest 2
- function WordAlignBit(Bits: Cardinal): Cardinal;
- begin
- Result := ((Bits+15) SHR 4) SHL 1;
- end;
- // Rounded up to nearest 4
- function DWordAlignBit(Bits: Cardinal): Cardinal;
- begin
- Result := ((Bits+31) SHR 5) SHL 2;
- end;
- // Round to arbitrary number of bits
- function AlignBit(Bits, BitsPerPixel, Alignment: Cardinal): Cardinal;
- begin
- Dec(Alignment);
- Result := ((Bits * BitsPerPixel) + Alignment) and not Alignment;
- Result := Result SHR 3;
- end;
- (*
- ** Compute Bits per Pixel from Number of Colors
- ** (Return the ceiling log of n)
- *)
- function Colors2bpp(Colors: integer): integer;
- var
- MaxColor : integer;
- begin
- (*
- ** This might be faster computed by multiple if then else statements
- *)
- if (Colors = 0) then
- Result := 0
- else
- begin
- Result := 1;
- MaxColor := 2;
- while (Colors > MaxColor) do
- begin
- inc(Result);
- MaxColor := MaxColor SHL 1;
- end;
- end;
- end;
- (*
- ** Write an ordinal byte value to a stream
- *)
- procedure WriteByte(Stream: TStream; b: BYTE);
- begin
- Stream.Write(b, 1);
- end;
- (*
- ** Read an ordinal byte value from a stream
- *)
- function ReadByte(Stream: TStream): BYTE;
- begin
- Stream.Read(Result, 1);
- end;
- (*
- ** Read data from stream and raise exception of EOF
- *)
- procedure ReadCheck(Stream: TStream; var Buffer; Size: LongInt);
- var
- ReadSize : integer;
- begin
- ReadSize := Stream.Read(Buffer, Size);
- if (ReadSize <> Size) then
- Error(sOutOfData);
- end;
- (*
- ** Write a string list to a stream as multiple blocks
- ** of max 255 characters in each.
- *)
- procedure WriteStrings(Stream: TStream; Text: TStrings);
- var
- i : integer;
- b : BYTE;
- size : integer;
- s : string;
- begin
- for i := 0 to Text.Count-1 do
- begin
- s := Text[i];
- size := length(s);
- if (size > 255) then
- b := 255
- else
- b := size;
- while (size > 0) do
- begin
- dec(size, b);
- WriteByte(Stream, b);
- Stream.Write(PChar(s)^, b);
- delete(s, 1, b);
- if (b > size) then
- b := size;
- end;
- end;
- // Terminating zero (length = 0)
- WriteByte(Stream, 0);
- end;
- (*
- ** Read a string list from a stream as multiple blocks
- ** of max 255 characters in each.
- *)
- { TODO -oanme -cImprovement : Replace ReadStrings with TGIFReader. }
- procedure ReadStrings(Stream: TStream; Text: TStrings);
- var
- size : BYTE;
- buf : array[0..255] of char;
- begin
- Text.Clear;
- if (Stream.Read(size, 1) <> 1) then
- exit;
- while (size > 0) do
- begin
- ReadCheck(Stream, buf, size);
- buf[size] := #0;
- Text.Add(Buf);
- if (Stream.Read(size, 1) <> 1) then
- exit;
- end;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Delphi 2.x / C++ Builder 1.x support
- //
- ////////////////////////////////////////////////////////////////////////////////
- {$IFDEF VER9x}
- var
- // From Delphi 3 graphics.pas
- SystemPalette16: HPalette; // 16 color palette that maps to the system palette
- type
- TPixelFormats = set of TPixelFormat;
- const
- // Only pf1bit, pf4bit and pf8bit is supported since they are the only ones
- // with palettes
- SupportedPixelformats: TPixelFormats = [pf1bit, pf4bit, pf8bit];
- {$ENDIF}
- // --------------------------
- // InitializeBitmapInfoHeader
- // --------------------------
- // Fills a TBitmapInfoHeader with the values of a bitmap when converted to a
- // DIB of a specified PixelFormat.
- //
- // Parameters:
- // Bitmap The handle of the source bitmap.
- // Info The TBitmapInfoHeader buffer that will receive the values.
- // PixelFormat The pixel format of the destination DIB.
- //
- {$IFDEF BAD_STACK_ALIGNMENT}
- // Disable optimization to circumvent optimizer bug...
- {$IFOPT O+}
- {$DEFINE O_PLUS}
- {$O-}
- {$ENDIF}
- {$ENDIF}
- procedure InitializeBitmapInfoHeader(Bitmap: HBITMAP; var Info: TBitmapInfoHeader;
- PixelFormat: TPixelFormat);
- // From graphics.pas, "optimized" for our use
- var
- DIB : TDIBSection;
- Bytes : Integer;
- begin
- DIB.dsbmih.biSize := 0;
- Bytes := GetObject(Bitmap, SizeOf(DIB), @DIB);
- if (Bytes = 0) then
- Error(sInvalidBitmap);
- if (Bytes >= (sizeof(DIB.dsbm) + sizeof(DIB.dsbmih))) and
- (DIB.dsbmih.biSize >= sizeof(DIB.dsbmih)) then
- Info := DIB.dsbmih
- else
- begin
- FillChar(Info, sizeof(Info), 0);
- with Info, DIB.dsbm do
- begin
- biSize := SizeOf(Info);
- biWidth := bmWidth;
- biHeight := bmHeight;
- end;
- end;
- case PixelFormat of
- pf1bit: Info.biBitCount := 1;
- pf4bit: Info.biBitCount := 4;
- pf8bit: Info.biBitCount := 8;
- pf24bit: Info.biBitCount := 24;
- else
- Error(sInvalidPixelFormat);
- // Info.biBitCount := DIB.dsbm.bmBitsPixel * DIB.dsbm.bmPlanes;
- end;
- Info.biPlanes := 1;
- Info.biCompression := BI_RGB; // Always return data in RGB format
- Info.biSizeImage := AlignBit(Info.biWidth, Info.biBitCount, 32) * Cardinal(abs(Info.biHeight));
- end;
- {$IFDEF O_PLUS}
- {$O+}
- {$UNDEF O_PLUS}
- {$ENDIF}
- // -------------------
- // InternalGetDIBSizes
- // -------------------
- // Calculates the buffer sizes nescessary for convertion of a bitmap to a DIB
- // of a specified PixelFormat.
- // See the GetDIBSizes API function for more info.
- //
- // Parameters:
- // Bitmap The handle of the source bitmap.
- // InfoHeaderSize
- // The returned size of a buffer that will receive the DIB's
- // TBitmapInfo structure.
- // ImageSize The returned size of a buffer that will receive the DIB's
- // pixel data.
- // PixelFormat The pixel format of the destination DIB.
- //
- procedure InternalGetDIBSizes(Bitmap: HBITMAP; var InfoHeaderSize: Integer;
- var ImageSize: longInt; PixelFormat: TPixelFormat);
- // From graphics.pas, "optimized" for our use
- var
- Info : TBitmapInfoHeader;
- begin
- InitializeBitmapInfoHeader(Bitmap, Info, PixelFormat);
- // Check for palette device format
- if (Info.biBitCount > 8) then
- begin
- // Header but no palette
- InfoHeaderSize := SizeOf(TBitmapInfoHeader);
- if ((Info.biCompression and BI_BITFIELDS) <> 0) then
- Inc(InfoHeaderSize, 12);
- end else
- // Header and palette
- InfoHeaderSize := SizeOf(TBitmapInfoHeader) + SizeOf(TRGBQuad) * (1 shl Info.biBitCount);
- ImageSize := Info.biSizeImage;
- end;
- // --------------
- // InternalGetDIB
- // --------------
- // Converts a bitmap to a DIB of a specified PixelFormat.
- //
- // Parameters:
- // Bitmap The handle of the source bitmap.
- // Pal The handle of the source palette.
- // BitmapInfo The buffer that will receive the DIB's TBitmapInfo structure.
- // A buffer of sufficient size must have been allocated prior to
- // calling this function.
- // Bits The buffer that will receive the DIB's pixel data.
- // A buffer of sufficient size must have been allocated prior to
- // calling this function.
- // PixelFormat The pixel format of the destination DIB.
- //
- // Returns:
- // True on success, False on failure.
- //
- // Note: The InternalGetDIBSizes function can be used to calculate the
- // nescessary sizes of the BitmapInfo and Bits buffers.
- //
- function InternalGetDIB(Bitmap: HBITMAP; Palette: HPALETTE;
- var BitmapInfo; var Bits; PixelFormat: TPixelFormat): Boolean;
- // From graphics.pas, "optimized" for our use
- var
- OldPal : HPALETTE;
- DC : HDC;
- begin
- InitializeBitmapInfoHeader(Bitmap, TBitmapInfoHeader(BitmapInfo), PixelFormat);
- OldPal := 0;
- DC := CreateCompatibleDC(0);
- try
- if (Palette <> 0) then
- begin
- OldPal := SelectPalette(DC, Palette, False);
- RealizePalette(DC);
- end;
- Result := (GetDIBits(DC, Bitmap, 0, abs(TBitmapInfoHeader(BitmapInfo).biHeight),
- @Bits, TBitmapInfo(BitmapInfo), DIB_RGB_COLORS) <> 0);
- finally
- if (OldPal <> 0) then
- SelectPalette(DC, OldPal, False);
- DeleteDC(DC);
- end;
- end;
- // ----------
- // DIBFromBit
- // ----------
- // Converts a bitmap to a DIB of a specified PixelFormat.
- // The DIB is returned in a TMemoryStream ready for streaming to a BMP file.
- //
- // Note: As opposed to D2's DIBFromBit function, the returned stream also
- // contains a TBitmapFileHeader at offset 0.
- //
- // Parameters:
- // Stream The TMemoryStream used to store the bitmap data.
- // The stream must be allocated and freed by the caller prior to
- // calling this function.
- // Src The handle of the source bitmap.
- // Pal The handle of the source palette.
- // PixelFormat The pixel format of the destination DIB.
- // DIBHeader A pointer to the DIB's TBitmapInfo (or TBitmapInfoHeader)
- // structure in the memory stream.
- // The size of the structure can either be deduced from the
- // pixel format (i.e. number of colors) or calculated by
- // subtracting the DIBHeader pointer from the DIBBits pointer.
- // DIBBits A pointer to the DIB's pixel data in the memory stream.
- //
- procedure DIBFromBit(Stream: TMemoryStream; Src: HBITMAP;
- Pal: HPALETTE; PixelFormat: TPixelFormat; var DIBHeader, DIBBits: Pointer);
- // (From D2 graphics.pas, "optimized" for our use)
- var
- HeaderSize : integer;
- FileSize : longInt;
- ImageSize : longInt;
- BitmapFileHeader : PBitmapFileHeader;
- begin
- if (Src = 0) then
- Error(sInvalidBitmap);
- // Get header- and pixel data size for new pixel format
- InternalGetDIBSizes(Src, HeaderSize, ImageSize, PixelFormat);
- // Make room in stream for a TBitmapInfo and pixel data
- FileSize := sizeof(TBitmapFileHeader) + HeaderSize + ImageSize;
- Stream.SetSize(FileSize);
- // Get pointer to TBitmapFileHeader
- BitmapFileHeader := Stream.Memory;
- // Get pointer to TBitmapInfo
- DIBHeader := Pointer(Longint(BitmapFileHeader) + sizeof(TBitmapFileHeader));
- // Get pointer to pixel data
- DIBBits := Pointer(Longint(DIBHeader) + HeaderSize);
- // Initialize file header
- FillChar(BitmapFileHeader^, sizeof(TBitmapFileHeader), 0);
- with BitmapFileHeader^ do
- begin
- bfType := $4D42; // 'BM' = Windows BMP signature
- bfSize := FileSize; // File size (not needed)
- bfOffBits := sizeof(TBitmapFileHeader) + HeaderSize; // Offset of pixel data
- end;
- // Get pixel data in new pixel format
- InternalGetDIB(Src, Pal, DIBHeader^, DIBBits^, PixelFormat);
- end;
- // --------------
- // GetPixelFormat
- // --------------
- // Returns the current pixel format of a bitmap.
- //
- // Replacement for delphi 3 TBitmap.PixelFormat getter.
- //
- // Parameters:
- // Bitmap The bitmap which pixel format is returned.
- //
- // Returns:
- // The PixelFormat of the bitmap
- //
- function GetPixelFormat(Bitmap: TBitmap): TPixelFormat;
- {$IFDEF VER9x}
- // From graphics.pas, "optimized" for our use
- var
- DIBSection : TDIBSection;
- Bytes : Integer;
- Handle : HBitmap;
- begin
- Result := pfCustom; // This value is never returned
- // BAD_STACK_ALIGNMENT
- // Note: To work around an optimizer bug, we do not use Bitmap.Handle
- // directly. Instead we store the value and use it indirectly. Unless we do
- // this, the register containing Bitmap.Handle will be overwritten!
- Handle := Bitmap.Handle;
- if (Handle <> 0) then
- begin
- Bytes := GetObject(Handle, SizeOf(DIBSection), @DIBSection);
- if (Bytes = 0) then
- Error(sInvalidBitmap);
- with (DIBSection) do
- begin
- // Check for NT bitmap
- if (Bytes < (SizeOf(dsbm) + SizeOf(dsbmih))) or (dsbmih.biSize < SizeOf(dsbmih)) then
- DIBSection.dsBmih.biBitCount := dsbm.bmBitsPixel * dsbm.bmPlanes;
- case (dsBmih.biBitCount) of
- 0: Result := pfDevice;
- 1: Result := pf1bit;
- 4: Result := pf4bit;
- 8: Result := pf8bit;
- 16: case (dsBmih.biCompression) of
- BI_RGB:
- Result := pf15Bit;
- BI_BITFIELDS:
- if (dsBitFields[1] = $07E0) then
- Result := pf16Bit;
- end;
- 24: Result := pf24Bit;
- 32: if (dsBmih.biCompression = BI_RGB) then
- Result := pf32Bit;
- else
- Error(sUnsupportedBitmap);
- end;
- end;
- end else
- // Result := pfDevice;
- Error(sUnsupportedBitmap);
- end;
- {$ELSE}
- begin
- Result := Bitmap.PixelFormat;
- end;
- {$ENDIF}
- // --------------
- // SetPixelFormat
- // --------------
- // Changes the pixel format of a TBitmap.
- //
- // Replacement for delphi 3 TBitmap.PixelFormat setter.
- // The returned TBitmap will always be a DIB.
- //
- // Note: Under Delphi 3.x this function will leak a palette handle each time it
- // converts a TBitmap to pf8bit format!
- // If possible, use SafeSetPixelFormat instead to avoid this.
- //
- // Parameters:
- // Bitmap The bitmap to modify.
- // PixelFormat The pixel format to convert to.
- //
- procedure SetPixelFormat(Bitmap: TBitmap; PixelFormat: TPixelFormat);
- {$IFDEF VER9x}
- var
- Stream : TMemoryStream;
- Header ,
- Bits : Pointer;
- begin
- // Can't change anything without a handle
- if (Bitmap.Handle = 0) then
- Error(sInvalidBitmap);
- // Only convert to supported formats
- if not(PixelFormat in SupportedPixelformats) then
- Error(sInvalidPixelFormat);
- // No need to convert to same format
- if (GetPixelFormat(Bitmap) = PixelFormat) then
- exit;
- Stream := TMemoryStream.Create;
- try
- // Convert to DIB file in memory stream
- DIBFromBit(Stream, Bitmap.Handle, Bitmap.Palette, PixelFormat, Header, Bits);
- // Load DIB from stream
- Stream.Position := 0;
- Bitmap.LoadFromStream(Stream);
- finally
- Stream.Free;
- end;
- end;
- {$ELSE}
- begin
- Bitmap.PixelFormat := PixelFormat;
- end;
- {$ENDIF}
- {$IFDEF VER100}
- var
- pf8BitBitmap: TBitmap = nil;
- {$ENDIF}
- // ------------------
- // SafeSetPixelFormat
- // ------------------
- // Changes the pixel format of a TBitmap but doesn't preserve the contents.
- //
- // Replacement for Delphi 3 TBitmap.PixelFormat setter.
- // The returned TBitmap will always be an empty DIB of the same size as the
- // original bitmap.
- //
- // This function is used to avoid the palette handle leak that Delphi 3's
- // SetPixelFormat and TBitmap.PixelFormat suffers from.
- //
- // Parameters:
- // Bitmap The bitmap to modify.
- // PixelFormat The pixel format to convert to.
- //
- procedure SafeSetPixelFormat(Bitmap: TBitmap; PixelFormat: TPixelFormat);
- {$IFDEF VER9x}
- begin
- SetPixelFormat(Bitmap, PixelFormat);
- end;
- {$ELSE}
- {$IFNDEF VER100}
- var
- Palette : hPalette;
- begin
- Bitmap.PixelFormat := PixelFormat;
- // Work around a bug in TBitmap:
- // When converting to pf8bit format, the palette assigned to TBitmap.Palette
- // will be a half tone palette (which only contains the 20 system colors).
- // Unfortunately this is not the palette used to render the bitmap and it
- // is also not the palette saved with the bitmap.
- if (PixelFormat = pf8bit) then
- begin
- // Disassociate the wrong palette from the bitmap (without affecting
- // the DIB color table)
- Palette := Bitmap.ReleasePalette;
- if (Palette <> 0) then
- DeleteObject(Palette);
- // Recreate the palette from the DIB color table
- Bitmap.Palette;
- end;
- end;
- {$ELSE}
- var
- Width ,
- Height : integer;
- begin
- if (PixelFormat = pf8bit) then
- begin
- // Partial solution to "TBitmap.PixelFormat := pf8bit" leak
- // by Greg Chapman <glc@well.com>
- if (pf8BitBitmap = nil) then
- begin
- // Create a "template" bitmap
- // The bitmap is deleted in the finalization section of the unit.
- pf8BitBitmap:= TBitmap.Create;
- // Convert template to pf8bit format
- // This will leak 1 palette handle, but only once
- pf8BitBitmap.PixelFormat:= pf8Bit;
- end;
- // Store the size of the original bitmap
- Width := Bitmap.Width;
- Height := Bitmap.Height;
- // Convert to pf8bit format by copying template
- Bitmap.Assign(pf8BitBitmap);
- // Restore the original size
- Bitmap.Width := Width;
- Bitmap.Height := Height;
- end else
- // This is safe since only pf8bit leaks
- Bitmap.PixelFormat := PixelFormat;
- end;
- {$ENDIF}
- {$ENDIF}
- {$IFDEF VER9x}
- // -----------
- // CopyPalette
- // -----------
- // Copies a HPALETTE.
- //
- // Copied from D3 graphics.pas.
- // This is declared private in some old versions of Delphi 2 so we have to
- // implement it here to support those old versions.
- //
- // Parameters:
- // Palette The palette to copy.
- //
- // Returns:
- // The handle to a new palette.
- //
- function CopyPalette(Palette: HPALETTE): HPALETTE;
- var
- PaletteSize: Integer;
- LogPal: TMaxLogPalette;
- begin
- Result := 0;
- if Palette = 0 then Exit;
- PaletteSize := 0;
- if GetObject(Palette, SizeOf(PaletteSize), @PaletteSize) = 0 then Exit;
- if PaletteSize = 0 then Exit;
- with LogPal do
- begin
- palVersion := $0300;
- palNumEntries := PaletteSize;
- GetPaletteEntries(Palette, 0, PaletteSize, palPalEntry);
- end;
- Result := CreatePalette(PLogPalette(@LogPal)^);
- end;
- // TThreadList implementation from Delphi 3 classes.pas
- constructor TThreadList.Create;
- begin
- inherited Create;
- InitializeCriticalSection(FLock);
- FList := TList.Create;
- end;
- destructor TThreadList.Destroy;
- begin
- LockList; // Make sure nobody else is inside the list.
- try
- FList.Free;
- inherited Destroy;
- finally
- UnlockList;
- DeleteCriticalSection(FLock);
- end;
- end;
- procedure TThreadList.Add(Item: Pointer);
- begin
- LockList;
- try
- if FList.IndexOf(Item) = -1 then
- FList.Add(Item);
- finally
- UnlockList;
- end;
- end;
- procedure TThreadList.Clear;
- begin
- LockList;
- try
- FList.Clear;
- finally
- UnlockList;
- end;
- end;
- function TThreadList.LockList: TList;
- begin
- EnterCriticalSection(FLock);
- Result := FList;
- end;
- procedure TThreadList.Remove(Item: Pointer);
- begin
- LockList;
- try
- FList.Remove(Item);
- finally
- UnlockList;
- end;
- end;
- procedure TThreadList.UnlockList;
- begin
- LeaveCriticalSection(FLock);
- end;
- // End of TThreadList implementation
- // From Delphi 3 sysutils.pas
- { CompareMem performs a binary compare of Length bytes of memory referenced
- by P1 to that of P2. CompareMem returns True if the memory referenced by
- P1 is identical to that of P2. }
- function CompareMem(P1, P2: Pointer; Length: Integer): Boolean; assembler;
- asm
- PUSH ESI
- PUSH EDI
- MOV ESI,P1
- MOV EDI,P2
- MOV EDX,ECX
- XOR EAX,EAX
- AND EDX,3
- SHR ECX,1
- SHR ECX,1
- REPE CMPSD
- JNE @@2
- MOV ECX,EDX
- REPE CMPSB
- JNE @@2
- @@1: INC EAX
- @@2: POP EDI
- POP ESI
- end;
- // Dummy ASSERT procedure since ASSERT does not exist in Delphi 2.x
- procedure ASSERT(Condition: boolean; Message: string);
- begin
- end;
- {$ENDIF} // Delphi 2.x stuff
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TDIB Classes
- //
- // These classes gives read and write access to TBitmap's pixel data
- // independently of the Delphi version used.
- //
- ////////////////////////////////////////////////////////////////////////////////
- type
- TDIB = class(TObject)
- private
- FBitmap : TBitmap;
- FPixelFormat : TPixelFormat;
- protected
- function GetScanline(Row: integer): pointer; virtual; abstract;
- constructor Create(ABitmap: TBitmap; APixelFormat: TPixelFormat);
- public
- property Scanline[Row: integer]: pointer read GetScanline;
- property Bitmap: TBitmap read FBitmap;
- property PixelFormat: TPixelFormat read FPixelFormat;
- end;
- TDIBReader = class(TDIB)
- private
- {$ifdef VER9x}
- FDIB : TDIBSection;
- FDC : HDC;
- FScanLine : pointer;
- FLastRow : integer;
- FInfo : PBitmapInfo;
- FBytes : integer;
- {$endif}
- protected
- function GetScanline(Row: integer): pointer; override;
- public
- constructor Create(ABitmap: TBitmap; APixelFormat: TPixelFormat);
- destructor Destroy; override;
- end;
- TDIBWriter = class(TDIB)
- private
- {$ifdef PIXELFORMAT_TOO_SLOW}
- FDIBInfo : PBitmapInfo;
- FDIBBits : pointer;
- FDIBInfoSize : integer;
- FDIBBitsSize : longInt;
- {$ifndef CREATEDIBSECTION_SLOW}
- FDIB : HBITMAP;
- {$endif}
- {$endif}
- FPalette : HPalette;
- FHeight : integer;
- FWidth : integer;
- protected
- procedure CreateDIB;
- procedure FreeDIB;
- procedure NeedDIB;
- function GetScanline(Row: integer): pointer; override;
- public
- constructor Create(ABitmap: TBitmap; APixelFormat: TPixelFormat;
- AWidth, AHeight: integer; APalette: HPalette);
- destructor Destroy; override;
- procedure UpdateBitmap;
- property Width: integer read FWidth;
- property Height: integer read FHeight;
- property Palette: HPalette read FPalette;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- constructor TDIB.Create(ABitmap: TBitmap; APixelFormat: TPixelFormat);
- begin
- inherited Create;
- FBitmap := ABitmap;
- FPixelFormat := APixelFormat;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- constructor TDIBReader.Create(ABitmap: TBitmap; APixelFormat: TPixelFormat);
- {$ifdef VER9x}
- var
- InfoHeaderSize : integer;
- ImageSize : longInt;
- {$endif}
- begin
- inherited Create(ABitmap, APixelFormat);
- {$ifndef VER9x}
- SetPixelFormat(FBitmap, FPixelFormat);
- {$else}
- FDC := CreateCompatibleDC(0);
- SelectPalette(FDC, FBitmap.Palette, False);
- // Allocate DIB info structure
- InternalGetDIBSizes(ABitmap.Handle, InfoHeaderSize, ImageSize, APixelFormat);
- GetMem(FInfo, InfoHeaderSize);
- // Get DIB info
- InitializeBitmapInfoHeader(ABitmap.Handle, FInfo^.bmiHeader, APixelFormat);
- // Allocate scan line buffer
- GetMem(FScanLine, ImageSize DIV abs(FInfo^.bmiHeader.biHeight));
- FLastRow := -1;
- {$endif}
- end;
- destructor TDIBReader.Destroy;
- begin
- {$ifdef VER9x}
- DeleteDC(FDC);
- FreeMem(FScanLine);
- FreeMem(FInfo);
- {$endif}
- inherited Destroy;
- end;
- function TDIBReader.GetScanline(Row: integer): pointer;
- begin
- {$ifdef VER9x}
- if (Row < 0) or (Row >= FBitmap.Height) then
- raise EInvalidGraphicOperation.Create(SScanLine);
- GDIFlush;
- Result := FScanLine;
- if (Row = FLastRow) then
- exit;
- FLastRow := Row;
- if (FInfo^.bmiHeader.biHeight > 0) then // bottom-up DIB
- Row := FInfo^.bmiHeader.biHeight - Row - 1;
- GetDIBits(FDC, FBitmap.Handle, Row, 1, FScanLine, FInfo^, DIB_RGB_COLORS);
- {$else}
- Result := FBitmap.ScanLine[Row];
- {$endif}
- end;
- ////////////////////////////////////////////////////////////////////////////////
- constructor TDIBWriter.Create(ABitmap: TBitmap; APixelFormat: TPixelFormat;
- AWidth, AHeight: integer; APalette: HPalette);
- begin
- inherited Create(ABitmap, APixelFormat);
- // DIB writer only supports 8 or 24 bit bitmaps
- if not(APixelFormat in [pf8bit, pf24bit]) then
- Error(sInvalidPixelFormat);
- if (AWidth = 0) or (AHeight = 0) then
- Error(sBadDimension);
- FHeight := AHeight;
- FWidth := AWidth;
- {$ifndef PIXELFORMAT_TOO_SLOW}
- FBitmap.Palette := 0;
- FBitmap.Height := FHeight;
- FBitmap.Width := FWidth;
- SafeSetPixelFormat(FBitmap, FPixelFormat);
- FPalette := CopyPalette(APalette);
- FBitmap.Palette := FPalette;
- {$else}
- FPalette := APalette;
- FDIBInfo := nil;
- FDIBBits := nil;
- {$ifndef CREATEDIBSECTION_SLOW}
- FDIB := 0;
- {$endif}
- {$endif}
- end;
- destructor TDIBWriter.Destroy;
- begin
- UpdateBitmap;
- FreeDIB;
- inherited Destroy;
- end;
- function TDIBWriter.GetScanline(Row: integer): pointer;
- begin
- {$ifdef PIXELFORMAT_TOO_SLOW}
- NeedDIB;
- if (FDIBBits = nil) then
- Error(sNoDIB);
- with FDIBInfo^.bmiHeader do
- begin
- if (Row < 0) or (Row >= Height) then
- raise EInvalidGraphicOperation.Create(SScanLine);
- GDIFlush;
- if biHeight > 0 then // bottom-up DIB
- Row := biHeight - Row - 1;
- Result := PChar(Cardinal(FDIBBits) + Cardinal(Row) * AlignBit(biWidth, biBitCount, 32));
- end;
- {$else}
- Result := FBitmap.ScanLine[Row];
- {$endif}
- end;
- procedure TDIBWriter.CreateDIB;
- {$IFDEF PIXELFORMAT_TOO_SLOW}
- var
- SrcColors : WORD;
- // ScreenDC : HDC;
- // From Delphi 3.02 graphics.pas
- // There is a bug in the ByteSwapColors from Delphi 3.0!
- procedure ByteSwapColors(var Colors; Count: Integer);
- var // convert RGB to BGR and vice-versa. TRGBQuad <-> TPaletteEntry
- SysInfo: TSystemInfo;
- begin
- GetSystemInfo(SysInfo);
- asm
- MOV EDX, Colors
- MOV ECX, Count
- DEC ECX
- JS @@END
- LEA EAX, SysInfo
- CMP [EAX].TSystemInfo.wProcessorLevel, 3
- JE @@386
- @@1: MOV EAX, [EDX+ECX*4]
- BSWAP EAX
- SHR EAX,8
- MOV [EDX+ECX*4],EAX
- DEC ECX
- JNS @@1
- JMP @@END
- @@386:
- PUSH EBX
- @@2: XOR EBX,EBX
- MOV EAX, [EDX+ECX*4]
- MOV BH, AL
- MOV BL, AH
- SHR EAX,16
- SHL EBX,8
- MOV BL, AL
- MOV [EDX+ECX*4],EBX
- DEC ECX
- JNS @@2
- POP EBX
- @@END:
- end;
- end;
- {$ENDIF}
- begin
- {$ifdef PIXELFORMAT_TOO_SLOW}
- FreeDIB;
- if (PixelFormat = pf8bit) then
- // 8 bit: Header and palette
- FDIBInfoSize := SizeOf(TBitmapInfoHeader) + SizeOf(TRGBQuad) * (1 shl 8)
- else
- // 24 bit: Header but no palette
- FDIBInfoSize := SizeOf(TBitmapInfoHeader);
- // Allocate TBitmapInfo structure
- GetMem(FDIBInfo, FDIBInfoSize);
- try
- FDIBInfo^.bmiHeader.biSize := SizeOf(FDIBInfo^.bmiHeader);
- FDIBInfo^.bmiHeader.biWidth := Width;
- FDIBInfo^.bmiHeader.biHeight := Height;
- FDIBInfo^.bmiHeader.biPlanes := 1;
- FDIBInfo^.bmiHeader.biSizeImage := 0;
- FDIBInfo^.bmiHeader.biCompression := BI_RGB;
- if (PixelFormat = pf8bit) then
- begin
- FDIBInfo^.bmiHeader.biBitCount := 8;
- // Find number of colors defined by palette
- if (Palette <> 0) and
- (GetObject(Palette, sizeof(SrcColors), @SrcColors) <> 0) and
- (SrcColors <> 0) then
- begin
- // Copy all colors...
- GetPaletteEntries(Palette, 0, SrcColors, FDIBInfo^.bmiColors[0]);
- // ...and convert BGR to RGB
- ByteSwapColors(FDIBInfo^.bmiColors[0], SrcColors);
- end else
- SrcColors := 0;
- // Finally zero any unused entried
- if (SrcColors < 256) then
- FillChar(pointer(LongInt(@FDIBInfo^.bmiColors)+SizeOf(TRGBQuad)*SrcColors)^,
- 256 - SrcColors, 0);
- FDIBInfo^.bmiHeader.biClrUsed := 256;
- FDIBInfo^.bmiHeader.biClrImportant := SrcColors;
- end else
- begin
- FDIBInfo^.bmiHeader.biBitCount := 24;
- FDIBInfo^.bmiHeader.biClrUsed := 0;
- FDIBInfo^.bmiHeader.biClrImportant := 0;
- end;
- FDIBBitsSize := AlignBit(Width, FDIBInfo^.bmiHeader.biBitCount, 32) * Cardinal(abs(Height));
- {$ifdef CREATEDIBSECTION_SLOW}
- FDIBBits := GlobalAllocPtr(GMEM_MOVEABLE, FDIBBitsSize);
- if (FDIBBits = nil) then
- raise EOutOfMemory.Create(sOutOfMemDIB);
- {$else}
- // ScreenDC := GDICheck(GetDC(0));
- try
- // Allocate DIB section
- // Note: You can ignore warnings about the HDC parameter being 0. The
- // parameter is not used for 24 bit bitmaps
- FDIB := GDICheck(CreateDIBSection(0 {ScreenDC}, FDIBInfo^, DIB_RGB_COLORS,
- FDIBBits,
- {$IFDEF VER9x} nil, {$ELSE} 0, {$ENDIF}
- 0));
- finally
- // ReleaseDC(0, ScreenDC);
- end;
- {$endif}
- except
- FreeDIB;
- raise;
- end;
- {$endif}
- end;
- procedure TDIBWriter.FreeDIB;
- begin
- {$ifdef PIXELFORMAT_TOO_SLOW}
- if (FDIBInfo <> nil) then
- FreeMem(FDIBInfo);
- {$ifdef CREATEDIBSECTION_SLOW}
- if (FDIBBits <> nil) then
- GlobalFreePtr(FDIBBits);
- {$else}
- if (FDIB <> 0) then
- DeleteObject(FDIB);
- FDIB := 0;
- {$endif}
- FDIBInfo := nil;
- FDIBBits := nil;
- {$endif}
- end;
- procedure TDIBWriter.NeedDIB;
- begin
- {$ifdef PIXELFORMAT_TOO_SLOW}
- {$ifdef CREATEDIBSECTION_SLOW}
- if (FDIBBits = nil) then
- {$else}
- if (FDIB = 0) then
- {$endif}
- CreateDIB;
- {$endif}
- end;
- // Convert the DIB created by CreateDIB back to a TBitmap
- procedure TDIBWriter.UpdateBitmap;
- {$ifdef PIXELFORMAT_TOO_SLOW}
- var
- Stream : TMemoryStream;
- FileSize : longInt;
- BitmapFileHeader : TBitmapFileHeader;
- {$endif}
- begin
- {$ifdef PIXELFORMAT_TOO_SLOW}
- {$ifdef CREATEDIBSECTION_SLOW}
- if (FDIBBits = nil) then
- {$else}
- if (FDIB = 0) then
- {$endif}
- exit;
- // Win95 and NT differs in what solution performs best
- {$ifndef CREATEDIBSECTION_SLOW}
- {$ifdef VER10_PLUS}
- if (Win32Platform = VER_PLATFORM_WIN32_NT) then
- begin
- // Assign DIB to bitmap
- FBitmap.Handle := FDIB;
- FDIB := 0;
- FBitmap.Palette := CopyPalette(Palette);
- end else
- {$endif}
- {$endif}
- begin
- // Write DIB to a stream in the BMP file format
- Stream := TMemoryStream.Create;
- try
- // Make room in stream for a TBitmapInfo and pixel data
- FileSize := sizeof(TBitmapFileHeader) + FDIBInfoSize + FDIBBitsSize;
- Stream.SetSize(FileSize);
- // Initialize file header
- FillChar(BitmapFileHeader, sizeof(TBitmapFileHeader), 0);
- with BitmapFileHeader do
- begin
- bfType := $4D42; // 'BM' = Windows BMP signature
- bfSize := FileSize; // File size (not needed)
- bfOffBits := sizeof(TBitmapFileHeader) + FDIBInfoSize; // Offset of pixel data
- end;
- // Save file header
- Stream.Write(BitmapFileHeader, sizeof(TBitmapFileHeader));
- // Save TBitmapInfo structure
- Stream.Write(FDIBInfo^, FDIBInfoSize);
- // Save pixel data
- Stream.Write(FDIBBits^, FDIBBitsSize);
- // Rewind and load bitmap from stream
- Stream.Position := 0;
- FBitmap.LoadFromStream(Stream);
- finally
- Stream.Free;
- end;
- end;
- {$endif}
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Color Mapping
- //
- ////////////////////////////////////////////////////////////////////////////////
- type
- TColorLookup = class(TObject)
- private
- FColors : integer;
- public
- constructor Create(Palette: hPalette); virtual;
- function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; virtual; abstract;
- property Colors: integer read FColors;
- end;
- PRGBQuadArray = ^TRGBQuadArray; // From Delphi 3 graphics.pas
- TRGBQuadArray = array[Byte] of TRGBQuad; // From Delphi 3 graphics.pas
- BGRArray = array[0..0] of TRGBTriple;
- PBGRArray = ^BGRArray;
- PalArray = array[byte] of TPaletteEntry;
- PPalArray = ^PalArray;
- // TFastColorLookup implements a simple but reasonably fast generic color
- // mapper. It trades precision for speed by reducing the size of the color
- // space.
- // Using a class instead of inline code results in a speed penalty of
- // approx. 15% but reduces the complexity of the color reduction routines that
- // uses it. If bitmap to GIF conversion speed is really important to you, the
- // implementation can easily be inlined again.
- TInverseLookup = array[0..1 SHL 15-1] of SmallInt;
- PInverseLookup = ^TInverseLookup;
- TFastColorLookup = class(TColorLookup)
- private
- FPaletteEntries : PPalArray;
- FInverseLookup : PInverseLookup;
- public
- constructor Create(Palette: hPalette); override;
- destructor Destroy; override;
- function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; override;
- end;
- // TSlowColorLookup implements a precise but very slow generic color mapper.
- // It uses the GetNearestPaletteIndex GDI function.
- // Note: Tests has shown TFastColorLookup to be more precise than
- // TSlowColorLookup in many cases. I can't explain why...
- TSlowColorLookup = class(TColorLookup)
- private
- FPaletteEntries : PPalArray;
- FPalette : hPalette;
- public
- constructor Create(Palette: hPalette); override;
- destructor Destroy; override;
- function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; override;
- end;
- // TNetscapeColorLookup maps colors to the netscape 6*6*6 color cube.
- TNetscapeColorLookup = class(TColorLookup)
- public
- constructor Create(Palette: hPalette); override;
- function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; override;
- end;
- // TGrayWindowsLookup maps colors to 4 shade palette.
- TGrayWindowsLookup = class(TSlowColorLookup)
- public
- constructor Create(Palette: hPalette); override;
- function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; override;
- end;
- // TGrayScaleLookup maps colors to a uniform 256 shade palette.
- TGrayScaleLookup = class(TColorLookup)
- public
- constructor Create(Palette: hPalette); override;
- function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; override;
- end;
- // TMonochromeLookup maps colors to a black/white palette.
- TMonochromeLookup = class(TColorLookup)
- public
- constructor Create(Palette: hPalette); override;
- function Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; override;
- end;
- constructor TColorLookup.Create(Palette: hPalette);
- begin
- inherited Create;
- end;
- constructor TFastColorLookup.Create(Palette: hPalette);
- var
- i : integer;
- InverseIndex : integer;
- begin
- inherited Create(Palette);
- GetMem(FPaletteEntries, sizeof(TPaletteEntry) * 256);
- FColors := GetPaletteEntries(Palette, 0, 256, FPaletteEntries^);
- New(FInverseLookup);
- for i := low(TInverseLookup) to high(TInverseLookup) do
- FInverseLookup^[i] := -1;
- // Premap palette colors
- if (FColors > 0) then
- for i := 0 to FColors-1 do
- with FPaletteEntries^[i] do
- begin
- InverseIndex := (peRed SHR 3) OR ((peGreen AND $F8) SHL 2) OR ((peBlue AND $F8) SHL 7);
- if (FInverseLookup^[InverseIndex] = -1) then
- FInverseLookup^[InverseIndex] := i;
- end;
- end;
- destructor TFastColorLookup.Destroy;
- begin
- if (FPaletteEntries <> nil) then
- FreeMem(FPaletteEntries);
- if (FInverseLookup <> nil) then
- Dispose(FInverseLookup);
- inherited Destroy;
- end;
- // Map color to arbitrary palette
- function TFastColorLookup.Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
- var
- i : integer;
- InverseIndex : integer;
- Delta ,
- MinDelta ,
- MinColor : integer;
- begin
- // Reduce color space with 3 bits in each dimension
- InverseIndex := (Red SHR 3) OR ((Green AND $F8) SHL 2) OR ((Blue AND $F8) SHL 7);
- if (FInverseLookup^[InverseIndex] <> -1) then
- Result := char(FInverseLookup^[InverseIndex])
- else
- begin
- // Sequential scan for nearest color to minimize euclidian distance
- MinDelta := 3 * (256 * 256);
- MinColor := 0;
- for i := 0 to FColors-1 do
- with FPaletteEntries[i] do
- begin
- Delta := ABS(peRed - Red) + ABS(peGreen - Green) + ABS(peBlue - Blue);
- if (Delta < MinDelta) then
- begin
- MinDelta := Delta;
- MinColor := i;
- end;
- end;
- Result := char(MinColor);
- FInverseLookup^[InverseIndex] := MinColor;
- end;
- with FPaletteEntries^[ord(Result)] do
- begin
- R := peRed;
- G := peGreen;
- B := peBlue;
- end;
- end;
- constructor TSlowColorLookup.Create(Palette: hPalette);
- begin
- inherited Create(Palette);
- FPalette := Palette;
- FColors := GetPaletteEntries(Palette, 0, 256, nil^);
- if (FColors > 0) then
- begin
- GetMem(FPaletteEntries, sizeof(TPaletteEntry) * FColors);
- FColors := GetPaletteEntries(Palette, 0, 256, FPaletteEntries^);
- end;
- end;
- destructor TSlowColorLookup.Destroy;
- begin
- if (FPaletteEntries <> nil) then
- FreeMem(FPaletteEntries);
- inherited Destroy;
- end;
- // Map color to arbitrary palette
- function TSlowColorLookup.Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
- begin
- Result := char(GetNearestPaletteIndex(FPalette, Red OR (Green SHL 8) OR (Blue SHL 16)));
- if (FPaletteEntries <> nil) then
- with FPaletteEntries^[ord(Result)] do
- begin
- R := peRed;
- G := peGreen;
- B := peBlue;
- end;
- end;
- constructor TNetscapeColorLookup.Create(Palette: hPalette);
- begin
- inherited Create(Palette);
- FColors := 6*6*6; // This better be true or something is wrong
- end;
- // Map color to netscape 6*6*6 color cube
- function TNetscapeColorLookup.Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
- begin
- R := (Red+3) DIV 51;
- G := (Green+3) DIV 51;
- B := (Blue+3) DIV 51;
- Result := char(B + 6*G + 36*R);
- R := R * 51;
- G := G * 51;
- B := B * 51;
- end;
- constructor TGrayWindowsLookup.Create(Palette: hPalette);
- begin
- inherited Create(Palette);
- FColors := 4;
- end;
- // Convert color to windows grays
- function TGrayWindowsLookup.Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
- begin
- Result := inherited Lookup(MulDiv(Red, 77, 256),
- MulDiv(Green, 150, 256), MulDiv(Blue, 29, 256), R, G, B);
- end;
- constructor TGrayScaleLookup.Create(Palette: hPalette);
- begin
- inherited Create(Palette);
- FColors := 256;
- end;
- // Convert color to grayscale
- function TGrayScaleLookup.Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
- begin
- Result := char((Blue*29 + Green*150 + Red*77) DIV 256);
- R := ord(Result);
- G := ord(Result);
- B := ord(Result);
- end;
- constructor TMonochromeLookup.Create(Palette: hPalette);
- begin
- inherited Create(Palette);
- FColors := 2;
- end;
- // Convert color to black/white
- function TMonochromeLookup.Lookup(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
- begin
- if ((Blue*29 + Green*150 + Red*77) > 32512) then
- begin
- Result := #1;
- R := 255;
- G := 255;
- B := 255;
- end else
- begin
- Result := #0;
- R := 0;
- G := 0;
- B := 0;
- end;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Dithering engine
- //
- ////////////////////////////////////////////////////////////////////////////////
- type
- TDitherEngine = class
- private
- protected
- FDirection : integer;
- FColumn : integer;
- FLookup : TColorLookup;
- Width : integer;
- public
- constructor Create(AWidth: integer; Lookup: TColorLookup); virtual;
- function Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; virtual;
- procedure NextLine; virtual;
- procedure NextColumn;
- property Direction: integer read FDirection;
- property Column: integer read FColumn;
- end;
- // Note: TErrorTerm does only *need* to be 16 bits wide, but since
- // it is *much* faster to use native machine words (32 bit), we sacrifice
- // some bytes (a lot actually) to improve performance.
- TErrorTerm = Integer;
- TErrors = array[0..0] of TErrorTerm;
- PErrors = ^TErrors;
- TFloydSteinbergDitherer = class(TDitherEngine)
- private
- ErrorsR ,
- ErrorsG ,
- ErrorsB : PErrors;
- ErrorR ,
- ErrorG ,
- ErrorB : PErrors;
- CurrentErrorR , // Current error or pixel value
- CurrentErrorG ,
- CurrentErrorB ,
- BelowErrorR , // Error for pixel below current
- BelowErrorG ,
- BelowErrorB ,
- BelowPrevErrorR , // Error for pixel below previous pixel
- BelowPrevErrorG ,
- BelowPrevErrorB : TErrorTerm;
- public
- constructor Create(AWidth: integer; Lookup: TColorLookup); override;
- destructor Destroy; override;
- function Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; override;
- procedure NextLine; override;
- end;
- T5by3Ditherer = class(TDitherEngine)
- private
- ErrorsR0 ,
- ErrorsG0 ,
- ErrorsB0 ,
- ErrorsR1 ,
- ErrorsG1 ,
- ErrorsB1 ,
- ErrorsR2 ,
- ErrorsG2 ,
- ErrorsB2 : PErrors;
- ErrorR0 ,
- ErrorG0 ,
- ErrorB0 ,
- ErrorR1 ,
- ErrorG1 ,
- ErrorB1 ,
- ErrorR2 ,
- ErrorG2 ,
- ErrorB2 : PErrors;
- FDirection2 : integer;
- protected
- FDivisor : integer;
- procedure Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer); virtual; abstract;
- public
- constructor Create(AWidth: integer; Lookup: TColorLookup); override;
- destructor Destroy; override;
- function Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; override;
- procedure NextLine; override;
- end;
- TStuckiDitherer = class(T5by3Ditherer)
- protected
- procedure Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer); override;
- public
- constructor Create(AWidth: integer; Lookup: TColorLookup); override;
- end;
- TSierraDitherer = class(T5by3Ditherer)
- protected
- procedure Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer); override;
- public
- constructor Create(AWidth: integer; Lookup: TColorLookup); override;
- end;
- TJaJuNiDitherer = class(T5by3Ditherer)
- protected
- procedure Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer); override;
- public
- constructor Create(AWidth: integer; Lookup: TColorLookup); override;
- end;
- TSteveArcheDitherer = class(TDitherEngine)
- private
- ErrorsR0 ,
- ErrorsG0 ,
- ErrorsB0 ,
- ErrorsR1 ,
- ErrorsG1 ,
- ErrorsB1 ,
- ErrorsR2 ,
- ErrorsG2 ,
- ErrorsB2 ,
- ErrorsR3 ,
- ErrorsG3 ,
- ErrorsB3 : PErrors;
- ErrorR0 ,
- ErrorG0 ,
- ErrorB0 ,
- ErrorR1 ,
- ErrorG1 ,
- ErrorB1 ,
- ErrorR2 ,
- ErrorG2 ,
- ErrorB2 ,
- ErrorR3 ,
- ErrorG3 ,
- ErrorB3 : PErrors;
- FDirection2 ,
- FDirection3 : integer;
- public
- constructor Create(AWidth: integer; Lookup: TColorLookup); override;
- destructor Destroy; override;
- function Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; override;
- procedure NextLine; override;
- end;
- TBurkesDitherer = class(TDitherEngine)
- private
- ErrorsR0 ,
- ErrorsG0 ,
- ErrorsB0 ,
- ErrorsR1 ,
- ErrorsG1 ,
- ErrorsB1 : PErrors;
- ErrorR0 ,
- ErrorG0 ,
- ErrorB0 ,
- ErrorR1 ,
- ErrorG1 ,
- ErrorB1 : PErrors;
- FDirection2 : integer;
- public
- constructor Create(AWidth: integer; Lookup: TColorLookup); override;
- destructor Destroy; override;
- function Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char; override;
- procedure NextLine; override;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- // TDitherEngine
- constructor TDitherEngine.Create(AWidth: integer; Lookup: TColorLookup);
- begin
- inherited Create;
- FLookup := Lookup;
- Width := AWidth;
- FDirection := 1;
- FColumn := 0;
- end;
- function TDitherEngine.Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
- begin
- // Map color to palette
- Result := FLookup.Lookup(Red, Green, Blue, R, G, B);
- NextColumn;
- end;
- procedure TDitherEngine.NextLine;
- begin
- FDirection := -FDirection;
- if (FDirection = 1) then
- FColumn := 0
- else
- FColumn := Width-1;
- end;
- procedure TDitherEngine.NextColumn;
- begin
- inc(FColumn, FDirection);
- end;
- ////////////////////////////////////////////////////////////////////////////////
- // TFloydSteinbergDitherer
- constructor TFloydSteinbergDitherer.Create(AWidth: integer; Lookup: TColorLookup);
- begin
- inherited Create(AWidth, Lookup);
- // The Error arrays has (columns + 2) entries; the extra entry at
- // each end saves us from special-casing the first and last pixels.
- // We can get away with a single array (holding one row's worth of errors)
- // by using it to store the current row's errors at pixel columns not yet
- // processed, but the next row's errors at columns already processed. We
- // need only a few extra variables to hold the errors immediately around the
- // current column. (If we are lucky, those variables are in registers, but
- // even if not, they're probably cheaper to access than array elements are.)
- GetMem(ErrorsR, sizeof(TErrorTerm)*(Width+2));
- GetMem(ErrorsG, sizeof(TErrorTerm)*(Width+2));
- GetMem(ErrorsB, sizeof(TErrorTerm)*(Width+2));
- FillChar(ErrorsR^, sizeof(TErrorTerm)*(Width+2), 0);
- FillChar(ErrorsG^, sizeof(TErrorTerm)*(Width+2), 0);
- FillChar(ErrorsB^, sizeof(TErrorTerm)*(Width+2), 0);
- ErrorR := ErrorsR;
- ErrorG := ErrorsG;
- ErrorB := ErrorsB;
- CurrentErrorR := 0;
- CurrentErrorG := CurrentErrorR;
- CurrentErrorB := CurrentErrorR;
- BelowErrorR := CurrentErrorR;
- BelowErrorG := CurrentErrorR;
- BelowErrorB := CurrentErrorR;
- BelowPrevErrorR := CurrentErrorR;
- BelowPrevErrorG := CurrentErrorR;
- BelowPrevErrorB := CurrentErrorR;
- end;
- destructor TFloydSteinbergDitherer.Destroy;
- begin
- FreeMem(ErrorsR);
- FreeMem(ErrorsG);
- FreeMem(ErrorsB);
- inherited Destroy;
- end;
- {$IFOPT R+}
- {$DEFINE R_PLUS}
- {$RANGECHECKS OFF}
- {$ENDIF}
- function TFloydSteinbergDitherer.Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
- var
- BelowNextError : TErrorTerm;
- Delta : TErrorTerm;
- begin
- CurrentErrorR := Red + (CurrentErrorR + ErrorR[0] + 8) DIV 16;
- // CurrentErrorR := Red + (CurrentErrorR + ErrorR[Direction] + 8) DIV 16;
- if (CurrentErrorR < 0) then
- CurrentErrorR := 0
- else if (CurrentErrorR > 255) then
- CurrentErrorR := 255;
- CurrentErrorG := Green + (CurrentErrorG + ErrorG[0] + 8) DIV 16;
- // CurrentErrorG := Green + (CurrentErrorG + ErrorG[Direction] + 8) DIV 16;
- if (CurrentErrorG < 0) then
- CurrentErrorG := 0
- else if (CurrentErrorG > 255) then
- CurrentErrorG := 255;
- CurrentErrorB := Blue + (CurrentErrorB + ErrorB[0] + 8) DIV 16;
- // CurrentErrorB := Blue + (CurrentErrorB + ErrorB[Direction] + 8) DIV 16;
- if (CurrentErrorB < 0) then
- CurrentErrorB := 0
- else if (CurrentErrorB > 255) then
- CurrentErrorB := 255;
- // Map color to palette
- Result := inherited Dither(CurrentErrorR, CurrentErrorG, CurrentErrorB, R, G, B);
- // Propagate Floyd-Steinberg error terms.
- // Errors are accumulated into the error arrays, at a resolution of
- // 1/16th of a pixel count. The error at a given pixel is propagated
- // to its not-yet-processed neighbors using the standard F-S fractions,
- // ... (here) 7/16
- // 3/16 5/16 1/16
- // We work left-to-right on even rows, right-to-left on odd rows.
- // Red component
- CurrentErrorR := CurrentErrorR - R;
- if (CurrentErrorR <> 0) then
- begin
- BelowNextError := CurrentErrorR; // Error * 1
- Delta := CurrentErrorR * 2;
- inc(CurrentErrorR, Delta);
- ErrorR[0] := BelowPrevErrorR + CurrentErrorR; // Error * 3
- inc(CurrentErrorR, Delta);
- BelowPrevErrorR := BelowErrorR + CurrentErrorR; // Error * 5
- BelowErrorR := BelowNextError; // Error * 1
- inc(CurrentErrorR, Delta); // Error * 7
- end;
- // Green component
- CurrentErrorG := CurrentErrorG - G;
- if (CurrentErrorG <> 0) then
- begin
- BelowNextError := CurrentErrorG; // Error * 1
- Delta := CurrentErrorG * 2;
- inc(CurrentErrorG, Delta);
- ErrorG[0] := BelowPrevErrorG + CurrentErrorG; // Error * 3
- inc(CurrentErrorG, Delta);
- BelowPrevErrorG := BelowErrorG + CurrentErrorG; // Error * 5
- BelowErrorG := BelowNextError; // Error * 1
- inc(CurrentErrorG, Delta); // Error * 7
- end;
- // Blue component
- CurrentErrorB := CurrentErrorB - B;
- if (CurrentErrorB <> 0) then
- begin
- BelowNextError := CurrentErrorB; // Error * 1
- Delta := CurrentErrorB * 2;
- inc(CurrentErrorB, Delta);
- ErrorB[0] := BelowPrevErrorB + CurrentErrorB; // Error * 3
- inc(CurrentErrorB, Delta);
- BelowPrevErrorB := BelowErrorB + CurrentErrorB; // Error * 5
- BelowErrorB := BelowNextError; // Error * 1
- inc(CurrentErrorB, Delta); // Error * 7
- end;
- // Move on to next column
- if (Direction = 1) then
- begin
- inc(longInt(ErrorR), sizeof(TErrorTerm));
- inc(longInt(ErrorG), sizeof(TErrorTerm));
- inc(longInt(ErrorB), sizeof(TErrorTerm));
- end else
- begin
- dec(longInt(ErrorR), sizeof(TErrorTerm));
- dec(longInt(ErrorG), sizeof(TErrorTerm));
- dec(longInt(ErrorB), sizeof(TErrorTerm));
- end;
- end;
- {$IFDEF R_PLUS}
- {$RANGECHECKS ON}
- {$UNDEF R_PLUS}
- {$ENDIF}
- {$IFOPT R+}
- {$DEFINE R_PLUS}
- {$RANGECHECKS OFF}
- {$ENDIF}
- procedure TFloydSteinbergDitherer.NextLine;
- begin
- ErrorR[0] := BelowPrevErrorR;
- ErrorG[0] := BelowPrevErrorG;
- ErrorB[0] := BelowPrevErrorB;
- // Note: The optimizer produces better code for this construct:
- // a := 0; b := a; c := a;
- // compared to this construct:
- // a := 0; b := 0; c := 0;
- CurrentErrorR := 0;
- CurrentErrorG := CurrentErrorR;
- CurrentErrorB := CurrentErrorG;
- BelowErrorR := CurrentErrorG;
- BelowErrorG := CurrentErrorG;
- BelowErrorB := CurrentErrorG;
- BelowPrevErrorR := CurrentErrorG;
- BelowPrevErrorG := CurrentErrorG;
- BelowPrevErrorB := CurrentErrorG;
- inherited NextLine;
- if (Direction = 1) then
- begin
- ErrorR := ErrorsR;
- ErrorG := ErrorsG;
- ErrorB := ErrorsB;
- end else
- begin
- ErrorR := @ErrorsR[Width+1];
- ErrorG := @ErrorsG[Width+1];
- ErrorB := @ErrorsB[Width+1];
- end;
- end;
- {$IFDEF R_PLUS}
- {$RANGECHECKS ON}
- {$UNDEF R_PLUS}
- {$ENDIF}
- ////////////////////////////////////////////////////////////////////////////////
- // T5by3Ditherer
- constructor T5by3Ditherer.Create(AWidth: integer; Lookup: TColorLookup);
- begin
- inherited Create(AWidth, Lookup);
- GetMem(ErrorsR0, sizeof(TErrorTerm)*(Width+4));
- GetMem(ErrorsG0, sizeof(TErrorTerm)*(Width+4));
- GetMem(ErrorsB0, sizeof(TErrorTerm)*(Width+4));
- GetMem(ErrorsR1, sizeof(TErrorTerm)*(Width+4));
- GetMem(ErrorsG1, sizeof(TErrorTerm)*(Width+4));
- GetMem(ErrorsB1, sizeof(TErrorTerm)*(Width+4));
- GetMem(ErrorsR2, sizeof(TErrorTerm)*(Width+4));
- GetMem(ErrorsG2, sizeof(TErrorTerm)*(Width+4));
- GetMem(ErrorsB2, sizeof(TErrorTerm)*(Width+4));
- FillChar(ErrorsR0^, sizeof(TErrorTerm)*(Width+4), 0);
- FillChar(ErrorsG0^, sizeof(TErrorTerm)*(Width+4), 0);
- FillChar(ErrorsB0^, sizeof(TErrorTerm)*(Width+4), 0);
- FillChar(ErrorsR1^, sizeof(TErrorTerm)*(Width+4), 0);
- FillChar(ErrorsG1^, sizeof(TErrorTerm)*(Width+4), 0);
- FillChar(ErrorsB1^, sizeof(TErrorTerm)*(Width+4), 0);
- FillChar(ErrorsR2^, sizeof(TErrorTerm)*(Width+4), 0);
- FillChar(ErrorsG2^, sizeof(TErrorTerm)*(Width+4), 0);
- FillChar(ErrorsB2^, sizeof(TErrorTerm)*(Width+4), 0);
- FDivisor := 1;
- FDirection2 := 2 * Direction;
- ErrorR0 := PErrors(longInt(ErrorsR0)+2*sizeof(TErrorTerm));
- ErrorG0 := PErrors(longInt(ErrorsG0)+2*sizeof(TErrorTerm));
- ErrorB0 := PErrors(longInt(ErrorsB0)+2*sizeof(TErrorTerm));
- ErrorR1 := PErrors(longInt(ErrorsR1)+2*sizeof(TErrorTerm));
- ErrorG1 := PErrors(longInt(ErrorsG1)+2*sizeof(TErrorTerm));
- ErrorB1 := PErrors(longInt(ErrorsB1)+2*sizeof(TErrorTerm));
- ErrorR2 := PErrors(longInt(ErrorsR2)+2*sizeof(TErrorTerm));
- ErrorG2 := PErrors(longInt(ErrorsG2)+2*sizeof(TErrorTerm));
- ErrorB2 := PErrors(longInt(ErrorsB2)+2*sizeof(TErrorTerm));
- end;
- destructor T5by3Ditherer.Destroy;
- begin
- FreeMem(ErrorsR0);
- FreeMem(ErrorsG0);
- FreeMem(ErrorsB0);
- FreeMem(ErrorsR1);
- FreeMem(ErrorsG1);
- FreeMem(ErrorsB1);
- FreeMem(ErrorsR2);
- FreeMem(ErrorsG2);
- FreeMem(ErrorsB2);
- inherited Destroy;
- end;
- {$IFOPT R+}
- {$DEFINE R_PLUS}
- {$RANGECHECKS OFF}
- {$ENDIF}
- function T5by3Ditherer.Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
- var
- ColorR ,
- ColorG ,
- ColorB : integer; // Error for current pixel
- begin
- // Apply red component error correction
- ColorR := Red + (ErrorR0[0] + FDivisor DIV 2) DIV FDivisor;
- if (ColorR < 0) then
- ColorR := 0
- else if (ColorR > 255) then
- ColorR := 255;
- // Apply green component error correction
- ColorG := Green + (ErrorG0[0] + FDivisor DIV 2) DIV FDivisor;
- if (ColorG < 0) then
- ColorG := 0
- else if (ColorG > 255) then
- ColorG := 255;
- // Apply blue component error correction
- ColorB := Blue + (ErrorB0[0] + FDivisor DIV 2) DIV FDivisor;
- if (ColorB < 0) then
- ColorB := 0
- else if (ColorB > 255) then
- ColorB := 255;
- // Map color to palette
- Result := inherited Dither(ColorR, ColorG, ColorB, R, G, B);
- // Propagate red component error
- Propagate(ErrorR0, ErrorR1, ErrorR2, ColorR - R);
- // Propagate green component error
- Propagate(ErrorG0, ErrorG1, ErrorG2, ColorG - G);
- // Propagate blue component error
- Propagate(ErrorB0, ErrorB1, ErrorB2, ColorB - B);
- // Move on to next column
- if (Direction = 1) then
- begin
- inc(longInt(ErrorR0), sizeof(TErrorTerm));
- inc(longInt(ErrorG0), sizeof(TErrorTerm));
- inc(longInt(ErrorB0), sizeof(TErrorTerm));
- inc(longInt(ErrorR1), sizeof(TErrorTerm));
- inc(longInt(ErrorG1), sizeof(TErrorTerm));
- inc(longInt(ErrorB1), sizeof(TErrorTerm));
- inc(longInt(ErrorR2), sizeof(TErrorTerm));
- inc(longInt(ErrorG2), sizeof(TErrorTerm));
- inc(longInt(ErrorB2), sizeof(TErrorTerm));
- end else
- begin
- dec(longInt(ErrorR0), sizeof(TErrorTerm));
- dec(longInt(ErrorG0), sizeof(TErrorTerm));
- dec(longInt(ErrorB0), sizeof(TErrorTerm));
- dec(longInt(ErrorR1), sizeof(TErrorTerm));
- dec(longInt(ErrorG1), sizeof(TErrorTerm));
- dec(longInt(ErrorB1), sizeof(TErrorTerm));
- dec(longInt(ErrorR2), sizeof(TErrorTerm));
- dec(longInt(ErrorG2), sizeof(TErrorTerm));
- dec(longInt(ErrorB2), sizeof(TErrorTerm));
- end;
- end;
- {$IFDEF R_PLUS}
- {$RANGECHECKS ON}
- {$UNDEF R_PLUS}
- {$ENDIF}
- {$IFOPT R+}
- {$DEFINE R_PLUS}
- {$RANGECHECKS OFF}
- {$ENDIF}
- procedure T5by3Ditherer.NextLine;
- var
- TempErrors : PErrors;
- begin
- FillChar(ErrorsR0^, sizeof(TErrorTerm)*(Width+4), 0);
- FillChar(ErrorsG0^, sizeof(TErrorTerm)*(Width+4), 0);
- FillChar(ErrorsB0^, sizeof(TErrorTerm)*(Width+4), 0);
- // Swap lines
- TempErrors := ErrorsR0;
- ErrorsR0 := ErrorsR1;
- ErrorsR1 := ErrorsR2;
- ErrorsR2 := TempErrors;
- TempErrors := ErrorsG0;
- ErrorsG0 := ErrorsG1;
- ErrorsG1 := ErrorsG2;
- ErrorsG2 := TempErrors;
- TempErrors := ErrorsB0;
- ErrorsB0 := ErrorsB1;
- ErrorsB1 := ErrorsB2;
- ErrorsB2 := TempErrors;
- inherited NextLine;
- FDirection2 := 2 * Direction;
- if (Direction = 1) then
- begin
- // ErrorsR0[1] gives compiler error, so we
- // use PErrors(longInt(ErrorsR0)+sizeof(TErrorTerm)) instead...
- ErrorR0 := PErrors(longInt(ErrorsR0)+2*sizeof(TErrorTerm));
- ErrorG0 := PErrors(longInt(ErrorsG0)+2*sizeof(TErrorTerm));
- ErrorB0 := PErrors(longInt(ErrorsB0)+2*sizeof(TErrorTerm));
- ErrorR1 := PErrors(longInt(ErrorsR1)+2*sizeof(TErrorTerm));
- ErrorG1 := PErrors(longInt(ErrorsG1)+2*sizeof(TErrorTerm));
- ErrorB1 := PErrors(longInt(ErrorsB1)+2*sizeof(TErrorTerm));
- ErrorR2 := PErrors(longInt(ErrorsR2)+2*sizeof(TErrorTerm));
- ErrorG2 := PErrors(longInt(ErrorsG2)+2*sizeof(TErrorTerm));
- ErrorB2 := PErrors(longInt(ErrorsB2)+2*sizeof(TErrorTerm));
- end else
- begin
- ErrorR0 := @ErrorsR0[Width+1];
- ErrorG0 := @ErrorsG0[Width+1];
- ErrorB0 := @ErrorsB0[Width+1];
- ErrorR1 := @ErrorsR1[Width+1];
- ErrorG1 := @ErrorsG1[Width+1];
- ErrorB1 := @ErrorsB1[Width+1];
- ErrorR2 := @ErrorsR2[Width+1];
- ErrorG2 := @ErrorsG2[Width+1];
- ErrorB2 := @ErrorsB2[Width+1];
- end;
- end;
- {$IFDEF R_PLUS}
- {$RANGECHECKS ON}
- {$UNDEF R_PLUS}
- {$ENDIF}
- ////////////////////////////////////////////////////////////////////////////////
- // TStuckiDitherer
- constructor TStuckiDitherer.Create(AWidth: integer; Lookup: TColorLookup);
- begin
- inherited Create(AWidth, Lookup);
- FDivisor := 42;
- end;
- {$IFOPT R+}
- {$DEFINE R_PLUS}
- {$RANGECHECKS OFF}
- {$ENDIF}
- procedure TStuckiDitherer.Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer);
- begin
- if (Error = 0) then
- exit;
- // Propagate Stucki error terms:
- // ... ... (here) 8/42 4/42
- // 2/42 4/42 8/42 4/42 2/42
- // 1/42 2/42 4/42 2/42 1/42
- inc(Errors2[FDirection2], Error); // Error * 1
- inc(Errors2[-FDirection2], Error); // Error * 1
- Error := Error + Error;
- inc(Errors1[FDirection2], Error); // Error * 2
- inc(Errors1[-FDirection2], Error); // Error * 2
- inc(Errors2[Direction], Error); // Error * 2
- inc(Errors2[-Direction], Error); // Error * 2
- Error := Error + Error;
- inc(Errors0[FDirection2], Error); // Error * 4
- inc(Errors1[-Direction], Error); // Error * 4
- inc(Errors1[Direction], Error); // Error * 4
- inc(Errors2[0], Error); // Error * 4
- Error := Error + Error;
- inc(Errors0[Direction], Error); // Error * 8
- inc(Errors1[0], Error); // Error * 8
- end;
- {$IFDEF R_PLUS}
- {$RANGECHECKS ON}
- {$UNDEF R_PLUS}
- {$ENDIF}
- ////////////////////////////////////////////////////////////////////////////////
- // TSierraDitherer
- constructor TSierraDitherer.Create(AWidth: integer; Lookup: TColorLookup);
- begin
- inherited Create(AWidth, Lookup);
- FDivisor := 32;
- end;
- {$IFOPT R+}
- {$DEFINE R_PLUS}
- {$RANGECHECKS OFF}
- {$ENDIF}
- procedure TSierraDitherer.Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer);
- var
- TempError : integer;
- begin
- if (Error = 0) then
- exit;
- // Propagate Sierra error terms:
- // ... ... (here) 5/32 3/32
- // 2/32 4/32 5/32 4/32 2/32
- // ... 2/32 3/32 2/32 ...
- TempError := Error + Error;
- inc(Errors1[FDirection2], TempError); // Error * 2
- inc(Errors1[-FDirection2], TempError);// Error * 2
- inc(Errors2[Direction], TempError); // Error * 2
- inc(Errors2[-Direction], TempError); // Error * 2
- inc(TempError, Error);
- inc(Errors0[FDirection2], TempError); // Error * 3
- inc(Errors2[0], TempError); // Error * 3
- inc(TempError, Error);
- inc(Errors1[-Direction], TempError); // Error * 4
- inc(Errors1[Direction], TempError); // Error * 4
- inc(TempError, Error);
- inc(Errors0[Direction], TempError); // Error * 5
- inc(Errors1[0], TempError); // Error * 5
- end;
- {$IFDEF R_PLUS}
- {$RANGECHECKS ON}
- {$UNDEF R_PLUS}
- {$ENDIF}
- ////////////////////////////////////////////////////////////////////////////////
- // TJaJuNiDitherer
- constructor TJaJuNiDitherer.Create(AWidth: integer; Lookup: TColorLookup);
- begin
- inherited Create(AWidth, Lookup);
- FDivisor := 38;
- end;
- {$IFOPT R+}
- {$DEFINE R_PLUS}
- {$RANGECHECKS OFF}
- {$ENDIF}
- procedure TJaJuNiDitherer.Propagate(Errors0, Errors1, Errors2: PErrors; Error: integer);
- var
- TempError : integer;
- begin
- if (Error = 0) then
- exit;
- // Propagate Jarvis, Judice and Ninke error terms:
- // ... ... (here) 8/38 4/38
- // 2/38 4/38 8/38 4/38 2/38
- // 1/38 2/38 4/38 2/38 1/38
- inc(Errors2[FDirection2], Error); // Error * 1
- inc(Errors2[-FDirection2], Error); // Error * 1
- TempError := Error + Error;
- inc(Error, TempError);
- inc(Errors1[FDirection2], Error); // Error * 3
- inc(Errors1[-FDirection2], Error); // Error * 3
- inc(Errors2[Direction], Error); // Error * 3
- inc(Errors2[-Direction], Error); // Error * 3
- inc(Error, TempError);
- inc(Errors0[FDirection2], Error); // Error * 5
- inc(Errors1[-Direction], Error); // Error * 5
- inc(Errors1[Direction], Error); // Error * 5
- inc(Errors2[0], Error); // Error * 5
- inc(Error, TempError);
- inc(Errors0[Direction], Error); // Error * 7
- inc(Errors1[0], Error); // Error * 7
- end;
- {$IFDEF R_PLUS}
- {$RANGECHECKS ON}
- {$UNDEF R_PLUS}
- {$ENDIF}
- ////////////////////////////////////////////////////////////////////////////////
- // TSteveArcheDitherer
- constructor TSteveArcheDitherer.Create(AWidth: integer; Lookup: TColorLookup);
- begin
- inherited Create(AWidth, Lookup);
- GetMem(ErrorsR0, sizeof(TErrorTerm)*(Width+6));
- GetMem(ErrorsG0, sizeof(TErrorTerm)*(Width+6));
- GetMem(ErrorsB0, sizeof(TErrorTerm)*(Width+6));
- GetMem(ErrorsR1, sizeof(TErrorTerm)*(Width+6));
- GetMem(ErrorsG1, sizeof(TErrorTerm)*(Width+6));
- GetMem(ErrorsB1, sizeof(TErrorTerm)*(Width+6));
- GetMem(ErrorsR2, sizeof(TErrorTerm)*(Width+6));
- GetMem(ErrorsG2, sizeof(TErrorTerm)*(Width+6));
- GetMem(ErrorsB2, sizeof(TErrorTerm)*(Width+6));
- GetMem(ErrorsR3, sizeof(TErrorTerm)*(Width+6));
- GetMem(ErrorsG3, sizeof(TErrorTerm)*(Width+6));
- GetMem(ErrorsB3, sizeof(TErrorTerm)*(Width+6));
- FillChar(ErrorsR0^, sizeof(TErrorTerm)*(Width+6), 0);
- FillChar(ErrorsG0^, sizeof(TErrorTerm)*(Width+6), 0);
- FillChar(ErrorsB0^, sizeof(TErrorTerm)*(Width+6), 0);
- FillChar(ErrorsR1^, sizeof(TErrorTerm)*(Width+6), 0);
- FillChar(ErrorsG1^, sizeof(TErrorTerm)*(Width+6), 0);
- FillChar(ErrorsB1^, sizeof(TErrorTerm)*(Width+6), 0);
- FillChar(ErrorsR2^, sizeof(TErrorTerm)*(Width+6), 0);
- FillChar(ErrorsG2^, sizeof(TErrorTerm)*(Width+6), 0);
- FillChar(ErrorsB2^, sizeof(TErrorTerm)*(Width+6), 0);
- FillChar(ErrorsR3^, sizeof(TErrorTerm)*(Width+6), 0);
- FillChar(ErrorsG3^, sizeof(TErrorTerm)*(Width+6), 0);
- FillChar(ErrorsB3^, sizeof(TErrorTerm)*(Width+6), 0);
- FDirection2 := 2 * Direction;
- FDirection3 := 3 * Direction;
- ErrorR0 := PErrors(longInt(ErrorsR0)+3*sizeof(TErrorTerm));
- ErrorG0 := PErrors(longInt(ErrorsG0)+3*sizeof(TErrorTerm));
- ErrorB0 := PErrors(longInt(ErrorsB0)+3*sizeof(TErrorTerm));
- ErrorR1 := PErrors(longInt(ErrorsR1)+3*sizeof(TErrorTerm));
- ErrorG1 := PErrors(longInt(ErrorsG1)+3*sizeof(TErrorTerm));
- ErrorB1 := PErrors(longInt(ErrorsB1)+3*sizeof(TErrorTerm));
- ErrorR2 := PErrors(longInt(ErrorsR2)+3*sizeof(TErrorTerm));
- ErrorG2 := PErrors(longInt(ErrorsG2)+3*sizeof(TErrorTerm));
- ErrorB2 := PErrors(longInt(ErrorsB2)+3*sizeof(TErrorTerm));
- ErrorR3 := PErrors(longInt(ErrorsR3)+3*sizeof(TErrorTerm));
- ErrorG3 := PErrors(longInt(ErrorsG3)+3*sizeof(TErrorTerm));
- ErrorB3 := PErrors(longInt(ErrorsB3)+3*sizeof(TErrorTerm));
- end;
- destructor TSteveArcheDitherer.Destroy;
- begin
- FreeMem(ErrorsR0);
- FreeMem(ErrorsG0);
- FreeMem(ErrorsB0);
- FreeMem(ErrorsR1);
- FreeMem(ErrorsG1);
- FreeMem(ErrorsB1);
- FreeMem(ErrorsR2);
- FreeMem(ErrorsG2);
- FreeMem(ErrorsB2);
- FreeMem(ErrorsR3);
- FreeMem(ErrorsG3);
- FreeMem(ErrorsB3);
- inherited Destroy;
- end;
- {$IFOPT R+}
- {$DEFINE R_PLUS}
- {$RANGECHECKS OFF}
- {$ENDIF}
- function TSteveArcheDitherer.Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
- var
- ColorR ,
- ColorG ,
- ColorB : integer; // Error for current pixel
- // Propagate Stevenson & Arche error terms:
- // ... ... ... (here) ... 32/200 ...
- // 12/200 ... 26/200 ... 30/200 ... 16/200
- // ... 12/200 ... 26/200 ... 12/200 ...
- // 5/200 ... 12/200 ... 12/200 ... 5/200
- procedure Propagate(Errors0, Errors1, Errors2, Errors3: PErrors; Error: integer);
- var
- TempError : integer;
- begin
- if (Error = 0) then
- exit;
- TempError := 5 * Error;
- inc(Errors3[FDirection3], TempError); // Error * 5
- inc(Errors3[-FDirection3], TempError); // Error * 5
- TempError := 12 * Error;
- inc(Errors1[-FDirection3], TempError); // Error * 12
- inc(Errors2[-FDirection2], TempError); // Error * 12
- inc(Errors2[FDirection2], TempError); // Error * 12
- inc(Errors3[-Direction], TempError); // Error * 12
- inc(Errors3[Direction], TempError); // Error * 12
- inc(Errors1[FDirection3], 16 * TempError); // Error * 16
- TempError := 26 * Error;
- inc(Errors1[-Direction], TempError); // Error * 26
- inc(Errors2[0], TempError); // Error * 26
- inc(Errors1[Direction], 30 * Error); // Error * 30
- inc(Errors0[FDirection2], 32 * Error); // Error * 32
- end;
- begin
- // Apply red component error correction
- ColorR := Red + (ErrorR0[0] + 100) DIV 200;
- if (ColorR < 0) then
- ColorR := 0
- else if (ColorR > 255) then
- ColorR := 255;
- // Apply green component error correction
- ColorG := Green + (ErrorG0[0] + 100) DIV 200;
- if (ColorG < 0) then
- ColorG := 0
- else if (ColorG > 255) then
- ColorG := 255;
- // Apply blue component error correction
- ColorB := Blue + (ErrorB0[0] + 100) DIV 200;
- if (ColorB < 0) then
- ColorB := 0
- else if (ColorB > 255) then
- ColorB := 255;
- // Map color to palette
- Result := inherited Dither(ColorR, ColorG, ColorB, R, G, B);
- // Propagate red component error
- Propagate(ErrorR0, ErrorR1, ErrorR2, ErrorR3, ColorR - R);
- // Propagate green component error
- Propagate(ErrorG0, ErrorG1, ErrorG2, ErrorG3, ColorG - G);
- // Propagate blue component error
- Propagate(ErrorB0, ErrorB1, ErrorB2, ErrorB3, ColorB - B);
- // Move on to next column
- if (Direction = 1) then
- begin
- inc(longInt(ErrorR0), sizeof(TErrorTerm));
- inc(longInt(ErrorG0), sizeof(TErrorTerm));
- inc(longInt(ErrorB0), sizeof(TErrorTerm));
- inc(longInt(ErrorR1), sizeof(TErrorTerm));
- inc(longInt(ErrorG1), sizeof(TErrorTerm));
- inc(longInt(ErrorB1), sizeof(TErrorTerm));
- inc(longInt(ErrorR2), sizeof(TErrorTerm));
- inc(longInt(ErrorG2), sizeof(TErrorTerm));
- inc(longInt(ErrorB2), sizeof(TErrorTerm));
- inc(longInt(ErrorR3), sizeof(TErrorTerm));
- inc(longInt(ErrorG3), sizeof(TErrorTerm));
- inc(longInt(ErrorB3), sizeof(TErrorTerm));
- end else
- begin
- dec(longInt(ErrorR0), sizeof(TErrorTerm));
- dec(longInt(ErrorG0), sizeof(TErrorTerm));
- dec(longInt(ErrorB0), sizeof(TErrorTerm));
- dec(longInt(ErrorR1), sizeof(TErrorTerm));
- dec(longInt(ErrorG1), sizeof(TErrorTerm));
- dec(longInt(ErrorB1), sizeof(TErrorTerm));
- dec(longInt(ErrorR2), sizeof(TErrorTerm));
- dec(longInt(ErrorG2), sizeof(TErrorTerm));
- dec(longInt(ErrorB2), sizeof(TErrorTerm));
- dec(longInt(ErrorR3), sizeof(TErrorTerm));
- dec(longInt(ErrorG3), sizeof(TErrorTerm));
- dec(longInt(ErrorB3), sizeof(TErrorTerm));
- end;
- end;
- {$IFDEF R_PLUS}
- {$RANGECHECKS ON}
- {$UNDEF R_PLUS}
- {$ENDIF}
- {$IFOPT R+}
- {$DEFINE R_PLUS}
- {$RANGECHECKS OFF}
- {$ENDIF}
- procedure TSteveArcheDitherer.NextLine;
- var
- TempErrors : PErrors;
- begin
- FillChar(ErrorsR0^, sizeof(TErrorTerm)*(Width+6), 0);
- FillChar(ErrorsG0^, sizeof(TErrorTerm)*(Width+6), 0);
- FillChar(ErrorsB0^, sizeof(TErrorTerm)*(Width+6), 0);
- // Swap lines
- TempErrors := ErrorsR0;
- ErrorsR0 := ErrorsR1;
- ErrorsR1 := ErrorsR2;
- ErrorsR2 := ErrorsR3;
- ErrorsR3 := TempErrors;
- TempErrors := ErrorsG0;
- ErrorsG0 := ErrorsG1;
- ErrorsG1 := ErrorsG2;
- ErrorsG2 := ErrorsG3;
- ErrorsG3 := TempErrors;
- TempErrors := ErrorsB0;
- ErrorsB0 := ErrorsB1;
- ErrorsB1 := ErrorsB2;
- ErrorsB2 := ErrorsB3;
- ErrorsB3 := TempErrors;
- inherited NextLine;
- FDirection2 := 2 * Direction;
- FDirection3 := 3 * Direction;
- if (Direction = 1) then
- begin
- // ErrorsR0[1] gives compiler error, so we
- // use PErrors(longInt(ErrorsR0)+sizeof(TErrorTerm)) instead...
- ErrorR0 := PErrors(longInt(ErrorsR0)+3*sizeof(TErrorTerm));
- ErrorG0 := PErrors(longInt(ErrorsG0)+3*sizeof(TErrorTerm));
- ErrorB0 := PErrors(longInt(ErrorsB0)+3*sizeof(TErrorTerm));
- ErrorR1 := PErrors(longInt(ErrorsR1)+3*sizeof(TErrorTerm));
- ErrorG1 := PErrors(longInt(ErrorsG1)+3*sizeof(TErrorTerm));
- ErrorB1 := PErrors(longInt(ErrorsB1)+3*sizeof(TErrorTerm));
- ErrorR2 := PErrors(longInt(ErrorsR2)+3*sizeof(TErrorTerm));
- ErrorG2 := PErrors(longInt(ErrorsG2)+3*sizeof(TErrorTerm));
- ErrorB2 := PErrors(longInt(ErrorsB2)+3*sizeof(TErrorTerm));
- ErrorR3 := PErrors(longInt(ErrorsR3)+3*sizeof(TErrorTerm));
- ErrorG3 := PErrors(longInt(ErrorsG3)+3*sizeof(TErrorTerm));
- ErrorB3 := PErrors(longInt(ErrorsB3)+3*sizeof(TErrorTerm));
- end else
- begin
- ErrorR0 := @ErrorsR0[Width+2];
- ErrorG0 := @ErrorsG0[Width+2];
- ErrorB0 := @ErrorsB0[Width+2];
- ErrorR1 := @ErrorsR1[Width+2];
- ErrorG1 := @ErrorsG1[Width+2];
- ErrorB1 := @ErrorsB1[Width+2];
- ErrorR2 := @ErrorsR2[Width+2];
- ErrorG2 := @ErrorsG2[Width+2];
- ErrorB2 := @ErrorsB2[Width+2];
- ErrorR3 := @ErrorsR2[Width+2];
- ErrorG3 := @ErrorsG2[Width+2];
- ErrorB3 := @ErrorsB2[Width+2];
- end;
- end;
- {$IFDEF R_PLUS}
- {$RANGECHECKS ON}
- {$UNDEF R_PLUS}
- {$ENDIF}
- ////////////////////////////////////////////////////////////////////////////////
- // TBurkesDitherer
- constructor TBurkesDitherer.Create(AWidth: integer; Lookup: TColorLookup);
- begin
- inherited Create(AWidth, Lookup);
- GetMem(ErrorsR0, sizeof(TErrorTerm)*(Width+4));
- GetMem(ErrorsG0, sizeof(TErrorTerm)*(Width+4));
- GetMem(ErrorsB0, sizeof(TErrorTerm)*(Width+4));
- GetMem(ErrorsR1, sizeof(TErrorTerm)*(Width+4));
- GetMem(ErrorsG1, sizeof(TErrorTerm)*(Width+4));
- GetMem(ErrorsB1, sizeof(TErrorTerm)*(Width+4));
- FillChar(ErrorsR0^, sizeof(TErrorTerm)*(Width+4), 0);
- FillChar(ErrorsG0^, sizeof(TErrorTerm)*(Width+4), 0);
- FillChar(ErrorsB0^, sizeof(TErrorTerm)*(Width+4), 0);
- FillChar(ErrorsR1^, sizeof(TErrorTerm)*(Width+4), 0);
- FillChar(ErrorsG1^, sizeof(TErrorTerm)*(Width+4), 0);
- FillChar(ErrorsB1^, sizeof(TErrorTerm)*(Width+4), 0);
- FDirection2 := 2 * Direction;
- ErrorR0 := PErrors(longInt(ErrorsR0)+2*sizeof(TErrorTerm));
- ErrorG0 := PErrors(longInt(ErrorsG0)+2*sizeof(TErrorTerm));
- ErrorB0 := PErrors(longInt(ErrorsB0)+2*sizeof(TErrorTerm));
- ErrorR1 := PErrors(longInt(ErrorsR1)+2*sizeof(TErrorTerm));
- ErrorG1 := PErrors(longInt(ErrorsG1)+2*sizeof(TErrorTerm));
- ErrorB1 := PErrors(longInt(ErrorsB1)+2*sizeof(TErrorTerm));
- end;
- destructor TBurkesDitherer.Destroy;
- begin
- FreeMem(ErrorsR0);
- FreeMem(ErrorsG0);
- FreeMem(ErrorsB0);
- FreeMem(ErrorsR1);
- FreeMem(ErrorsG1);
- FreeMem(ErrorsB1);
- inherited Destroy;
- end;
- {$IFOPT R+}
- {$DEFINE R_PLUS}
- {$RANGECHECKS OFF}
- {$ENDIF}
- function TBurkesDitherer.Dither(Red, Green, Blue: BYTE; var R, G, B: BYTE): char;
- var
- ErrorR ,
- ErrorG ,
- ErrorB : integer; // Error for current pixel
- // Propagate Burkes error terms:
- // ... ... (here) 8/32 4/32
- // 2/32 4/32 8/32 4/32 2/32
- procedure Propagate(Errors0, Errors1: PErrors; Error: integer);
- begin
- if (Error = 0) then
- exit;
- inc(Error, Error);
- inc(Errors1[FDirection2], Error); // Error * 2
- inc(Errors1[-FDirection2], Error); // Error * 2
- inc(Error, Error);
- inc(Errors0[FDirection2], Error); // Error * 4
- inc(Errors1[-Direction], Error); // Error * 4
- inc(Errors1[Direction], Error); // Error * 4
- inc(Error, Error);
- inc(Errors0[Direction], Error); // Error * 8
- inc(Errors1[0], Error); // Error * 8
- end;
- begin
- // Apply red component error correction
- ErrorR := Red + (ErrorR0[0] + 16) DIV 32;
- if (ErrorR < 0) then
- ErrorR := 0
- else if (ErrorR > 255) then
- ErrorR := 255;
- // Apply green component error correction
- ErrorG := Green + (ErrorG0[0] + 16) DIV 32;
- if (ErrorG < 0) then
- ErrorG := 0
- else if (ErrorG > 255) then
- ErrorG := 255;
- // Apply blue component error correction
- ErrorB := Blue + (ErrorB0[0] + 16) DIV 32;
- if (ErrorB < 0) then
- ErrorB := 0
- else if (ErrorB > 255) then
- ErrorB := 255;
- // Map color to palette
- Result := inherited Dither(ErrorR, ErrorG, ErrorB, R, G, B);
- // Propagate red component error
- Propagate(ErrorR0, ErrorR1, ErrorR - R);
- // Propagate green component error
- Propagate(ErrorG0, ErrorG1, ErrorG - G);
- // Propagate blue component error
- Propagate(ErrorB0, ErrorB1, ErrorB - B);
- // Move on to next column
- if (Direction = 1) then
- begin
- inc(longInt(ErrorR0), sizeof(TErrorTerm));
- inc(longInt(ErrorG0), sizeof(TErrorTerm));
- inc(longInt(ErrorB0), sizeof(TErrorTerm));
- inc(longInt(ErrorR1), sizeof(TErrorTerm));
- inc(longInt(ErrorG1), sizeof(TErrorTerm));
- inc(longInt(ErrorB1), sizeof(TErrorTerm));
- end else
- begin
- dec(longInt(ErrorR0), sizeof(TErrorTerm));
- dec(longInt(ErrorG0), sizeof(TErrorTerm));
- dec(longInt(ErrorB0), sizeof(TErrorTerm));
- dec(longInt(ErrorR1), sizeof(TErrorTerm));
- dec(longInt(ErrorG1), sizeof(TErrorTerm));
- dec(longInt(ErrorB1), sizeof(TErrorTerm));
- end;
- end;
- {$IFDEF R_PLUS}
- {$RANGECHECKS ON}
- {$UNDEF R_PLUS}
- {$ENDIF}
- {$IFOPT R+}
- {$DEFINE R_PLUS}
- {$RANGECHECKS OFF}
- {$ENDIF}
- procedure TBurkesDitherer.NextLine;
- var
- TempErrors : PErrors;
- begin
- FillChar(ErrorsR0^, sizeof(TErrorTerm)*(Width+4), 0);
- FillChar(ErrorsG0^, sizeof(TErrorTerm)*(Width+4), 0);
- FillChar(ErrorsB0^, sizeof(TErrorTerm)*(Width+4), 0);
- // Swap lines
- TempErrors := ErrorsR0;
- ErrorsR0 := ErrorsR1;
- ErrorsR1 := TempErrors;
- TempErrors := ErrorsG0;
- ErrorsG0 := ErrorsG1;
- ErrorsG1 := TempErrors;
- TempErrors := ErrorsB0;
- ErrorsB0 := ErrorsB1;
- ErrorsB1 := TempErrors;
- inherited NextLine;
- FDirection2 := 2 * Direction;
- if (Direction = 1) then
- begin
- // ErrorsR0[1] gives compiler error, so we
- // use PErrors(longInt(ErrorsR0)+sizeof(TErrorTerm)) instead...
- ErrorR0 := PErrors(longInt(ErrorsR0)+2*sizeof(TErrorTerm));
- ErrorG0 := PErrors(longInt(ErrorsG0)+2*sizeof(TErrorTerm));
- ErrorB0 := PErrors(longInt(ErrorsB0)+2*sizeof(TErrorTerm));
- ErrorR1 := PErrors(longInt(ErrorsR1)+2*sizeof(TErrorTerm));
- ErrorG1 := PErrors(longInt(ErrorsG1)+2*sizeof(TErrorTerm));
- ErrorB1 := PErrors(longInt(ErrorsB1)+2*sizeof(TErrorTerm));
- end else
- begin
- ErrorR0 := @ErrorsR0[Width+1];
- ErrorG0 := @ErrorsG0[Width+1];
- ErrorB0 := @ErrorsB0[Width+1];
- ErrorR1 := @ErrorsR1[Width+1];
- ErrorG1 := @ErrorsG1[Width+1];
- ErrorB1 := @ErrorsB1[Width+1];
- end;
- end;
- {$IFDEF R_PLUS}
- {$RANGECHECKS ON}
- {$UNDEF R_PLUS}
- {$ENDIF}
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Octree Color Quantization Engine
- //
- ////////////////////////////////////////////////////////////////////////////////
- // Adapted from Earl F. Glynn's ColorQuantizationLibrary, March 1998
- ////////////////////////////////////////////////////////////////////////////////
- type
- TOctreeNode = class; // Forward definition so TReducibleNodes can be declared
- TReducibleNodes = array[0..7] of TOctreeNode;
- TOctreeNode = Class(TObject)
- public
- IsLeaf : Boolean;
- PixelCount : integer;
- RedSum : integer;
- GreenSum : integer;
- BlueSum : integer;
- Next : TOctreeNode;
- Child : TReducibleNodes;
- constructor Create(Level: integer; ColorBits: integer; var LeafCount: integer;
- var ReducibleNodes: TReducibleNodes);
- destructor Destroy; override;
- end;
- TColorQuantizer = class(TObject)
- private
- FTree : TOctreeNode;
- FLeafCount : integer;
- FReducibleNodes : TReducibleNodes;
- FMaxColors : integer;
- FColorBits : integer;
- protected
- procedure AddColor(var Node: TOctreeNode; r, g, b: byte; ColorBits: integer;
- Level: integer; var LeafCount: integer; var ReducibleNodes: TReducibleNodes);
- procedure DeleteTree(var Node: TOctreeNode);
- procedure GetPaletteColors(const Node: TOctreeNode;
- var RGBQuadArray: TRGBQuadArray; var Index: integer);
- procedure ReduceTree(ColorBits: integer; var LeafCount: integer;
- var ReducibleNodes: TReducibleNodes);
- public
- constructor Create(MaxColors: integer; ColorBits: integer);
- destructor Destroy; override;
- procedure GetColorTable(var RGBQuadArray: TRGBQuadArray);
- function ProcessImage(const DIB: TDIBReader): boolean;
- property ColorCount: integer read FLeafCount;
- end;
- constructor TOctreeNode.Create(Level: integer; ColorBits: integer;
- var LeafCount: integer; var ReducibleNodes: TReducibleNodes);
- var
- i : integer;
- begin
- PixelCount := 0;
- RedSum := 0;
- GreenSum := 0;
- BlueSum := 0;
- for i := Low(Child) to High(Child) do
- Child[i] := nil;
- IsLeaf := (Level = ColorBits);
- if (IsLeaf) then
- begin
- Next := nil;
- inc(LeafCount);
- end else
- begin
- Next := ReducibleNodes[Level];
- ReducibleNodes[Level] := self;
- end;
- end;
- destructor TOctreeNode.Destroy;
- var
- i : integer;
- begin
- for i := High(Child) downto Low(Child) do
- Child[i].Free;
- end;
- constructor TColorQuantizer.Create(MaxColors: integer; ColorBits: integer);
- var
- i : integer;
- begin
- ASSERT(ColorBits <= 8, 'ColorBits must be 8 or less');
- FTree := nil;
- FLeafCount := 0;
- // Initialize all nodes even though only ColorBits+1 of them are needed
- for i := Low(FReducibleNodes) to High(FReducibleNodes) do
- FReducibleNodes[i] := nil;
- FMaxColors := MaxColors;
- FColorBits := ColorBits;
- end;
- destructor TColorQuantizer.Destroy;
- begin
- if (FTree <> nil) then
- DeleteTree(FTree);
- end;
- procedure TColorQuantizer.GetColorTable(var RGBQuadArray: TRGBQuadArray);
- var
- Index : integer;
- begin
- Index := 0;
- GetPaletteColors(FTree, RGBQuadArray, Index);
- end;
- // Handles passed to ProcessImage should refer to DIB sections, not DDBs.
- // In certain cases, specifically when it's called upon to process 1, 4, or
- // 8-bit per pixel images on systems with palettized display adapters,
- // ProcessImage can produce incorrect results if it's passed a handle to a
- // DDB.
- function TColorQuantizer.ProcessImage(const DIB: TDIBReader): boolean;
- var
- i ,
- j : integer;
- ScanLine : pointer;
- Pixel : PRGBTriple;
- begin
- Result := True;
- for j := 0 to DIB.Bitmap.Height-1 do
- begin
- Scanline := DIB.Scanline[j];
- Pixel := ScanLine;
- for i := 0 to DIB.Bitmap.Width-1 do
- begin
- with Pixel^ do
- AddColor(FTree, rgbtRed, rgbtGreen, rgbtBlue,
- FColorBits, 0, FLeafCount, FReducibleNodes);
- while FLeafCount > FMaxColors do
- ReduceTree(FColorbits, FLeafCount, FReducibleNodes);
- inc(Pixel);
- end;
- end;
- end;
- procedure TColorQuantizer.AddColor(var Node: TOctreeNode; r,g,b: byte;
- ColorBits: integer; Level: integer; var LeafCount: integer;
- var ReducibleNodes: TReducibleNodes);
- const
- Mask: array[0..7] of BYTE = ($80, $40, $20, $10, $08, $04, $02, $01);
- var
- Index : integer;
- Shift : integer;
- begin
- // If the node doesn't exist, create it.
- if (Node = nil) then
- Node := TOctreeNode.Create(Level, ColorBits, LeafCount, ReducibleNodes);
- if (Node.IsLeaf) then
- begin
- inc(Node.PixelCount);
- inc(Node.RedSum, r);
- inc(Node.GreenSum, g);
- inc(Node.BlueSum, b);
- end else
- begin
- // Recurse a level deeper if the node is not a leaf.
- Shift := 7 - Level;
- Index := (((r and mask[Level]) SHR Shift) SHL 2) or
- (((g and mask[Level]) SHR Shift) SHL 1) or
- ((b and mask[Level]) SHR Shift);
- AddColor(Node.Child[Index], r, g, b, ColorBits, Level+1, LeafCount, ReducibleNodes);
- end;
- end;
- procedure TColorQuantizer.DeleteTree(var Node: TOctreeNode);
- var
- i : integer;
- begin
- for i := High(TReducibleNodes) downto Low(TReducibleNodes) do
- if (Node.Child[i] <> nil) then
- DeleteTree(Node.Child[i]);
- Node.Free;
- Node := nil;
- end;
- procedure TColorQuantizer.GetPaletteColors(const Node: TOctreeNode;
- var RGBQuadArray: TRGBQuadArray; var Index: integer);
- var
- i : integer;
- begin
- if (Node.IsLeaf) then
- begin
- with RGBQuadArray[Index] do
- begin
- if (Node.PixelCount <> 0) then
- begin
- rgbRed := BYTE(Node.RedSum DIV Node.PixelCount);
- rgbGreen := BYTE(Node.GreenSum DIV Node.PixelCount);
- rgbBlue := BYTE(Node.BlueSum DIV Node.PixelCount);
- end else
- begin
- rgbRed := 0;
- rgbGreen := 0;
- rgbBlue := 0;
- end;
- rgbReserved := 0;
- end;
- inc(Index);
- end else
- begin
- for i := Low(Node.Child) to High(Node.Child) do
- if (Node.Child[i] <> nil) then
- GetPaletteColors(Node.Child[i], RGBQuadArray, Index);
- end;
- end;
- procedure TColorQuantizer.ReduceTree(ColorBits: integer; var LeafCount: integer;
- var ReducibleNodes: TReducibleNodes);
- var
- RedSum ,
- GreenSum ,
- BlueSum : integer;
- Children : integer;
- i : integer;
- Node : TOctreeNode;
- begin
- // Find the deepest level containing at least one reducible node
- i := Colorbits - 1;
- while (i > 0) and (ReducibleNodes[i] = nil) do
- dec(i);
- // Reduce the node most recently added to the list at level i.
- Node := ReducibleNodes[i];
- ReducibleNodes[i] := Node.Next;
- RedSum := 0;
- GreenSum := 0;
- BlueSum := 0;
- Children := 0;
- for i := Low(ReducibleNodes) to High(ReducibleNodes) do
- if (Node.Child[i] <> nil) then
- begin
- inc(RedSum, Node.Child[i].RedSum);
- inc(GreenSum, Node.Child[i].GreenSum);
- inc(BlueSum, Node.Child[i].BlueSum);
- inc(Node.PixelCount, Node.Child[i].PixelCount);
- Node.Child[i].Free;
- Node.Child[i] := nil;
- inc(Children);
- end;
- Node.IsLeaf := TRUE;
- Node.RedSum := RedSum;
- Node.GreenSum := GreenSum;
- Node.BlueSum := BlueSum;
- dec(LeafCount, Children-1);
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Octree Color Quantization Wrapper
- //
- ////////////////////////////////////////////////////////////////////////////////
- // Adapted from Earl F. Glynn's PaletteLibrary, March 1998
- ////////////////////////////////////////////////////////////////////////////////
- // Wrapper for internal use - uses TDIBReader for bitmap access
- function doCreateOptimizedPaletteFromSingleBitmap(const DIB: TDIBReader;
- Colors, ColorBits: integer; Windows: boolean): hPalette;
- var
- SystemPalette : HPalette;
- ColorQuantizer : TColorQuantizer;
- i : integer;
- LogicalPalette : TMaxLogPalette;
- RGBQuadArray : TRGBQuadArray;
- Offset : integer;
- begin
- LogicalPalette.palVersion := $0300;
- LogicalPalette.palNumEntries := Colors;
- // 2003.03.06 ->
- {reset palette to black}
- FillChar(LogicalPalette.palPalEntry, SizeOf(LogicalPalette.palPalEntry), 0);
- for i := 0 to 255 do
- LogicalPalette.palPalEntry[i].peFlags := PC_NOCOLLAPSE;
- // 2003.03.06 <-
- if (Windows) then
- begin
- // Get the windows 20 color system palette
- SystemPalette := GetStockObject(DEFAULT_PALETTE);
- GetPaletteEntries(SystemPalette, 0, 10, LogicalPalette.palPalEntry[0]);
- //GetPaletteEntries(SystemPalette, 10, 10, LogicalPalette.palPalEntry[245]); // wrong offset
- GetPaletteEntries(SystemPalette, 10, 10, LogicalPalette.palPalEntry[246]); // 2003.03.06
- Colors := 236;
- Offset := 10;
- LogicalPalette.palNumEntries := 256;
- { Test code
- // 2003.03.06 ->
- // Get the windows 20 color system palette
- SystemPalette := GetStockObject(DEFAULT_PALETTE);
- GetPaletteEntries(SystemPalette, 0, 10, LogicalPalette.palPalEntry[0]);
- GetPaletteEntries(SystemPalette, 10, 10, LogicalPalette.palPalEntry[10]);
- Colors := 236;
- Offset := 20;
- LogicalPalette.palNumEntries := 256;
- // 2003.03.06 <-
- }
- end else
- Offset := 0;
- // Normally for 24-bit images, use ColorBits of 5 or 6. For 8-bit images
- // use ColorBits = 8.
- ColorQuantizer := TColorQuantizer.Create(Colors, ColorBits);
- try
- ColorQuantizer.ProcessImage(DIB);
- ColorQuantizer.GetColorTable(RGBQuadArray);
- finally
- ColorQuantizer.Free;
- end;
- for i := 0 to Colors-1 do
- with LogicalPalette.palPalEntry[i+Offset] do
- begin
- peRed := RGBQuadArray[i].rgbRed;
- peGreen := RGBQuadArray[i].rgbGreen;
- peBlue := RGBQuadArray[i].rgbBlue;
- peFlags := RGBQuadArray[i].rgbReserved;
- end;
- Result := CreatePalette(pLogPalette(@LogicalPalette)^);
- end;
- function CreateOptimizedPaletteFromSingleBitmap(const Bitmap: TBitmap;
- Colors, ColorBits: integer; Windows: boolean): hPalette;
- var
- DIB : TDIBReader;
- begin
- DIB := TDIBReader.Create(Bitmap, pf24bit);
- try
- Result := doCreateOptimizedPaletteFromSingleBitmap(DIB, Colors, ColorBits, Windows);
- finally
- DIB.Free;
- end;
- end;
- function CreateOptimizedPaletteFromManyBitmaps(Bitmaps: TList; Colors, ColorBits: integer;
- Windows: boolean): hPalette;
- var
- SystemPalette : HPalette;
- ColorQuantizer : TColorQuantizer;
- i : integer;
- LogicalPalette : TMaxLogPalette;
- RGBQuadArray : TRGBQuadArray;
- Offset : integer;
- DIB : TDIBReader;
- begin
- if (Bitmaps = nil) or (Bitmaps.Count = 0) then
- Error(sInvalidBitmapList);
- LogicalPalette.palVersion := $0300;
- LogicalPalette.palNumEntries := Colors;
- // 2003.03.06 ->
- {reset palette to black}
- FillChar(LogicalPalette.palPalEntry, SizeOf(LogicalPalette.palPalEntry), 0);
- for i := 0 to 255 do
- LogicalPalette.palPalEntry[i].peFlags := PC_NOCOLLAPSE;
- // 2003.03.06 <-
- if (Windows) then
- begin
- // Get the windows 20 color system palette
- SystemPalette := GetStockObject(DEFAULT_PALETTE);
- GetPaletteEntries(SystemPalette, 0, 10, LogicalPalette.palPalEntry[0]);
- //GetPaletteEntries(SystemPalette, 10, 10, LogicalPalette.palPalEntry[245]); // wrong offset
- GetPaletteEntries(SystemPalette, 10, 10, LogicalPalette.palPalEntry[246]); // 2003.03.06
- Colors := 236;
- Offset := 10;
- LogicalPalette.palNumEntries := 256;
- { Test code
- // 2003.03.06 ->
- // Get the windows 20 color system palette
- SystemPalette := GetStockObject(DEFAULT_PALETTE);
- GetPaletteEntries(SystemPalette, 0, 10, LogicalPalette.palPalEntry[0]);
- GetPaletteEntries(SystemPalette, 10, 10, LogicalPalette.palPalEntry[10]);
- Colors := 236;
- Offset := 20;
- LogicalPalette.palNumEntries := 256;
- // 2003.03.06 <-
- }
- end else
- Offset := 0;
- // Normally for 24-bit images, use ColorBits of 5 or 6. For 8-bit images
- // use ColorBits = 8.
- ColorQuantizer := TColorQuantizer.Create(Colors, ColorBits);
- try
- for i := 0 to Bitmaps.Count-1 do
- begin
- DIB := TDIBReader.Create(TBitmap(Bitmaps[i]), pf24bit);
- try
- ColorQuantizer.ProcessImage(DIB);
- finally
- DIB.Free;
- end;
- end;
- ColorQuantizer.GetColorTable(RGBQuadArray);
- finally
- ColorQuantizer.Free;
- end;
- for i := 0 to Colors-1 do
- with LogicalPalette.palPalEntry[i+Offset] do
- begin
- peRed := RGBQuadArray[i].rgbRed;
- peGreen := RGBQuadArray[i].rgbGreen;
- peBlue := RGBQuadArray[i].rgbBlue;
- peFlags := RGBQuadArray[i].rgbReserved;
- end;
- Result := CreatePalette(pLogPalette(@LogicalPalette)^);
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Color reduction
- //
- ////////////////////////////////////////////////////////////////////////////////
- {$IFOPT R+}
- {$DEFINE R_PLUS}
- {$RANGECHECKS OFF}
- {$ENDIF}
- //: Reduces the color depth of a bitmap using color quantization and dithering.
- function ReduceColors(Bitmap: TBitmap; ColorReduction: TColorReduction;
- DitherMode: TDitherMode; ReductionBits: integer; CustomPalette: hPalette): TBitmap;
- var
- Palette : hPalette;
- ColorLookup : TColorLookup;
- Ditherer : TDitherEngine;
- Row : Integer;
- DIBResult : TDIBWriter;
- DIBSource : TDIBReader;
- SrcScanLine ,
- Src : PRGBTriple;
- DstScanLine ,
- Dst : PChar;
- BGR : TRGBTriple;
- {$ifdef DEBUG_DITHERPERFORMANCE}
- TimeStart ,
- TimeStop : DWORD;
- {$endif}
- function GrayScalePalette: hPalette;
- var
- i : integer;
- Pal : TMaxLogPalette;
- begin
- Pal.palVersion := $0300;
- Pal.palNumEntries := 256;
- for i := 0 to 255 do
- begin
- with (Pal.palPalEntry[i]) do
- begin
- peRed := i;
- peGreen := i;
- peBlue := i;
- peFlags := PC_NOCOLLAPSE;
- end;
- end;
- Result := CreatePalette(pLogPalette(@Pal)^);
- end;
- function MonochromePalette: hPalette;
- var
- i : integer;
- Pal : TMaxLogPalette;
- const
- Values : array[0..1] of byte
- = (0, 255);
- begin
- Pal.palVersion := $0300;
- Pal.palNumEntries := 2;
- for i := 0 to 1 do
- begin
- with (Pal.palPalEntry[i]) do
- begin
- peRed := Values[i];
- peGreen := Values[i];
- peBlue := Values[i];
- peFlags := PC_NOCOLLAPSE;
- end;
- end;
- Result := CreatePalette(pLogPalette(@Pal)^);
- end;
- function WindowsGrayScalePalette: hPalette;
- var
- i : integer;
- Pal : TMaxLogPalette;
- const
- Values : array[0..3] of byte
- = (0, 128, 192, 255);
- begin
- Pal.palVersion := $0300;
- Pal.palNumEntries := 4;
- for i := 0 to 3 do
- begin
- with (Pal.palPalEntry[i]) do
- begin
- peRed := Values[i];
- peGreen := Values[i];
- peBlue := Values[i];
- peFlags := PC_NOCOLLAPSE;
- end;
- end;
- Result := CreatePalette(pLogPalette(@Pal)^);
- end;
- function WindowsHalftonePalette: hPalette;
- var
- DC : HDC;
- begin
- DC := GDICheck(GetDC(0));
- try
- Result := CreateHalfTonePalette(DC);
- finally
- ReleaseDC(0, DC);
- end;
- end;
- begin
- {$ifdef DEBUG_DITHERPERFORMANCE}
- timeBeginPeriod(5);
- TimeStart := timeGetTime;
- {$endif}
- Result := TBitmap.Create;
- try
- if (ColorReduction = rmNone) then
- begin
- Result.Assign(Bitmap);
- {$ifndef VER9x}
- SetPixelFormat(Result, pf24bit);
- {$endif}
- exit;
- end;
- {$IFNDEF VER9x}
- if (Bitmap.Width*Bitmap.Height > BitmapAllocationThreshold) then
- SetPixelFormat(Result, pf1bit); // To reduce resource consumption of resize
- {$ENDIF}
- ColorLookup := nil;
- Ditherer := nil;
- DIBResult := nil;
- DIBSource := nil;
- Palette := 0;
- try // Protect above resources
- // Dithering and color mapper only supports 24 bit bitmaps,
- // so we have convert the source bitmap to the appropiate format.
- DIBSource := TDIBReader.Create(Bitmap, pf24bit);
- // Create a palette based on current options
- case (ColorReduction) of
- rmQuantize:
- Palette := doCreateOptimizedPaletteFromSingleBitmap(DIBSource, 1 SHL ReductionBits, 8, False);
- rmQuantizeWindows:
- Palette := CreateOptimizedPaletteFromSingleBitmap(Bitmap, 256, 8, True);
- rmNetscape:
- Palette := WebPalette;
- rmGrayScale:
- Palette := GrayScalePalette;
- rmMonochrome:
- Palette := MonochromePalette;
- rmWindowsGray:
- Palette := WindowsGrayScalePalette;
- rmWindows20:
- Palette := GetStockObject(DEFAULT_PALETTE);
- rmWindows256:
- Palette := WindowsHalftonePalette;
- rmPalette:
- Palette := CopyPalette(CustomPalette);
- else
- exit;
- end;
- { TODO -oanme -cImprovement : Gray scale conversion should be done prior to dithering/mapping. Otherwise corrected values will be converted multiple times. }
- // Create a color mapper based on current options
- case (ColorReduction) of
- // For some strange reason my fast and dirty color lookup
- // is more precise that Windows GetNearestPaletteIndex...
- // rmWindows20:
- // ColorLookup := TSlowColorLookup.Create(Palette);
- // rmWindowsGray:
- // ColorLookup := TGrayWindowsLookup.Create(Palette);
- rmQuantize:
- // ColorLookup := TFastColorLookup.Create(Palette);
- ColorLookup := TSlowColorLookup.Create(Palette); // 2003-03-06
- rmNetscape:
- ColorLookup := TNetscapeColorLookup.Create(Palette);
- rmGrayScale:
- ColorLookup := TGrayScaleLookup.Create(Palette);
- rmMonochrome:
- ColorLookup := TMonochromeLookup.Create(Palette);
- else
- // ColorLookup := TFastColorLookup.Create(Palette);
- ColorLookup := TSlowColorLookup.Create(Palette); // 2003-03-06
- end;
- // Nothing to do if palette doesn't contain any colors
- if (ColorLookup.Colors = 0) then
- exit;
- // Create a ditherer based on current options
- case (DitherMode) of
- dmNearest:
- Ditherer := TDitherEngine.Create(Bitmap.Width, ColorLookup);
- dmFloydSteinberg:
- Ditherer := TFloydSteinbergDitherer.Create(Bitmap.Width, ColorLookup);
- dmStucki:
- Ditherer := TStuckiDitherer.Create(Bitmap.Width, ColorLookup);
- dmSierra:
- Ditherer := TSierraDitherer.Create(Bitmap.Width, ColorLookup);
- dmJaJuNI:
- Ditherer := TJaJuNIDitherer.Create(Bitmap.Width, ColorLookup);
- dmSteveArche:
- Ditherer := TSteveArcheDitherer.Create(Bitmap.Width, ColorLookup);
- dmBurkes:
- Ditherer := TBurkesDitherer.Create(Bitmap.Width, ColorLookup);
- else
- exit;
- end;
- // The processed bitmap is returned in pf8bit format
- DIBResult := TDIBWriter.Create(Result, pf8bit, Bitmap.Width, Bitmap.Height,
- Palette);
- // Process the image
- Row := 0;
- while (Row < Bitmap.Height) do
- begin
- SrcScanline := DIBSource.ScanLine[Row];
- DstScanline := DIBResult.ScanLine[Row];
- Src := pointer(longInt(SrcScanLine) + Ditherer.Column*sizeof(TRGBTriple));
- Dst := pointer(longInt(DstScanLine) + Ditherer.Column);
- while (Ditherer.Column < Ditherer.Width) and (Ditherer.Column >= 0) do
- begin
- BGR := Src^;
- // Dither and map a single pixel
- Dst^ := Ditherer.Dither(BGR.rgbtRed, BGR.rgbtGreen, BGR.rgbtBlue,
- BGR.rgbtRed, BGR.rgbtGreen, BGR.rgbtBlue);
- inc(Src, Ditherer.Direction);
- inc(Dst, Ditherer.Direction);
- end;
- Inc(Row);
- Ditherer.NextLine;
- end;
- finally
- if (ColorLookup <> nil) then
- ColorLookup.Free;
- if (Ditherer <> nil) then
- Ditherer.Free;
- if (DIBResult <> nil) then
- DIBResult.Free;
- if (DIBSource <> nil) then
- DIBSource.Free;
- // Must delete palette after TDIBWriter since TDIBWriter uses palette
- if (Palette <> 0) then
- DeleteObject(Palette);
- end;
- except
- Result.Free;
- raise;
- end;
- {$ifdef DEBUG_DITHERPERFORMANCE}
- TimeStop := timeGetTime;
- ShowMessage(format('Dithered %d pixels in %d mS, Rate %d pixels/mS (%d pixels/S)',
- [Bitmap.Height*Bitmap.Width, TimeStop-TimeStart,
- MulDiv(Bitmap.Height, Bitmap.Width, TimeStop-TimeStart+1),
- MulDiv(Bitmap.Height, Bitmap.Width * 1000, TimeStop-TimeStart+1)]));
- timeEndPeriod(5);
- {$endif}
- end;
- {$IFDEF R_PLUS}
- {$RANGECHECKS ON}
- {$UNDEF R_PLUS}
- {$ENDIF}
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFColorMap
- //
- ////////////////////////////////////////////////////////////////////////////////
- const
- InitColorMapSize = 16;
- DeltaColorMapSize = 32;
- //: Creates an instance of a TGIFColorMap object.
- constructor TGIFColorMap.Create;
- begin
- inherited Create;
- FColorMap := nil;
- FCapacity := 0;
- FCount := 0;
- FOptimized := False;
- end;
- //: Destroys an instance of a TGIFColorMap object.
- destructor TGIFColorMap.Destroy;
- begin
- Clear;
- Changed;
- inherited Destroy;
- end;
- //: Empties the color map.
- procedure TGIFColorMap.Clear;
- begin
- if (FColorMap <> nil) then
- FreeMem(FColorMap);
- FColorMap := nil;
- FCapacity := 0;
- FCount := 0;
- FOptimized := False;
- end;
- //: Converts a Windows color value to a RGB value.
- class function TGIFColorMap.Color2RGB(Color: TColor): TGIFColor;
- begin
- Result.Blue := (Color shr 16) and $FF;
- Result.Green := (Color shr 8) and $FF;
- Result.Red := Color and $FF;
- end;
- //: Converts a RGB value to a Windows color value.
- class function TGIFColorMap.RGB2Color(Color: TGIFColor): TColor;
- begin
- Result := (Color.Blue SHL 16) OR (Color.Green SHL 8) OR Color.Red;
- end;
- //: Saves the color map to a stream.
- procedure TGIFColorMap.SaveToStream(Stream: TStream);
- var
- Dummies : integer;
- Dummy : TGIFColor;
- begin
- if (FCount = 0) then
- exit;
- Stream.WriteBuffer(FColorMap^, FCount*sizeof(TGIFColor));
- Dummies := (1 SHL BitsPerPixel)-FCount;
- Dummy.Red := 0;
- Dummy.Green := 0;
- Dummy.Blue := 0;
- while (Dummies > 0) do
- begin
- Stream.WriteBuffer(Dummy, sizeof(TGIFColor));
- dec(Dummies);
- end;
- end;
- //: Loads the color map from a stream.
- procedure TGIFColorMap.LoadFromStream(Stream: TStream; Count: integer);
- begin
- Clear;
- SetCapacity(Count);
- ReadCheck(Stream, FColorMap^, Count*sizeof(TGIFColor));
- FCount := Count;
- end;
- //: Returns the position of a color in the color map.
- function TGIFColorMap.IndexOf(Color: TColor): integer;
- var
- RGB : TGIFColor;
- begin
- RGB := Color2RGB(Color);
- if (FOptimized) then
- begin
- // Optimized palette has most frequently occuring entries first
- Result := 0;
- // Reverse search to (hopefully) check latest colors first
- while (Result < FCount) do
- with (FColorMap^[Result]) do
- begin
- if (RGB.Red = Red) and (RGB.Green = Green) and (RGB.Blue = Blue) then
- exit;
- Inc(Result);
- end;
- Result := -1;
- end else
- begin
- Result := FCount-1;
- // Reverse search to (hopefully) check latest colors first
- while (Result >= 0) do
- with (FColorMap^[Result]) do
- begin
- if (RGB.Red = Red) and (RGB.Green = Green) and (RGB.Blue = Blue) then
- exit;
- Dec(Result);
- end;
- end;
- end;
- procedure TGIFColorMap.SetCapacity(Size: integer);
- begin
- if (Size >= FCapacity) then
- begin
- if (Size <= InitColorMapSize) then
- FCapacity := InitColorMapSize
- else
- FCapacity := (Size + DeltaColorMapSize - 1) DIV DeltaColorMapSize * DeltaColorMapSize;
- if (FCapacity > GIFMaxColors) then
- FCapacity := GIFMaxColors;
- ReallocMem(FColorMap, FCapacity * sizeof(TGIFColor));
- end;
- end;
- //: Imports a Windows palette into the color map.
- procedure TGIFColorMap.ImportPalette(Palette: HPalette);
- type
- PalArray = array[byte] of TPaletteEntry;
- var
- Pal : PalArray;
- NewCount : integer;
- i : integer;
- begin
- Clear;
- NewCount := GetPaletteEntries(Palette, 0, 256, pal);
- if (NewCount = 0) then
- exit;
- SetCapacity(NewCount);
- for i := 0 to NewCount-1 do
- with FColorMap[i], Pal[i] do
- begin
- Red := peRed;
- Green := peGreen;
- Blue := peBlue;
- end;
- FCount := NewCount;
- Changed;
- end;
- //: Imports a color map structure into the color map.
- procedure TGIFColorMap.ImportColorMap(Map: TColorMap; Count: integer);
- begin
- Clear;
- if (Count = 0) then
- exit;
- SetCapacity(Count);
- FCount := Count;
- System.Move(Map, FColorMap^, FCount * sizeof(TGIFColor));
- Changed;
- end;
- //: Imports a Windows palette structure into the color map.
- procedure TGIFColorMap.ImportColorTable(Pal: pointer; Count: integer);
- var
- i : integer;
- begin
- Clear;
- if (Count = 0) then
- exit;
- SetCapacity(Count);
- for i := 0 to Count-1 do
- with FColorMap[i], PRGBQuadArray(Pal)[i] do
- begin
- Red := rgbRed;
- Green := rgbGreen;
- Blue := rgbBlue;
- end;
- FCount := Count;
- Changed;
- end;
- //: Imports the color table of a DIB into the color map.
- procedure TGIFColorMap.ImportDIBColors(Handle: HDC);
- var
- Pal : Pointer;
- NewCount : integer;
- begin
- Clear;
- GetMem(Pal, sizeof(TRGBQuad) * 256);
- try
- NewCount := GetDIBColorTable(Handle, 0, 256, Pal^);
- ImportColorTable(Pal, NewCount);
- finally
- FreeMem(Pal);
- end;
- Changed;
- end;
- //: Creates a Windows palette from the color map.
- function TGIFColorMap.ExportPalette: HPalette;
- var
- Pal : TMaxLogPalette;
- i : Integer;
- begin
- if (Count = 0) then
- begin
- Result := 0;
- exit;
- end;
- Pal.palVersion := $300;
- Pal.palNumEntries := Count;
- for i := 0 to Count-1 do
- with FColorMap[i], Pal.palPalEntry[i] do
- begin
- peRed := Red;
- peGreen := Green;
- peBlue := Blue;
- peFlags := PC_NOCOLLAPSE; { TODO -oanme -cImprovement : Verify that PC_NOCOLLAPSE is the correct value to use. }
- end;
- Result := CreatePalette(PLogPalette(@Pal)^);
- end;
- //: Adds a color to the color map.
- function TGIFColorMap.Add(Color: TColor): integer;
- begin
- if (FCount >= GIFMaxColors) then
- // Color map full
- Error(sTooManyColors);
- Result := FCount;
- if (Result >= FCapacity) then
- SetCapacity(FCount+1);
- FColorMap^[FCount] := Color2RGB(Color);
- inc(FCount);
- FOptimized := False;
- Changed;
- end;
- function TGIFColorMap.AddUnique(Color: TColor): integer;
- begin
- // Look up color before add (same as IndexOf)
- Result := IndexOf(Color);
- if (Result >= 0) then
- // Color already in map
- exit;
- Result := Add(Color);
- end;
- //: Removes a color from the color map.
- procedure TGIFColorMap.Delete(Index: integer);
- begin
- if (Index < 0) or (Index >= FCount) then
- // Color index out of range
- Error(sBadColorIndex);
- dec(FCount);
- if (Index < FCount) then
- System.Move(FColorMap^[Index + 1], FColorMap^[Index], (FCount - Index)* sizeof(TGIFColor));
- FOptimized := False;
- Changed;
- end;
- function TGIFColorMap.GetColor(Index: integer): TColor;
- begin
- if (Index < 0) or (Index >= FCount) then
- begin
- // Color index out of range
- Warning(gsWarning, sBadColorIndex);
- // Raise an exception if the color map is empty
- if (FCount = 0) then
- Error(sEmptyColorMap);
- // Default to color index 0
- Index := 0;
- end;
- Result := RGB2Color(FColorMap^[Index]);
- end;
- procedure TGIFColorMap.SetColor(Index: integer; Value: TColor);
- begin
- if (Index < 0) or (Index >= FCount) then
- // Color index out of range
- Error(sBadColorIndex);
- FColorMap^[Index] := Color2RGB(Value);
- Changed;
- end;
- function TGIFColorMap.DoOptimize: boolean;
- var
- Usage : TColormapHistogram;
- TempMap : array[0..255] of TGIFColor;
- ReverseMap : TColormapReverse;
- i : integer;
- LastFound : boolean;
- NewCount : integer;
- T : TUsageCount;
- Pivot : integer;
- procedure QuickSort(iLo, iHi: Integer);
- var
- Lo, Hi: Integer;
- begin
- repeat
- Lo := iLo;
- Hi := iHi;
- Pivot := Usage[(iLo + iHi) SHR 1].Count;
- repeat
- while (Usage[Lo].Count - Pivot > 0) do inc(Lo);
- while (Usage[Hi].Count - Pivot < 0) do dec(Hi);
- if (Lo <= Hi) then
- begin
- T := Usage[Lo];
- Usage[Lo] := Usage[Hi];
- Usage[Hi] := T;
- inc(Lo);
- dec(Hi);
- end;
- until (Lo > Hi);
- if (iLo < Hi) then
- QuickSort(iLo, Hi);
- iLo := Lo;
- until (Lo >= iHi);
- end;
- begin
- if (FCount <= 1) then
- begin
- Result := False;
- exit;
- end;
- FOptimized := True;
- Result := True;
- BuildHistogram(Usage);
- (*
- ** Sort according to usage count
- *)
- QuickSort(0, FCount-1);
- (*
- ** Test for table already sorted
- *)
- for i := 0 to FCount-1 do
- if (Usage[i].Index <> i) then
- break;
- if (i = FCount) then
- exit;
- (*
- ** Build old to new map
- *)
- for i := 0 to FCount-1 do
- ReverseMap[Usage[i].Index] := i;
- MapImages(ReverseMap);
- (*
- ** Reorder colormap
- *)
- LastFound := False;
- NewCount := FCount;
- Move(FColorMap^, TempMap, FCount * sizeof(TGIFColor));
- for i := 0 to FCount-1 do
- begin
- FColorMap^[ReverseMap[i]] := TempMap[i];
- // Find last used color index
- if (Usage[i].Count = 0) and not(LastFound) then
- begin
- LastFound := True;
- NewCount := i;
- end;
- end;
- FCount := NewCount;
- Changed;
- end;
- function TGIFColorMap.GetBitsPerPixel: integer;
- begin
- Result := Colors2bpp(FCount);
- end;
- //: Copies one color map to another.
- procedure TGIFColorMap.Assign(Source: TPersistent);
- begin
- if (Source is TGIFColorMap) then
- begin
- Clear;
- FCapacity := TGIFColorMap(Source).FCapacity;
- FCount := TGIFColorMap(Source).FCount;
- FOptimized := TGIFColorMap(Source).FOptimized;
- FColorMap := AllocMem(FCapacity * sizeof(TGIFColor));
- System.Move(TGIFColorMap(Source).FColorMap^, FColorMap^, FCount * sizeof(TGIFColor));
- Changed;
- end else
- inherited Assign(Source);
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFItem
- //
- ////////////////////////////////////////////////////////////////////////////////
- constructor TGIFItem.Create(GIFImage: TGIFImage);
- begin
- inherited Create;
- FGIFImage := GIFImage;
- end;
- procedure TGIFItem.Warning(Severity: TGIFSeverity; Message: string);
- begin
- FGIFImage.Warning(self, Severity, Message);
- end;
- function TGIFItem.GetVersion: TGIFVersion;
- begin
- Result := gv87a;
- end;
- procedure TGIFItem.LoadFromFile(const Filename: string);
- var
- Stream: TStream;
- begin
- Stream := TFileStream.Create(Filename, fmOpenRead OR fmShareDenyWrite);
- try
- LoadFromStream(Stream);
- finally
- Stream.Free;
- end;
- end;
- procedure TGIFItem.SaveToFile(const Filename: string);
- var
- Stream: TStream;
- begin
- Stream := TFileStream.Create(Filename, fmCreate);
- try
- SaveToStream(Stream);
- finally
- Stream.Free;
- end;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFList
- //
- ////////////////////////////////////////////////////////////////////////////////
- constructor TGIFList.Create(Image: TGIFImage);
- begin
- inherited Create;
- FImage := Image;
- FItems := TList.Create;
- end;
- destructor TGIFList.Destroy;
- begin
- Clear;
- FItems.Free;
- inherited Destroy;
- end;
- function TGIFList.GetItem(Index: Integer): TGIFItem;
- begin
- Result := TGIFItem(FItems[Index]);
- end;
- procedure TGIFList.SetItem(Index: Integer; Item: TGIFItem);
- begin
- FItems[Index] := Item;
- end;
- function TGIFList.GetCount: Integer;
- begin
- Result := FItems.Count;
- end;
- function TGIFList.Add(Item: TGIFItem): Integer;
- begin
- Result := FItems.Add(Item);
- end;
- procedure TGIFList.Clear;
- begin
- while (FItems.Count > 0) do
- Delete(0);
- end;
- procedure TGIFList.Delete(Index: Integer);
- var
- Item : TGIFItem;
- begin
- Item := TGIFItem(FItems[Index]);
- // Delete before item is destroyed to avoid recursion
- FItems.Delete(Index);
- Item.Free;
- end;
- procedure TGIFList.Exchange(Index1, Index2: Integer);
- begin
- FItems.Exchange(Index1, Index2);
- end;
- function TGIFList.First: TGIFItem;
- begin
- Result := TGIFItem(FItems.First);
- end;
- function TGIFList.IndexOf(Item: TGIFItem): Integer;
- begin
- Result := FItems.IndexOf(Item);
- end;
- procedure TGIFList.Insert(Index: Integer; Item: TGIFItem);
- begin
- FItems.Insert(Index, Item);
- end;
- function TGIFList.Last: TGIFItem;
- begin
- Result := TGIFItem(FItems.Last);
- end;
- procedure TGIFList.Move(CurIndex, NewIndex: Integer);
- begin
- FItems.Move(CurIndex, NewIndex);
- end;
- function TGIFList.Remove(Item: TGIFItem): Integer;
- begin
- // Note: TGIFList.Remove must not destroy item
- Result := FItems.Remove(Item);
- end;
- procedure TGIFList.SaveToStream(Stream: TStream);
- var
- i : integer;
- begin
- for i := 0 to FItems.Count-1 do
- TGIFItem(FItems[i]).SaveToStream(Stream);
- end;
- procedure TGIFList.Warning(Severity: TGIFSeverity; Message: string);
- begin
- Image.Warning(self, Severity, Message);
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFGlobalColorMap
- //
- ////////////////////////////////////////////////////////////////////////////////
- type
- TGIFGlobalColorMap = class(TGIFColorMap)
- private
- FHeader : TGIFHeader;
- protected
- procedure Warning(Severity: TGIFSeverity; Message: string); override;
- procedure BuildHistogram(var Histogram: TColormapHistogram); override;
- procedure MapImages(var Map: TColormapReverse); override;
- public
- constructor Create(HeaderItem: TGIFHeader);
- function Optimize: boolean; override;
- procedure Changed; override;
- end;
- constructor TGIFGlobalColorMap.Create(HeaderItem: TGIFHeader);
- begin
- Inherited Create;
- FHeader := HeaderItem;
- end;
- procedure TGIFGlobalColorMap.Warning(Severity: TGIFSeverity; Message: string);
- begin
- FHeader.Image.Warning(self, Severity, Message);
- end;
- procedure TGIFGlobalColorMap.BuildHistogram(var Histogram: TColormapHistogram);
- var
- Pixel ,
- LastPixel : PChar;
- i : integer;
- begin
- (*
- ** Init histogram
- *)
- for i := 0 to Count-1 do
- begin
- Histogram[i].Index := i;
- Histogram[i].Count := 0;
- end;
- for i := 0 to FHeader.Image.Images.Count-1 do
- if (FHeader.Image.Images[i].ActiveColorMap = self) then
- begin
- Pixel := FHeader.Image.Images[i].Data;
- LastPixel := Pixel + FHeader.Image.Images[i].Width * FHeader.Image.Images[i].Height;
- (*
- ** Sum up usage count for each color
- *)
- while (Pixel < LastPixel) do
- begin
- inc(Histogram[ord(Pixel^)].Count);
- inc(Pixel);
- end;
- end;
- end;
- procedure TGIFGlobalColorMap.MapImages(var Map: TColormapReverse);
- var
- Pixel ,
- LastPixel : PChar;
- i : integer;
- begin
- for i := 0 to FHeader.Image.Images.Count-1 do
- if (FHeader.Image.Images[i].ActiveColorMap = self) then
- begin
- Pixel := FHeader.Image.Images[i].Data;
- LastPixel := Pixel + FHeader.Image.Images[i].Width * FHeader.Image.Images[i].Height;
- (*
- ** Reorder all pixel to new map
- *)
- while (Pixel < LastPixel) do
- begin
- Pixel^ := chr(Map[ord(Pixel^)]);
- inc(Pixel);
- end;
- (*
- ** Reorder transparent colors
- *)
- if (FHeader.Image.Images[i].Transparent) then
- FHeader.Image.Images[i].GraphicControlExtension.TransparentColorIndex :=
- Map[FHeader.Image.Images[i].GraphicControlExtension.TransparentColorIndex];
- end;
- end;
- function TGIFGlobalColorMap.Optimize: boolean;
- begin
- { Optimize with first image, Remove unused colors if only one image }
- if (FHeader.Image.Images.Count > 0) then
- Result := DoOptimize
- else
- Result := False;
- end;
- procedure TGIFGlobalColorMap.Changed;
- begin
- FHeader.Image.Palette := 0;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFHeader
- //
- ////////////////////////////////////////////////////////////////////////////////
- constructor TGIFHeader.Create(GIFImage: TGIFImage);
- begin
- inherited Create(GIFImage);
- FColorMap := TGIFGlobalColorMap.Create(self);
- Clear;
- end;
- destructor TGIFHeader.Destroy;
- begin
- FColorMap.Free;
- inherited Destroy;
- end;
- procedure TGIFHeader.Clear;
- begin
- FColorMap.Clear;
- FLogicalScreenDescriptor.ScreenWidth := 0;
- FLogicalScreenDescriptor.ScreenHeight := 0;
- FLogicalScreenDescriptor.PackedFields := 0;
- FLogicalScreenDescriptor.BackgroundColorIndex := 0;
- FLogicalScreenDescriptor.AspectRatio := 0;
- end;
- procedure TGIFHeader.Assign(Source: TPersistent);
- begin
- if (Source is TGIFHeader) then
- begin
- ColorMap.Assign(TGIFHeader(Source).ColorMap);
- FLogicalScreenDescriptor := TGIFHeader(Source).FLogicalScreenDescriptor;
- end else
- if (Source is TGIFColorMap) then
- begin
- Clear;
- ColorMap.Assign(TGIFColorMap(Source));
- end else
- inherited Assign(Source);
- end;
- type
- TGIFHeaderRec = packed record
- Signature: array[0..2] of char; { contains 'GIF' }
- Version: TGIFVersionRec; { '87a' or '89a' }
- end;
- const
- { logical screen descriptor packed field masks }
- lsdGlobalColorTable = $80; { set if global color table follows L.S.D. }
- lsdColorResolution = $70; { Color resolution - 3 bits }
- lsdSort = $08; { set if global color table is sorted - 1 bit }
- lsdColorTableSize = $07; { size of global color table - 3 bits }
- { Actual size = 2^value+1 - value is 3 bits }
- procedure TGIFHeader.Prepare;
- var
- pack : BYTE;
- begin
- Pack := $00;
- if (ColorMap.Count > 0) then
- begin
- Pack := lsdGlobalColorTable;
- if (ColorMap.Optimized) then
- Pack := Pack OR lsdSort;
- end;
- // Note: The SHL below was SHL 5 in the original source, but that looks wrong
- Pack := Pack OR ((Image.ColorResolution SHL 4) AND lsdColorResolution);
- Pack := Pack OR ((Image.BitsPerPixel-1) AND lsdColorTableSize);
- FLogicalScreenDescriptor.PackedFields := Pack;
- end;
- procedure TGIFHeader.SaveToStream(Stream: TStream);
- var
- GifHeader : TGIFHeaderRec;
- v : TGIFVersion;
- begin
- v := Image.Version;
- if (v = gvUnknown) then
- Error(sBadVersion);
- GifHeader.Signature := 'GIF';
- GifHeader.Version := GIFVersions[v];
- Prepare;
- Stream.Write(GifHeader, sizeof(GifHeader));
- Stream.Write(FLogicalScreenDescriptor, sizeof(FLogicalScreenDescriptor));
- if (FLogicalScreenDescriptor.PackedFields AND lsdGlobalColorTable = lsdGlobalColorTable) then
- ColorMap.SaveToStream(Stream);
- end;
- procedure TGIFHeader.LoadFromStream(Stream: TStream);
- var
- GifHeader : TGIFHeaderRec;
- ColorCount : integer;
- Position : integer;
- begin
- Position := Stream.Position;
- ReadCheck(Stream, GifHeader, sizeof(GifHeader));
- if (uppercase(GifHeader.Signature) <> 'GIF') then
- begin
- // Attempt recovery in case we are reading a GIF stored in a form by rxLib
- Stream.Position := Position;
- // Seek past size stored in stream
- Stream.Seek(sizeof(longInt), soFromCurrent);
- // Attempt to read signature again
- ReadCheck(Stream, GifHeader, sizeof(GifHeader));
- if (uppercase(GifHeader.Signature) <> 'GIF') then
- Error(sBadSignature);
- end;
- ReadCheck(Stream, FLogicalScreenDescriptor, sizeof(FLogicalScreenDescriptor));
- if (FLogicalScreenDescriptor.PackedFields AND lsdGlobalColorTable = lsdGlobalColorTable) then
- begin
- ColorCount := 2 SHL (FLogicalScreenDescriptor.PackedFields AND lsdColorTableSize);
- if (ColorCount < 2) or (ColorCount > 256) then
- Error(sScreenBadColorSize);
- ColorMap.LoadFromStream(Stream, ColorCount)
- end else
- ColorMap.Clear;
- end;
- function TGIFHeader.GetVersion: TGIFVersion;
- begin
- if (FColorMap.Optimized) or (AspectRatio <> 0) then
- Result := gv89a
- else
- Result := inherited GetVersion;
- end;
- function TGIFHeader.GetBackgroundColor: TColor;
- begin
- Result := FColorMap[BackgroundColorIndex];
- end;
- procedure TGIFHeader.SetBackgroundColor(Color: TColor);
- begin
- BackgroundColorIndex := FColorMap.AddUnique(Color);
- end;
- procedure TGIFHeader.SetBackgroundColorIndex(Index: BYTE);
- begin
- if ((Index >= FColorMap.Count) and (FColorMap.Count > 0)) then
- begin
- Warning(gsWarning, sBadColorIndex);
- Index := 0;
- end;
- FLogicalScreenDescriptor.BackgroundColorIndex := Index;
- end;
- function TGIFHeader.GetBitsPerPixel: integer;
- begin
- Result := FColorMap.BitsPerPixel;
- end;
- function TGIFHeader.GetColorResolution: integer;
- begin
- Result := FColorMap.BitsPerPixel-1;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFLocalColorMap
- //
- ////////////////////////////////////////////////////////////////////////////////
- type
- TGIFLocalColorMap = class(TGIFColorMap)
- private
- FSubImage : TGIFSubImage;
- protected
- procedure Warning(Severity: TGIFSeverity; Message: string); override;
- procedure BuildHistogram(var Histogram: TColormapHistogram); override;
- procedure MapImages(var Map: TColormapReverse); override;
- public
- constructor Create(SubImage: TGIFSubImage);
- function Optimize: boolean; override;
- procedure Changed; override;
- end;
- constructor TGIFLocalColorMap.Create(SubImage: TGIFSubImage);
- begin
- Inherited Create;
- FSubImage := SubImage;
- end;
- procedure TGIFLocalColorMap.Warning(Severity: TGIFSeverity; Message: string);
- begin
- FSubImage.Image.Warning(self, Severity, Message);
- end;
- procedure TGIFLocalColorMap.BuildHistogram(var Histogram: TColormapHistogram);
- var
- Pixel ,
- LastPixel : PChar;
- i : integer;
- begin
- Pixel := FSubImage.Data;
- LastPixel := Pixel + FSubImage.Width * FSubImage.Height;
- (*
- ** Init histogram
- *)
- for i := 0 to Count-1 do
- begin
- Histogram[i].Index := i;
- Histogram[i].Count := 0;
- end;
- (*
- ** Sum up usage count for each color
- *)
- while (Pixel < LastPixel) do
- begin
- inc(Histogram[ord(Pixel^)].Count);
- inc(Pixel);
- end;
- end;
- procedure TGIFLocalColorMap.MapImages(var Map: TColormapReverse);
- var
- Pixel ,
- LastPixel : PChar;
- begin
- Pixel := FSubImage.Data;
- LastPixel := Pixel + FSubImage.Width * FSubImage.Height;
- (*
- ** Reorder all pixel to new map
- *)
- while (Pixel < LastPixel) do
- begin
- Pixel^ := chr(Map[ord(Pixel^)]);
- inc(Pixel);
- end;
- (*
- ** Reorder transparent colors
- *)
- if (FSubImage.Transparent) then
- FSubImage.GraphicControlExtension.TransparentColorIndex :=
- Map[FSubImage.GraphicControlExtension.TransparentColorIndex];
- end;
- function TGIFLocalColorMap.Optimize: boolean;
- begin
- Result := DoOptimize;
- end;
- procedure TGIFLocalColorMap.Changed;
- begin
- FSubImage.Palette := 0;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // LZW Decoder
- //
- ////////////////////////////////////////////////////////////////////////////////
- const
- GIFCodeBits = 12; // Max number of bits per GIF token code
- GIFCodeMax = (1 SHL GIFCodeBits)-1;// Max GIF token code
- // 12 bits = 4095
- StackSize = (2 SHL GIFCodeBits); // Size of decompression stack
- TableSize = (1 SHL GIFCodeBits); // Size of decompression table
- procedure TGIFSubImage.Decompress(Stream: TStream);
- var
- table0 : array[0..TableSize-1] of integer;
- table1 : array[0..TableSize-1] of integer;
- firstcode, oldcode : integer;
- buf : array[0..257] of BYTE;
- Dest : PChar;
- v ,
- xpos, ypos, pass : integer;
- stack : array[0..StackSize-1] of integer;
- Source : ^integer;
- BitsPerCode : integer; // number of CodeTableBits/code
- InitialBitsPerCode : BYTE;
- MaxCode : integer; // maximum code, given BitsPerCode
- MaxCodeSize : integer;
- ClearCode : integer; // Special code to signal "Clear table"
- EOFCode : integer; // Special code to signal EOF
- step : integer;
- i : integer;
- StartBit , // Index of bit buffer start
- LastBit , // Index of last bit in buffer
- LastByte : integer; // Index of last byte in buffer
- get_done ,
- return_clear ,
- ZeroBlock : boolean;
- ClearValue : BYTE;
- {$ifdef DEBUG_DECOMPRESSPERFORMANCE}
- TimeStartDecompress ,
- TimeStopDecompress : DWORD;
- {$endif}
- function nextCode(BitsPerCode: integer): integer;
- const
- masks: array[0..15] of integer =
- ($0000, $0001, $0003, $0007,
- $000f, $001f, $003f, $007f,
- $00ff, $01ff, $03ff, $07ff,
- $0fff, $1fff, $3fff, $7fff);
- var
- StartIndex, EndIndex : integer;
- ret : integer;
- EndBit : integer;
- count : BYTE;
- begin
- if (return_clear) then
- begin
- return_clear := False;
- Result := ClearCode;
- exit;
- end;
- EndBit := StartBit + BitsPerCode;
- if (EndBit >= LastBit) then
- begin
- if (get_done) then
- begin
- if (StartBit >= LastBit) then
- Warning(gsWarning, sDecodeTooFewBits);
- Result := -1;
- exit;
- end;
- buf[0] := buf[LastByte-2];
- buf[1] := buf[LastByte-1];
- if (Stream.Read(count, 1) <> 1) then
- begin
- Result := -1;
- exit;
- end;
- if (count = 0) then
- begin
- ZeroBlock := True;
- get_done := TRUE;
- end else
- begin
- // Handle premature end of file
- if (Stream.Size - Stream.Position < Count) then
- begin
- Warning(gsWarning, sOutOfData);
- // Not enough data left - Just read as much as we can get
- Count := Stream.Size - Stream.Position;
- end;
- if (Count <> 0) then
- ReadCheck(Stream, Buf[2], Count);
- end;
- LastByte := 2 + count;
- StartBit := (StartBit - LastBit) + 16;
- LastBit := LastByte * 8;
- EndBit := StartBit + BitsPerCode;
- end;
- EndIndex := EndBit DIV 8;
- StartIndex := StartBit DIV 8;
- ASSERT(StartIndex <= high(buf), 'StartIndex too large');
- if (StartIndex = EndIndex) then
- ret := buf[StartIndex]
- else
- if (StartIndex + 1 = EndIndex) then
- ret := buf[StartIndex] OR (buf[StartIndex+1] SHL 8)
- else
- ret := buf[StartIndex] OR (buf[StartIndex+1] SHL 8) OR (buf[StartIndex+2] SHL 16);
- ret := (ret SHR (StartBit AND $0007)) AND masks[BitsPerCode];
- Inc(StartBit, BitsPerCode);
- Result := ret;
- end;
- function NextLZW: integer;
- var
- code, incode : integer;
- i : integer;
- b : BYTE;
- begin
- code := nextCode(BitsPerCode);
- while (code >= 0) do
- begin
- if (code = ClearCode) then
- begin
- ASSERT(ClearCode < TableSize, 'ClearCode too large');
- for i := 0 to ClearCode-1 do
- begin
- table0[i] := 0;
- table1[i] := i;
- end;
- for i := ClearCode to TableSize-1 do
- begin
- table0[i] := 0;
- table1[i] := 0;
- end;
- BitsPerCode := InitialBitsPerCode+1;
- MaxCodeSize := 2 * ClearCode;
- MaxCode := ClearCode + 2;
- Source := @stack;
- repeat
- firstcode := nextCode(BitsPerCode);
- oldcode := firstcode;
- until (firstcode <> ClearCode);
- Result := firstcode;
- exit;
- end;
- if (code = EOFCode) then
- begin
- Result := -2;
- if (ZeroBlock) then
- exit;
- // Eat rest of data blocks
- if (Stream.Read(b, 1) <> 1) then
- exit;
- while (b <> 0) do
- begin
- Stream.Seek(b, soFromCurrent);
- if (Stream.Read(b, 1) <> 1) then
- exit;
- end;
- exit;
- end;
- incode := code;
- if (code >= MaxCode) then
- begin
- Source^ := firstcode;
- Inc(Source);
- code := oldcode;
- end;
- ASSERT(Code < TableSize, 'Code too large');
- while (code >= ClearCode) do
- begin
- Source^ := table1[code];
- Inc(Source);
- if (code = table0[code]) then
- Error(sDecodeCircular);
- code := table0[code];
- ASSERT(Code < TableSize, 'Code too large');
- end;
- firstcode := table1[code];
- Source^ := firstcode;
- Inc(Source);
- code := MaxCode;
- if (code <= GIFCodeMax) then
- begin
- table0[code] := oldcode;
- table1[code] := firstcode;
- Inc(MaxCode);
- if ((MaxCode >= MaxCodeSize) and (MaxCodeSize <= GIFCodeMax)) then
- begin
- MaxCodeSize := MaxCodeSize * 2;
- Inc(BitsPerCode);
- end;
- end;
- oldcode := incode;
- if (longInt(Source) > longInt(@stack)) then
- begin
- Dec(Source);
- Result := Source^;
- exit;
- end
- end;
- Result := code;
- end;
- function readLZW: integer;
- begin
- if (longInt(Source) > longInt(@stack)) then
- begin
- Dec(Source);
- Result := Source^;
- end else
- Result := NextLZW;
- end;
- begin
- NewImage;
- // Clear image data in case decompress doesn't complete
- if (Transparent) then
- // Clear to transparent color
- ClearValue := GraphicControlExtension.GetTransparentColorIndex
- else
- // Clear to first color
- ClearValue := 0;
- FillChar(FData^, FDataSize, ClearValue);
- {$ifdef DEBUG_DECOMPRESSPERFORMANCE}
- TimeStartDecompress := timeGetTime;
- {$endif}
- (*
- ** Read initial code size in bits from stream
- *)
- if (Stream.Read(InitialBitsPerCode, 1) <> 1) then
- exit;
- (*
- ** Initialize the Compression routines
- *)
- BitsPerCode := InitialBitsPerCode + 1;
- ClearCode := 1 SHL InitialBitsPerCode;
- EOFCode := ClearCode + 1;
- MaxCodeSize := 2 * ClearCode;
- MaxCode := ClearCode + 2;
- StartBit := 0;
- LastBit := 0;
- LastByte := 2;
- ZeroBlock := False;
- get_done := False;
- return_clear := TRUE;
- Source := @stack;
- try
- if (Interlaced) then
- begin
- ypos := 0;
- pass := 0;
- step := 8;
- for i := 0 to Height-1 do
- begin
- Dest := FData + Width * ypos;
- for xpos := 0 to width-1 do
- begin
- v := readLZW;
- if (v < 0) then
- exit;
- Dest^ := char(v);
- Inc(Dest);
- end;
- Inc(ypos, step);
- if (ypos >= height) then
- repeat
- if (pass > 0) then
- step := step DIV 2;
- Inc(pass);
- ypos := step DIV 2;
- until (ypos < height);
- end;
- end else
- begin
- Dest := FData;
- for ypos := 0 to (height * width)-1 do
- begin
- v := readLZW;
- if (v < 0) then
- exit;
- Dest^ := char(v);
- Inc(Dest);
- end;
- end;
- finally
- if (readLZW >= 0) then
- ;
- // raise GIFException.Create('Too much input data, ignoring extra...');
- end;
- {$ifdef DEBUG_DECOMPRESSPERFORMANCE}
- TimeStopDecompress := timeGetTime;
- ShowMessage(format('Decompressed %d pixels in %d mS, Rate %d pixels/mS',
- [Height*Width, TimeStopDecompress-TimeStartDecompress,
- (Height*Width) DIV (TimeStopDecompress-TimeStartDecompress+1)]));
- {$endif}
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // LZW Encoder stuff
- //
- ////////////////////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////////////////////
- // LZW Encoder THashTable
- ////////////////////////////////////////////////////////////////////////////////
- const
- HashKeyBits = 13; // Max number of bits per Hash Key
- HashSize = 8009; // Size of hash table
- // Must be prime
- // Must be > than HashMaxCode
- // Must be < than HashMaxKey
- HashKeyMax = (1 SHL HashKeyBits)-1;// Max hash key value
- // 13 bits = 8191
- HashKeyMask = HashKeyMax; // $1FFF
- GIFCodeMask = GIFCodeMax; // $0FFF
- HashEmpty = $000FFFFF; // 20 bits
- type
- // A Hash Key is 20 bits wide.
- // - The lower 8 bits are the postfix character (the new pixel).
- // - The upper 12 bits are the prefix code (the GIF token).
- // A KeyInt must be able to represent the integer values -1..(2^20)-1
- KeyInt = longInt; // 32 bits
- CodeInt = SmallInt; // 16 bits
- THashArray = array[0..HashSize-1] of KeyInt;
- PHashArray = ^THashArray;
- THashTable = class
- {$ifdef DEBUG_HASHPERFORMANCE}
- CountLookupFound : longInt;
- CountMissFound : longInt;
- CountLookupNotFound : longInt;
- CountMissNotFound : longInt;
- {$endif}
- HashTable: PHashArray;
- public
- constructor Create;
- destructor Destroy; override;
- procedure Clear;
- procedure Insert(Key: KeyInt; Code: CodeInt);
- function Lookup(Key: KeyInt): CodeInt;
- end;
- function HashKey(Key: KeyInt): CodeInt;
- begin
- Result := ((Key SHR (GIFCodeBits-8)) XOR Key) MOD HashSize;
- end;
- function NextHashKey(HKey: CodeInt): CodeInt;
- var
- disp : CodeInt;
- begin
- (*
- ** secondary hash (after G. Knott)
- *)
- disp := HashSize - HKey;
- if (HKey = 0) then
- disp := 1;
- // disp := 13; // disp should be prime relative to HashSize, but
- // it doesn't seem to matter here...
- dec(HKey, disp);
- if (HKey < 0) then
- inc(HKey, HashSize);
- Result := HKey;
- end;
- constructor THashTable.Create;
- begin
- ASSERT(longInt($FFFFFFFF) = -1, 'TGIFImage implementation assumes $FFFFFFFF = -1');
- inherited Create;
- GetMem(HashTable, sizeof(THashArray));
- Clear;
- {$ifdef DEBUG_HASHPERFORMANCE}
- CountLookupFound := 0;
- CountMissFound := 0;
- CountLookupNotFound := 0;
- CountMissNotFound := 0;
- {$endif}
- end;
- destructor THashTable.Destroy;
- begin
- {$ifdef DEBUG_HASHPERFORMANCE}
- ShowMessage(
- Format('Found: %d HitRate: %.2f',
- [CountLookupFound, (CountLookupFound+1)/(CountMissFound+1)])+#13+
- Format('Not found: %d HitRate: %.2f',
- [CountLookupNotFound, (CountLookupNotFound+1)/(CountMissNotFound+1)]));
- {$endif}
- FreeMem(HashTable);
- inherited Destroy;
- end;
- // Clear hash table and fill with empty slots (doh!)
- procedure THashTable.Clear;
- {$ifdef DEBUG_HASHFILLFACTOR}
- var
- i ,
- Count : longInt;
- {$endif}
- begin
- {$ifdef DEBUG_HASHFILLFACTOR}
- Count := 0;
- for i := 0 to HashSize-1 do
- if (HashTable[i] SHR GIFCodeBits <> HashEmpty) then
- inc(Count);
- ShowMessage(format('Size: %d, Filled: %d, Rate %.4f',
- [HashSize, Count, Count/HashSize]));
- {$endif}
- FillChar(HashTable^, sizeof(THashArray), $FF);
- end;
- // Insert new key/value pair into hash table
- procedure THashTable.Insert(Key: KeyInt; Code: CodeInt);
- var
- HKey : CodeInt;
- begin
- // Create hash key from prefix string
- HKey := HashKey(Key);
- // Scan for empty slot
- // while (HashTable[HKey] SHR GIFCodeBits <> HashEmpty) do { Unoptimized }
- while (HashTable[HKey] AND (HashEmpty SHL GIFCodeBits) <> (HashEmpty SHL GIFCodeBits)) do { Optimized }
- HKey := NextHashKey(HKey);
- // Fill slot with key/value pair
- HashTable[HKey] := (Key SHL GIFCodeBits) OR (Code AND GIFCodeMask);
- end;
- // Search for key in hash table.
- // Returns value if found or -1 if not
- function THashTable.Lookup(Key: KeyInt): CodeInt;
- var
- HKey : CodeInt;
- HTKey : KeyInt;
- {$ifdef DEBUG_HASHPERFORMANCE}
- n : LongInt;
- {$endif}
- begin
- // Create hash key from prefix string
- HKey := HashKey(Key);
- {$ifdef DEBUG_HASHPERFORMANCE}
- n := 0;
- {$endif}
- // Scan table for key
- // HTKey := HashTable[HKey] SHR GIFCodeBits; { Unoptimized }
- Key := Key SHL GIFCodeBits; { Optimized }
- HTKey := HashTable[HKey] AND (HashEmpty SHL GIFCodeBits); { Optimized }
- // while (HTKey <> HashEmpty) do { Unoptimized }
- while (HTKey <> HashEmpty SHL GIFCodeBits) do { Optimized }
- begin
- if (Key = HTKey) then
- begin
- // Extract and return value
- Result := HashTable[HKey] AND GIFCodeMask;
- {$ifdef DEBUG_HASHPERFORMANCE}
- inc(CountLookupFound);
- inc(CountMissFound, n);
- {$endif}
- exit;
- end;
- {$ifdef DEBUG_HASHPERFORMANCE}
- inc(n);
- {$endif}
- // Try next slot
- HKey := NextHashKey(HKey);
- // HTKey := HashTable[HKey] SHR GIFCodeBits; { Unoptimized }
- HTKey := HashTable[HKey] AND (HashEmpty SHL GIFCodeBits); { Optimized }
- end;
- // Found empty slot - key doesn't exist
- Result := -1;
- {$ifdef DEBUG_HASHPERFORMANCE}
- inc(CountLookupNotFound);
- inc(CountMissNotFound, n);
- {$endif}
- end;
- ////////////////////////////////////////////////////////////////////////////////
- // TGIFStream - Abstract GIF block stream
- //
- // Descendants from TGIFStream either reads or writes data in blocks
- // of up to 255 bytes. These blocks are organized as a leading byte
- // containing the number of bytes in the block (exclusing the count
- // byte itself), followed by the data (up to 254 bytes of data).
- ////////////////////////////////////////////////////////////////////////////////
- type
- TGIFStream = class(TStream)
- private
- FOnWarning : TGIFWarning;
- FStream : TStream;
- FOnProgress : TNotifyEvent;
- FBuffer : array [BYTE] of Char;
- FBufferCount : integer;
- protected
- constructor Create(Stream: TStream);
- function Read(var Buffer; Count: Longint): Longint; override;
- function Write(const Buffer; Count: Longint): Longint; override;
- function Seek(Offset: Longint; Origin: Word): Longint; override;
- procedure Progress(Sender: TObject); dynamic;
- property OnProgress: TNotifyEvent read FOnProgress write FOnProgress;
- public
- property Warning: TGIFWarning read FOnWarning write FOnWarning;
- end;
- constructor TGIFStream.Create(Stream: TStream);
- begin
- inherited Create;
- FStream := Stream;
- FBufferCount := 1; // Reserve first byte of buffer for length
- end;
- procedure TGIFStream.Progress(Sender: TObject);
- begin
- if Assigned(FOnProgress) then
- FOnProgress(Sender);
- end;
- function TGIFStream.Write(const Buffer; Count: Longint): Longint;
- begin
- raise Exception.Create(sInvalidStream);
- end;
- function TGIFStream.Read(var Buffer; Count: Longint): Longint;
- begin
- raise Exception.Create(sInvalidStream);
- end;
- function TGIFStream.Seek(Offset: Longint; Origin: Word): Longint;
- begin
- raise Exception.Create(sInvalidStream);
- end;
- ////////////////////////////////////////////////////////////////////////////////
- // TGIFReader - GIF block reader
- ////////////////////////////////////////////////////////////////////////////////
- type
- TGIFReader = class(TGIFStream)
- public
- constructor Create(Stream: TStream);
- function Read(var Buffer; Count: Longint): Longint; override;
- end;
- constructor TGIFReader.Create(Stream: TStream);
- begin
- inherited Create(Stream);
- FBufferCount := 0;
- end;
- function TGIFReader.Read(var Buffer; Count: Longint): Longint;
- var
- n : integer;
- Dst : PChar;
- size : BYTE;
- begin
- Dst := @Buffer;
- Result := 0;
- while (Count > 0) do
- begin
- // Get data from buffer
- while (FBufferCount > 0) and (Count > 0) do
- begin
- if (FBufferCount > Count) then
- n := Count
- else
- n := FBufferCount;
- Move(FBuffer, Dst^, n);
- dec(FBufferCount, n);
- dec(Count, n);
- inc(Result, n);
- inc(Dst, n);
- end;
- // Refill buffer when it becomes empty
- if (FBufferCount <= 0) then
- begin
- FStream.Read(size, 1);
- { TODO -oanme -cImprovement : Should be handled as a warning instead of an error. }
- if (size >= 255) then
- Error('GIF block too large');
- FBufferCount := size;
- if (FBufferCount > 0) then
- begin
- n := FStream.Read(FBuffer, size);
- if (n = FBufferCount) then
- begin
- Warning(self, gsWarning, sOutOfData);
- break;
- end;
- end else
- break;
- end;
- end;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- // TGIFWriter - GIF block writer
- ////////////////////////////////////////////////////////////////////////////////
- type
- TGIFWriter = class(TGIFStream)
- private
- FOutputDirty : boolean;
- protected
- procedure FlushBuffer;
- public
- constructor Create(Stream: TStream);
- destructor Destroy; override;
- function Write(const Buffer; Count: Longint): Longint; override;
- function WriteByte(Value: BYTE): Longint;
- end;
- constructor TGIFWriter.Create(Stream: TStream);
- begin
- inherited Create(Stream);
- FBufferCount := 1; // Reserve first byte of buffer for length
- FOutputDirty := False;
- end;
- destructor TGIFWriter.Destroy;
- begin
- inherited Destroy;
- if (FOutputDirty) then
- FlushBuffer;
- end;
- procedure TGIFWriter.FlushBuffer;
- begin
- if (FBufferCount <= 0) then
- exit;
- FBuffer[0] := char(FBufferCount-1); // Block size excluding the count
- FStream.WriteBuffer(FBuffer, FBufferCount);
- FBufferCount := 1; // Reserve first byte of buffer for length
- FOutputDirty := False;
- end;
- function TGIFWriter.Write(const Buffer; Count: Longint): Longint;
- var
- n : integer;
- Src : PChar;
- begin
- Result := Count;
- FOutputDirty := True;
- Src := @Buffer;
- while (Count > 0) do
- begin
- // Move data to the internal buffer in 255 byte chunks
- while (FBufferCount < sizeof(FBuffer)) and (Count > 0) do
- begin
- n := sizeof(FBuffer) - FBufferCount;
- if (n > Count) then
- n := Count;
- Move(Src^, FBuffer[FBufferCount], n);
- inc(Src, n);
- inc(FBufferCount, n);
- dec(Count, n);
- end;
- // Flush the buffer when it is full
- if (FBufferCount >= sizeof(FBuffer)) then
- FlushBuffer;
- end;
- end;
- function TGIFWriter.WriteByte(Value: BYTE): Longint;
- begin
- Result := Write(Value, 1);
- end;
- ////////////////////////////////////////////////////////////////////////////////
- // TGIFEncoder - Abstract encoder
- ////////////////////////////////////////////////////////////////////////////////
- type
- TGIFEncoder = class(TObject)
- protected
- FOnWarning : TGIFWarning;
- MaxColor : integer;
- BitsPerPixel : BYTE; // Bits per pixel of image
- Stream : TStream; // Output stream
- Width , // Width of image in pixels
- Height : integer; // height of image in pixels
- Interlace : boolean; // Interlace flag (True = interlaced image)
- Data : PChar; // Pointer to pixel data
- GIFStream : TGIFWriter; // Output buffer
- OutputBucket : longInt; // Output bit bucket
- OutputBits : integer; // Current # of bits in bucket
- ClearFlag : Boolean; // True if dictionary has just been cleared
- BitsPerCode , // Current # of bits per code
- InitialBitsPerCode : integer; // Initial # of bits per code after
- // dictionary has been cleared
- MaxCode : CodeInt; // maximum code, given BitsPerCode
- ClearCode : CodeInt; // Special output code to signal "Clear table"
- EOFCode : CodeInt; // Special output code to signal EOF
- BaseCode : CodeInt; // ...
- Pixel : PChar; // Pointer to current pixel
- cX , // Current X counter (Width - X)
- Y : integer; // Current Y
- Pass : integer; // Interlace pass
- function MaxCodesFromBits(Bits: integer): CodeInt;
- procedure Output(Value: integer); virtual;
- procedure Clear; virtual;
- function BumpPixel: boolean;
- procedure DoCompress; virtual; abstract;
- public
- procedure Compress(AStream: TStream; ABitsPerPixel: integer;
- AWidth, AHeight: integer; AInterlace: boolean; AData: PChar; AMaxColor: integer);
- property Warning: TGIFWarning read FOnWarning write FOnWarning;
- end;
- // Calculate the maximum number of codes that a given number of bits can represent
- // MaxCodes := (1^bits)-1
- function TGIFEncoder.MaxCodesFromBits(Bits: integer): CodeInt;
- begin
- Result := (CodeInt(1) SHL Bits) - 1;
- end;
- // Stuff bits (variable sized codes) into a buffer and output them
- // a byte at a time
- procedure TGIFEncoder.Output(Value: integer);
- const
- BitBucketMask: array[0..16] of longInt =
- ($0000,
- $0001, $0003, $0007, $000F,
- $001F, $003F, $007F, $00FF,
- $01FF, $03FF, $07FF, $0FFF,
- $1FFF, $3FFF, $7FFF, $FFFF);
- begin
- if (OutputBits > 0) then
- OutputBucket :=
- (OutputBucket AND BitBucketMask[OutputBits]) OR (longInt(Value) SHL OutputBits)
- else
- OutputBucket := Value;
- inc(OutputBits, BitsPerCode);
- while (OutputBits >= 8) do
- begin
- GIFStream.WriteByte(OutputBucket AND $FF);
- OutputBucket := OutputBucket SHR 8;
- dec(OutputBits, 8);
- end;
- if (Value = EOFCode) then
- begin
- // At EOF, write the rest of the buffer.
- while (OutputBits > 0) do
- begin
- GIFStream.WriteByte(OutputBucket AND $FF);
- OutputBucket := OutputBucket SHR 8;
- dec(OutputBits, 8);
- end;
- end;
- end;
- procedure TGIFEncoder.Clear;
- begin
- // just_cleared = 1;
- ClearFlag := TRUE;
- Output(ClearCode);
- end;
- // Bump (X,Y) and data pointer to point to the next pixel
- function TGIFEncoder.BumpPixel: boolean;
- begin
- // Bump the current X position
- dec(cX);
- // If we are at the end of a scan line, set cX back to the beginning
- // If we are interlaced, bump Y to the appropriate spot, otherwise,
- // just increment it.
- if (cX <= 0) then
- begin
- if not(Interlace) then
- begin
- // Done - no more data
- Result := False;
- exit;
- end;
- cX := Width;
- case (Pass) of
- 0:
- begin
- inc(Y, 8);
- if (Y >= Height) then
- begin
- inc(Pass);
- Y := 4;
- end;
- end;
- 1:
- begin
- inc(Y, 8);
- if (Y >= Height) then
- begin
- inc(Pass);
- Y := 2;
- end;
- end;
- 2:
- begin
- inc(Y, 4);
- if (Y >= Height) then
- begin
- inc(Pass);
- Y := 1;
- end;
- end;
- 3:
- inc(Y, 2);
- end;
- if (Y >= height) then
- begin
- // Done - No more data
- Result := False;
- exit;
- end;
- Pixel := Data + (Y * Width);
- end;
- Result := True;
- end;
- procedure TGIFEncoder.Compress(AStream: TStream; ABitsPerPixel: integer;
- AWidth, AHeight: integer; AInterlace: boolean; AData: PChar; AMaxColor: integer);
- const
- EndBlockByte = $00; // End of block marker
- {$ifdef DEBUG_COMPRESSPERFORMANCE}
- var
- TimeStartCompress ,
- TimeStopCompress : DWORD;
- {$endif}
- begin
- MaxColor := AMaxColor;
- Stream := AStream;
- BitsPerPixel := ABitsPerPixel;
- Width := AWidth;
- Height := AHeight;
- Interlace := AInterlace;
- Data := AData;
- if (BitsPerPixel <= 1) then
- BitsPerPixel := 2;
- InitialBitsPerCode := BitsPerPixel + 1;
- Stream.Write(BitsPerPixel, 1);
- // out_bits_init = init_bits;
- BitsPerCode := InitialBitsPerCode;
- MaxCode := MaxCodesFromBits(BitsPerCode);
- ClearCode := (1 SHL (InitialBitsPerCode - 1));
- EOFCode := ClearCode + 1;
- BaseCode := EOFCode + 1;
- // Clear bit bucket
- OutputBucket := 0;
- OutputBits := 0;
- // Reset pixel counter
- if (Interlace) then
- cX := Width
- else
- cX := Width*Height;
- // Reset row counter
- Y := 0;
- Pass := 0;
- GIFStream := TGIFWriter.Create(AStream);
- try
- GIFStream.Warning := Warning;
- if (Data <> nil) and (Height > 0) and (Width > 0) then
- begin
- {$ifdef DEBUG_COMPRESSPERFORMANCE}
- TimeStartCompress := timeGetTime;
- {$endif}
- // Call compress implementation
- DoCompress;
- {$ifdef DEBUG_COMPRESSPERFORMANCE}
- TimeStopCompress := timeGetTime;
- ShowMessage(format('Compressed %d pixels in %d mS, Rate %d pixels/mS',
- [Height*Width, TimeStopCompress-TimeStartCompress,
- DWORD(Height*Width) DIV (TimeStopCompress-TimeStartCompress+1)]));
- {$endif}
- // Output the final code.
- Output(EOFCode);
- end else
- // Output the final code (and nothing else).
- TGIFEncoder(self).Output(EOFCode);
- finally
- GIFStream.Free;
- end;
- WriteByte(Stream, EndBlockByte);
- end;
- ////////////////////////////////////////////////////////////////////////////////
- // TRLEEncoder - RLE encoder
- ////////////////////////////////////////////////////////////////////////////////
- type
- TRLEEncoder = class(TGIFEncoder)
- private
- MaxCodes : integer;
- OutBumpInit ,
- OutClearInit : integer;
- Prefix : integer; // Current run color
- RunLengthTableMax ,
- RunLengthTablePixel ,
- OutCount ,
- OutClear ,
- OutBump : integer;
- protected
- function ComputeTriangleCount(count: integer; nrepcodes: integer): integer;
- procedure MaxOutClear;
- procedure ResetOutClear;
- procedure FlushFromClear(Count: integer);
- procedure FlushClearOrRepeat(Count: integer);
- procedure FlushWithTable(Count: integer);
- procedure Flush(RunLengthCount: integer);
- procedure OutputPlain(Value: integer);
- procedure Clear; override;
- procedure DoCompress; override;
- end;
- procedure TRLEEncoder.Clear;
- begin
- OutBump := OutBumpInit;
- OutClear := OutClearInit;
- OutCount := 0;
- RunLengthTableMax := 0;
- inherited Clear;
- BitsPerCode := InitialBitsPerCode;
- end;
- procedure TRLEEncoder.OutputPlain(Value: integer);
- begin
- ClearFlag := False;
- Output(Value);
- inc(OutCount);
- if (OutCount >= OutBump) then
- begin
- inc(BitsPerCode);
- inc(OutBump, 1 SHL (BitsPerCode - 1));
- end;
- if (OutCount >= OutClear) then
- Clear;
- end;
- function TRLEEncoder.ComputeTriangleCount(count: integer; nrepcodes: integer): integer;
- var
- PerRepeat : integer;
- n : integer;
- function iSqrt(x: integer): integer;
- var
- r, v : integer;
- begin
- if (x < 2) then
- begin
- Result := x;
- exit;
- end else
- begin
- v := x;
- r := 1;
- while (v > 0) do
- begin
- v := v DIV 4;
- r := r * 2;
- end;
- end;
- while (True) do
- begin
- v := ((x DIV r) + r) DIV 2;
- if ((v = r) or (v = r+1)) then
- begin
- Result := r;
- exit;
- end;
- r := v;
- end;
- end;
- begin
- Result := 0;
- PerRepeat := (nrepcodes * (nrepcodes+1)) DIV 2;
- while (Count >= PerRepeat) do
- begin
- inc(Result, nrepcodes);
- dec(Count, PerRepeat);
- end;
- if (Count > 0) then
- begin
- n := iSqrt(Count);
- while ((n * (n+1)) >= 2*Count) do
- dec(n);
- while ((n * (n+1)) < 2*Count) do
- inc(n);
- inc(Result, n);
- end;
- end;
- procedure TRLEEncoder.MaxOutClear;
- begin
- OutClear := MaxCodes;
- end;
- procedure TRLEEncoder.ResetOutClear;
- begin
- OutClear := OutClearInit;
- if (OutCount >= OutClear) then
- Clear;
- end;
- procedure TRLEEncoder.FlushFromClear(Count: integer);
- var
- n : integer;
- begin
- MaxOutClear;
- RunLengthTablePixel := Prefix;
- n := 1;
- while (Count > 0) do
- begin
- if (n = 1) then
- begin
- RunLengthTableMax := 1;
- OutputPlain(Prefix);
- dec(Count);
- end else
- if (Count >= n) then
- begin
- RunLengthTableMax := n;
- OutputPlain(BaseCode + n - 2);
- dec(Count, n);
- end else
- if (Count = 1) then
- begin
- inc(RunLengthTableMax);
- OutputPlain(Prefix);
- break;
- end else
- begin
- inc(RunLengthTableMax);
- OutputPlain(BaseCode + Count - 2);
- break;
- end;
- if (OutCount = 0) then
- n := 1
- else
- inc(n);
- end;
- ResetOutClear;
- end;
- procedure TRLEEncoder.FlushClearOrRepeat(Count: integer);
- var
- WithClear : integer;
- begin
- WithClear := 1 + ComputeTriangleCount(Count, MaxCodes);
- if (WithClear < Count) then
- begin
- Clear;
- FlushFromClear(Count);
- end else
- while (Count > 0) do
- begin
- OutputPlain(Prefix);
- dec(Count);
- end;
- end;
- procedure TRLEEncoder.FlushWithTable(Count: integer);
- var
- RepeatMax ,
- RepeatLeft ,
- LeftOver : integer;
- begin
- RepeatMax := Count DIV RunLengthTableMax;
- LeftOver := Count MOD RunLengthTableMax;
- if (LeftOver <> 0) then
- RepeatLeft := 1
- else
- RepeatLeft := 0;
- if (OutCount + RepeatMax + RepeatLeft > MaxCodes) then
- begin
- RepeatMax := MaxCodes - OutCount;
- LeftOver := Count - (RepeatMax * RunLengthTableMax);
- RepeatLeft := 1 + ComputeTriangleCount(LeftOver, MaxCodes);
- end;
- if (1 + ComputeTriangleCount(Count, MaxCodes) < RepeatMax + RepeatLeft) then
- begin
- Clear;
- FlushFromClear(Count);
- exit;
- end;
- MaxOutClear;
- while (RepeatMax > 0) do
- begin
- OutputPlain(BaseCode + RunLengthTableMax-2);
- dec(RepeatMax);
- end;
- if (LeftOver > 0) then
- begin
- if (ClearFlag) then
- FlushFromClear(LeftOver)
- else if (LeftOver = 1) then
- OutputPlain(Prefix)
- else
- OutputPlain(BaseCode + LeftOver - 2);
- end;
- ResetOutClear;
- end;
- procedure TRLEEncoder.Flush(RunLengthCount: integer);
- begin
- if (RunLengthCount = 1) then
- begin
- OutputPlain(Prefix);
- exit;
- end;
- if (ClearFlag) then
- FlushFromClear(RunLengthCount)
- else if ((RunLengthTableMax < 2) or (RunLengthTablePixel <> Prefix)) then
- FlushClearOrRepeat(RunLengthCount)
- else
- FlushWithTable(RunLengthCount);
- end;
- procedure TRLEEncoder.DoCompress;
- var
- Color : CodeInt;
- RunLengthCount : integer;
- begin
- OutBumpInit := ClearCode - 1;
- // For images with a lot of runs, making OutClearInit larger will
- // give better compression.
- if (BitsPerPixel <= 3) then
- OutClearInit := 9
- else
- OutClearInit := OutBumpInit - 1;
- // max_ocodes = (1 << GIFBITS) - ((1 << (out_bits_init - 1)) + 3);
- // <=> MaxCodes := (1 SHL GIFCodeBits) - ((1 SHL (BitsPerCode - 1)) + 3);
- // <=> MaxCodes := (1 SHL GIFCodeBits) - ((1 SHL (InitialBitsPerCode - 1)) + 3);
- // <=> MaxCodes := (1 SHL GIFCodeBits) - (ClearCode + 3);
- // <=> MaxCodes := (1 SHL GIFCodeBits) - (EOFCode + 2);
- // <=> MaxCodes := (1 SHL GIFCodeBits) - (BaseCode + 1);
- // <=> MaxCodes := MaxCodesFromBits(GIFCodeBits) - BaseCode;
- MaxCodes := MaxCodesFromBits(GIFCodeBits) - BaseCode;
- Clear;
- RunLengthCount := 0;
- Pixel := Data;
- Prefix := -1; // Dummy value to make Color <> Prefix
- repeat
- // Fetch the next pixel
- Color := CodeInt(Pixel^);
- inc(Pixel);
- if (Color >= MaxColor) then
- Error(sInvalidColor);
- if (RunLengthCount > 0) and (Color <> Prefix) then
- begin
- // End of current run
- Flush(RunLengthCount);
- RunLengthCount := 0;
- end;
- if (Color = Prefix) then
- // Increment run length
- inc(RunLengthCount)
- else
- begin
- // Start new run
- Prefix := Color;
- RunLengthCount := 1;
- end;
- until not(BumpPixel);
- Flush(RunLengthCount);
- end;
- ////////////////////////////////////////////////////////////////////////////////
- // TLZWEncoder - LZW encoder
- ////////////////////////////////////////////////////////////////////////////////
- const
- TableMaxMaxCode = (1 SHL GIFCodeBits); //
- TableMaxFill = TableMaxMaxCode-1; // Clear table when it fills to
- // this point.
- // Note: Must be <= GIFCodeMax
- type
- TLZWEncoder = class(TGIFEncoder)
- private
- Prefix : CodeInt; // Current run color
- FreeEntry : CodeInt; // next unused code in table
- HashTable : THashTable;
- protected
- procedure Output(Value: integer); override;
- procedure Clear; override;
- procedure DoCompress; override;
- end;
- procedure TLZWEncoder.Output(Value: integer);
- begin
- inherited Output(Value);
- // If the next entry is going to be too big for the code size,
- // then increase it, if possible.
- if (FreeEntry > MaxCode) or (ClearFlag) then
- begin
- if (ClearFlag) then
- begin
- BitsPerCode := InitialBitsPerCode;
- MaxCode := MaxCodesFromBits(BitsPerCode);
- ClearFlag := False;
- end else
- begin
- inc(BitsPerCode);
- if (BitsPerCode = GIFCodeBits) then
- MaxCode := TableMaxMaxCode
- else
- MaxCode := MaxCodesFromBits(BitsPerCode);
- end;
- end;
- end;
- procedure TLZWEncoder.Clear;
- begin
- inherited Clear;
- HashTable.Clear;
- FreeEntry := ClearCode + 2;
- end;
- procedure TLZWEncoder.DoCompress;
- var
- Color : char;
- NewKey : KeyInt;
- NewCode : CodeInt;
- begin
- HashTable := THashTable.Create;
- try
- // clear hash table and sync decoder
- Clear;
- Pixel := Data;
- Prefix := CodeInt(Pixel^);
- inc(Pixel);
- if (Prefix >= MaxColor) then
- Error(sInvalidColor);
- while (BumpPixel) do
- begin
- // Fetch the next pixel
- Color := Pixel^;
- inc(Pixel);
- if (ord(Color) >= MaxColor) then
- Error(sInvalidColor);
- // Append Postfix to Prefix and lookup in table...
- NewKey := (KeyInt(Prefix) SHL 8) OR ord(Color);
- NewCode := HashTable.Lookup(NewKey);
- if (NewCode >= 0) then
- begin
- // ...if found, get next pixel
- Prefix := NewCode;
- continue;
- end;
- // ...if not found, output and start over
- Output(Prefix);
- Prefix := CodeInt(Color);
- if (FreeEntry < TableMaxFill) then
- begin
- HashTable.Insert(NewKey, FreeEntry);
- inc(FreeEntry);
- end else
- Clear;
- end;
- Output(Prefix);
- finally
- HashTable.Free;
- end;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFSubImage
- //
- ////////////////////////////////////////////////////////////////////////////////
- /////////////////////////////////////////////////////////////////////////
- // TGIFSubImage.Compress
- /////////////////////////////////////////////////////////////////////////
- procedure TGIFSubImage.Compress(Stream: TStream);
- var
- Encoder : TGIFEncoder;
- BitsPerPixel : BYTE;
- MaxColors : integer;
- begin
- if (ColorMap.Count > 0) then
- begin
- MaxColors := ColorMap.Count;
- BitsPerPixel := ColorMap.BitsPerPixel
- end else
- begin
- BitsPerPixel := Image.BitsPerPixel;
- MaxColors := 1 SHL BitsPerPixel;
- end;
- // Create a RLE or LZW GIF encoder
- if (Image.Compression = gcRLE) then
- Encoder := TRLEEncoder.Create
- else
- Encoder := TLZWEncoder.Create;
- try
- Encoder.Warning := Image.Warning;
- Encoder.Compress(Stream, BitsPerPixel, Width, Height, Interlaced, FData, MaxColors);
- finally
- Encoder.Free;
- end;
- end;
- function TGIFExtensionList.GetExtension(Index: Integer): TGIFExtension;
- begin
- Result := TGIFExtension(Items[Index]);
- end;
- procedure TGIFExtensionList.SetExtension(Index: Integer; Extension: TGIFExtension);
- begin
- Items[Index] := Extension;
- end;
- procedure TGIFExtensionList.LoadFromStream(Stream: TStream; Parent: TObject);
- var
- b : BYTE;
- Extension : TGIFExtension;
- ExtensionClass : TGIFExtensionClass;
- begin
- // Peek ahead to determine block type
- if (Stream.Read(b, 1) <> 1) then
- exit;
- while not(b in [bsTrailer, bsImageDescriptor]) do
- begin
- if (b = bsExtensionIntroducer) then
- begin
- ExtensionClass := TGIFExtension.FindExtension(Stream);
- if (ExtensionClass = nil) then
- Error(sUnknownExtension);
- Stream.Seek(-1, soFromCurrent);
- Extension := ExtensionClass.Create(Parent as TGIFSubImage);
- try
- Extension.LoadFromStream(Stream);
- Add(Extension);
- except
- Extension.Free;
- raise;
- end;
- end else
- begin
- Warning(gsWarning, sBadExtensionLabel);
- break;
- end;
- if (Stream.Read(b, 1) <> 1) then
- exit;
- end;
- Stream.Seek(-1, soFromCurrent);
- end;
- const
- { image descriptor bit masks }
- idLocalColorTable = $80; { set if a local color table follows }
- idInterlaced = $40; { set if image is interlaced }
- idSort = $20; { set if color table is sorted }
- idReserved = $0C; { reserved - must be set to $00 }
- idColorTableSize = $07; { size of color table as above }
- constructor TGIFSubImage.Create(GIFImage: TGIFImage);
- begin
- inherited Create(GIFImage);
- FExtensions := TGIFExtensionList.Create(GIFImage);
- FColorMap := TGIFLocalColorMap.Create(self);
- FImageDescriptor.Separator := bsImageDescriptor;
- FImageDescriptor.Left := 0;
- FImageDescriptor.Top := 0;
- FImageDescriptor.Width := 0;
- FImageDescriptor.Height := 0;
- FImageDescriptor.PackedFields := 0;
- FBitmap := nil;
- FMask := 0;
- FNeedMask := True;
- FData := nil;
- FDataSize := 0;
- FTransparent := False;
- FGCE := nil;
- // Remember to synchronize with TGIFSubImage.Clear
- end;
- destructor TGIFSubImage.Destroy;
- begin
- if (FGIFImage <> nil) then
- FGIFImage.Images.Remove(self);
- Clear;
- FExtensions.Free;
- FColorMap.Free;
- if (FLocalPalette <> 0) then
- DeleteObject(FLocalPalette);
- inherited Destroy;
- end;
- procedure TGIFSubImage.Clear;
- begin
- FExtensions.Clear;
- FColorMap.Clear;
- FreeImage;
- Height := 0;
- Width := 0;
- FTransparent := False;
- FGCE := nil;
- FreeBitmap;
- FreeMask;
- // Remember to synchronize with TGIFSubImage.Create
- end;
- function TGIFSubImage.GetEmpty: Boolean;
- begin
- Result := ((FData = nil) or (FDataSize = 0) or (Height = 0) or (Width = 0));
- end;
- function TGIFSubImage.GetPalette: HPALETTE;
- begin
- if (FBitmap <> nil) and (FBitmap.Palette <> 0) then
- // Use bitmaps own palette if possible
- Result := FBitmap.Palette
- else if (FLocalPalette <> 0) then
- // Or a previously exported local palette
- Result := FLocalPalette
- else if (Image.DoDither) then
- begin
- // or create a new dither palette
- FLocalPalette := WebPalette;
- Result := FLocalPalette;
- end
- else if (ColorMap.Count > 0) then
- begin
- // or create a new if first time
- FLocalPalette := ColorMap.ExportPalette;
- Result := FLocalPalette;
- end else
- // Use global palette if everything else fails
- Result := Image.Palette;
- end;
- procedure TGIFSubImage.SetPalette(Value: HPalette);
- var
- NeedNewBitmap : boolean;
- begin
- if (Value <> FLocalPalette) then
- begin
- // Zap old palette
- if (FLocalPalette <> 0) then
- DeleteObject(FLocalPalette);
- // Zap bitmap unless new palette is same as bitmaps own
- NeedNewBitmap := (FBitmap <> nil) and (Value <> FBitmap.Palette);
- // Use new palette
- FLocalPalette := Value;
- if (NeedNewBitmap) then
- begin
- // Need to create new bitmap and repaint
- FreeBitmap;
- Image.PaletteModified := True;
- Image.Changed(Self);
- end;
- end;
- end;
- procedure TGIFSubImage.NeedImage;
- begin
- if (FData = nil) then
- NewImage;
- if (FDataSize = 0) then
- Error(sEmptyImage);
- end;
- procedure TGIFSubImage.NewImage;
- var
- NewSize : longInt;
- begin
- FreeImage;
- NewSize := Height * Width;
- if (NewSize <> 0) then
- begin
- GetMem(FData, NewSize);
- FillChar(FData^, NewSize, 0);
- end else
- FData := nil;
- FDataSize := NewSize;
- end;
- procedure TGIFSubImage.FreeImage;
- begin
- if (FData <> nil) then
- FreeMem(FData);
- FDataSize := 0;
- FData := nil;
- end;
- function TGIFSubImage.GetHasBitmap: boolean;
- begin
- Result := (FBitmap <> nil);
- end;
- procedure TGIFSubImage.SetHasBitmap(Value: boolean);
- begin
- if (Value <> (FBitmap <> nil)) then
- begin
- if (Value) then
- Bitmap // Referencing Bitmap will automatically create it
- else
- FreeBitmap;
- end;
- end;
- procedure TGIFSubImage.NewBitmap;
- begin
- FreeBitmap;
- FBitmap := TBitmap.Create;
- end;
- procedure TGIFSubImage.FreeBitmap;
- begin
- if (FBitmap <> nil) then
- begin
- FBitmap.Free;
- FBitmap := nil;
- end;
- end;
- procedure TGIFSubImage.FreeMask;
- begin
- if (FMask <> 0) then
- begin
- DeleteObject(FMask);
- FMask := 0;
- end;
- FNeedMask := True;
- end;
- function TGIFSubImage.HasMask: boolean;
- begin
- if (FNeedMask) and (Transparent) then
- begin
- // Zap old bitmap
- FreeBitmap;
- // Create new bitmap and mask
- GetBitmap;
- end;
- Result := (FMask <> 0);
- end;
- function TGIFSubImage.GetBounds(Index: integer): WORD;
- begin
- case (Index) of
- 1: Result := FImageDescriptor.Left;
- 2: Result := FImageDescriptor.Top;
- 3: Result := FImageDescriptor.Width;
- 4: Result := FImageDescriptor.Height;
- else
- Result := 0; // To avoid compiler warnings
- end;
- end;
- procedure TGIFSubImage.SetBounds(Index: integer; Value: WORD);
- begin
- case (Index) of
- 1: DoSetBounds(Value, FImageDescriptor.Top, FImageDescriptor.Width, FImageDescriptor.Height);
- 2: DoSetBounds(FImageDescriptor.Left, Value, FImageDescriptor.Width, FImageDescriptor.Height);
- 3: DoSetBounds(FImageDescriptor.Left, FImageDescriptor.Top, Value, FImageDescriptor.Height);
- 4: DoSetBounds(FImageDescriptor.Left, FImageDescriptor.Top, FImageDescriptor.Width, Value);
- end;
- end;
- {$IFOPT R+}
- {$DEFINE R_PLUS}
- {$RANGECHECKS OFF}
- {$ENDIF}
- function TGIFSubImage.DoGetDitherBitmap: TBitmap;
- var
- ColorLookup : TColorLookup;
- Ditherer : TDitherEngine;
- DIBResult : TDIB;
- Src : PChar;
- Dst : PChar;
- Row : integer;
- Color : TGIFColor;
- ColMap : PColorMap;
- Index : byte;
- TransparentIndex : byte;
- IsTransparent : boolean;
- WasTransparent : boolean;
- MappedTransparentIndex: char;
- MaskBits : PChar;
- MaskDest : PChar;
- MaskRow : PChar;
- MaskRowWidth ,
- MaskRowBitWidth : integer;
- Bit ,
- RightBit : BYTE;
- begin
- Result := TBitmap.Create;
- try
- {$IFNDEF VER9x}
- if (Width*Height > BitmapAllocationThreshold) then
- SetPixelFormat(Result, pf1bit); // To reduce resource consumption of resize
- {$ENDIF}
- if (Empty) then
- begin
- // Set bitmap width and height
- Result.Width := Width;
- Result.Height := Height;
- // Build and copy palette to bitmap
- Result.Palette := CopyPalette(Palette);
- exit;
- end;
- ColorLookup := nil;
- Ditherer := nil;
- DIBResult := nil;
- try // Protect above resources
- ColorLookup := TNetscapeColorLookup.Create(Palette);
- Ditherer := TFloydSteinbergDitherer.Create(Width, ColorLookup);
- // Get DIB buffer for scanline operations
- // It is assumed that the source palette is the 216 color Netscape palette
- DIBResult := TDIBWriter.Create(Result, pf8bit, Width, Height, Palette);
- // Determine if this image is transparent
- ColMap := ActiveColorMap.Data;
- IsTransparent := FNeedMask and Transparent;
- WasTransparent := False;
- FNeedMask := False;
- TransparentIndex := 0;
- MappedTransparentIndex := #0;
- if (FMask = 0) and (IsTransparent) then
- begin
- IsTransparent := True;
- TransparentIndex := GraphicControlExtension.TransparentColorIndex;
- Color := ColMap[ord(TransparentIndex)];
- MappedTransparentIndex := char(Color.Blue DIV 51 +
- MulDiv(6, Color.Green, 51) + MulDiv(36, Color.Red, 51)+1);
- end;
- // Allocate bit buffer for transparency mask
- MaskDest := nil;
- Bit := $00;
- if (IsTransparent) then
- begin
- MaskRowWidth := ((Width+15) DIV 16) * 2;
- MaskRowBitWidth := (Width+7) DIV 8;
- RightBit := $01 SHL ((8 - (Width AND $0007)) AND $0007);
- GetMem(MaskBits, MaskRowWidth * Height);
- FillChar(MaskBits^, MaskRowWidth * Height, 0);
- end else
- begin
- MaskBits := nil;
- MaskRowWidth := 0;
- MaskRowBitWidth := 0;
- RightBit := $00;
- end;
- try
- // Process the image
- Row := 0;
- MaskRow := MaskBits;
- Src := FData;
- while (Row < Height) do
- begin
- if ((Row AND $1F) = 0) then
- Image.Progress(Self, psRunning, MulDiv(Row, 100, Height),
- False, Rect(0,0,0,0), sProgressRendering);
- Dst := DIBResult.ScanLine[Row];
- if (IsTransparent) then
- begin
- // Preset all pixels to transparent
- FillChar(Dst^, Width, ord(MappedTransparentIndex));
- if (Ditherer.Direction = 1) then
- begin
- MaskDest := MaskRow;
- Bit := $80;
- end else
- begin
- MaskDest := MaskRow + MaskRowBitWidth-1;
- Bit := RightBit;
- end;
- end;
- inc(Dst, Ditherer.Column);
- while (Ditherer.Column < Ditherer.Width) and (Ditherer.Column >= 0) do
- begin
- Index := ord(Src^);
- Color := ColMap[ord(Index)];
- if (IsTransparent) and (Index = TransparentIndex) then
- begin
- MaskDest^ := char(byte(MaskDest^) OR Bit);
- WasTransparent := True;
- Ditherer.NextColumn;
- end else
- begin
- // Dither and map a single pixel
- Dst^ := Ditherer.Dither(Color.Red, Color.Green, Color.Blue,
- Color.Red, Color.Green, Color.Blue);
- end;
- if (IsTransparent) then
- begin
- if (Ditherer.Direction = 1) then
- begin
- Bit := Bit SHR 1;
- if (Bit = $00) then
- begin
- Bit := $80;
- inc(MaskDest, 1);
- end;
- end else
- begin
- Bit := Bit SHL 1;
- if (Bit = $00) then
- begin
- Bit := $01;
- dec(MaskDest, 1);
- end;
- end;
- end;
- inc(Src, Ditherer.Direction);
- inc(Dst, Ditherer.Direction);
- end;
- if (IsTransparent) then
- Inc(MaskRow, MaskRowWidth);
- Inc(Row);
- inc(Src, Width-Ditherer.Direction);
- Ditherer.NextLine;
- end;
- // Transparent paint needs a mask bitmap
- if (IsTransparent) and (WasTransparent) then
- FMask := CreateBitmap(Width, Height, 1, 1, MaskBits);
- finally
- if (MaskBits <> nil) then
- FreeMem(MaskBits);
- end;
- finally
- if (ColorLookup <> nil) then
- ColorLookup.Free;
- if (Ditherer <> nil) then
- Ditherer.Free;
- if (DIBResult <> nil) then
- DIBResult.Free;
- end;
- except
- Result.Free;
- raise;
- end;
- end;
- {$IFDEF R_PLUS}
- {$RANGECHECKS ON}
- {$UNDEF R_PLUS}
- {$ENDIF}
- function TGIFSubImage.DoGetBitmap: TBitmap;
- var
- ScanLineRow : Integer;
- DIBResult : TDIB;
- DestScanLine ,
- Src : PChar;
- TransparentIndex : byte;
- IsTransparent : boolean;
- WasTransparent : boolean;
- MaskBits : PChar;
- MaskDest : PChar;
- MaskRow : PChar;
- MaskRowWidth : integer;
- Col : integer;
- MaskByte : byte;
- Bit : byte;
- begin
- Result := TBitmap.Create;
- try
- {$IFNDEF VER9x}
- if (Width*Height > BitmapAllocationThreshold) then
- SetPixelFormat(Result, pf1bit); // To reduce resource consumption of resize
- {$ENDIF}
- if (Empty) then
- begin
- // Set bitmap width and height
- Result.Width := Width;
- Result.Height := Height;
- // Build and copy palette to bitmap
- Result.Palette := CopyPalette(Palette);
- exit;
- end;
- // Get DIB buffer for scanline operations
- DIBResult := TDIBWriter.Create(Result, pf8bit, Width, Height, Palette);
- try
- // Determine if this image is transparent
- IsTransparent := FNeedMask and Transparent;
- WasTransparent := False;
- FNeedMask := False;
- TransparentIndex := 0;
- if (FMask = 0) and (IsTransparent) then
- begin
- IsTransparent := True;
- TransparentIndex := GraphicControlExtension.TransparentColorIndex;
- end;
- // Allocate bit buffer for transparency mask
- if (IsTransparent) then
- begin
- MaskRowWidth := ((Width+15) DIV 16) * 2;
- GetMem(MaskBits, MaskRowWidth * Height);
- FillChar(MaskBits^, MaskRowWidth * Height, 0);
- IsTransparent := (MaskBits <> nil);
- end else
- begin
- MaskBits := nil;
- MaskRowWidth := 0;
- end;
- try
- ScanLineRow := 0;
- Src := FData;
- MaskRow := MaskBits;
- while (ScanLineRow < Height) do
- begin
- DestScanline := DIBResult.ScanLine[ScanLineRow];
- if ((ScanLineRow AND $1F) = 0) then
- Image.Progress(Self, psRunning, MulDiv(ScanLineRow, 100, Height),
- False, Rect(0,0,0,0), sProgressRendering);
- Move(Src^, DestScanline^, Width);
- Inc(ScanLineRow);
- if (IsTransparent) then
- begin
- Bit := $80;
- MaskDest := MaskRow;
- MaskByte := 0;
- for Col := 0 to Width-1 do
- begin
- // Set a bit in the mask if the pixel is transparent
- if (Src^ = char(TransparentIndex)) then
- MaskByte := MaskByte OR Bit;
- Bit := Bit SHR 1;
- if (Bit = $00) then
- begin
- // Store a mask byte for each 8 pixels
- Bit := $80;
- WasTransparent := WasTransparent or (MaskByte <> 0);
- MaskDest^ := char(MaskByte);
- inc(MaskDest);
- MaskByte := 0;
- end;
- Inc(Src);
- end;
- // Save the last mask byte in case the width isn't divisable by 8
- if (MaskByte <> 0) then
- begin
- WasTransparent := True;
- MaskDest^ := char(MaskByte);
- end;
- Inc(MaskRow, MaskRowWidth);
- end else
- Inc(Src, Width);
- end;
- // Transparent paint needs a mask bitmap
- if (IsTransparent) and (WasTransparent) then
- FMask := CreateBitmap(Width, Height, 1, 1, MaskBits);
- finally
- if (MaskBits <> nil) then
- FreeMem(MaskBits);
- end;
- finally
- // Free DIB buffer used for scanline operations
- DIBResult.Free;
- end;
- except
- Result.Free;
- raise;
- end;
- end;
- {$ifdef DEBUG_RENDERPERFORMANCE}
- var
- ImageCount : DWORD = 0;
- RenderTime : DWORD = 0;
- {$endif}
- function TGIFSubImage.GetBitmap: TBitmap;
- var
- n : integer;
- {$ifdef DEBUG_RENDERPERFORMANCE}
- RenderStartTime : DWORD;
- {$endif}
- begin
- {$ifdef DEBUG_RENDERPERFORMANCE}
- if (GetAsyncKeyState(VK_CONTROL) <> 0) then
- begin
- ShowMessage(format('Render %d images in %d mS, Rate %d mS/image (%d images/S)',
- [ImageCount, RenderTime,
- RenderTime DIV (ImageCount+1),
- MulDiv(ImageCount, 1000, RenderTime+1)]));
- end;
- {$endif}
- Result := FBitmap;
- if (Result <> nil) or (Empty) then
- Exit;
- {$ifdef DEBUG_RENDERPERFORMANCE}
- inc(ImageCount);
- RenderStartTime := timeGetTime;
- {$endif}
- try
- Image.Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressRendering);
- try
- if (Image.DoDither) then
- // Create dithered bitmap
- FBitmap := DoGetDitherBitmap
- else
- // Create "regular" bitmap
- FBitmap := DoGetBitmap;
- Result := FBitmap;
- finally
- if ExceptObject = nil then
- n := 100
- else
- n := 0;
- Image.Progress(Self, psEnding, n, Image.PaletteModified, Rect(0,0,0,0),
- sProgressRendering);
- // Make sure new palette gets realized, in case OnProgress event didn't.
- if Image.PaletteModified then
- Image.Changed(Self);
- end;
- except
- on EAbort do ; // OnProgress can raise EAbort to cancel image load
- end;
- {$ifdef DEBUG_RENDERPERFORMANCE}
- inc(RenderTime, timeGetTime-RenderStartTime);
- {$endif}
- end;
- procedure TGIFSubImage.SetBitmap(Value: TBitmap);
- begin
- FreeBitmap;
- if (Value <> nil) then
- Assign(Value);
- end;
- function TGIFSubImage.GetActiveColorMap: TGIFColorMap;
- begin
- if (ColorMap.Count > 0) or (Image.GlobalColorMap.Count = 0) then
- Result := ColorMap
- else
- Result := Image.GlobalColorMap;
- end;
- function TGIFSubImage.GetInterlaced: boolean;
- begin
- Result := (FImageDescriptor.PackedFields AND idInterlaced) <> 0;
- end;
- procedure TGIFSubImage.SetInterlaced(Value: boolean);
- begin
- if (Value) then
- FImageDescriptor.PackedFields := FImageDescriptor.PackedFields OR idInterlaced
- else
- FImageDescriptor.PackedFields := FImageDescriptor.PackedFields AND NOT(idInterlaced);
- end;
- function TGIFSubImage.GetVersion: TGIFVersion;
- var
- v : TGIFVersion;
- i : integer;
- begin
- if (ColorMap.Optimized) then
- Result := gv89a
- else
- Result := inherited GetVersion;
- i := 0;
- while (Result < high(TGIFVersion)) and (i < FExtensions.Count) do
- begin
- v := FExtensions[i].Version;
- if (v > Result) then
- Result := v;
- end;
- end;
- function TGIFSubImage.GetColorResolution: integer;
- begin
- Result := ColorMap.BitsPerPixel-1;
- end;
- function TGIFSubImage.GetBitsPerPixel: integer;
- begin
- Result := ColorMap.BitsPerPixel;
- end;
- function TGIFSubImage.GetBoundsRect: TRect;
- begin
- Result := Rect(FImageDescriptor.Left,
- FImageDescriptor.Top,
- FImageDescriptor.Left+FImageDescriptor.Width,
- FImageDescriptor.Top+FImageDescriptor.Height);
- end;
- procedure TGIFSubImage.DoSetBounds(ALeft, ATop, AWidth, AHeight: integer);
- var
- TooLarge : boolean;
- Zap : boolean;
- begin
- Zap := (FImageDescriptor.Width <> Width) or (FImageDescriptor.Height <> AHeight);
- FImageDescriptor.Left := ALeft;
- FImageDescriptor.Top := ATop;
- FImageDescriptor.Width := AWidth;
- FImageDescriptor.Height := AHeight;
- // Delete existing image and bitmaps if size has changed
- if (Zap) then
- begin
- FreeBitmap;
- FreeMask;
- FreeImage;
- // ...and allocate a new image
- NewImage;
- end;
- TooLarge := False;
- // Set width & height if added image is larger than existing images
- {$IFDEF STRICT_MOZILLA}
- // From Mozilla source:
- // Work around broken GIF files where the logical screen
- // size has weird width or height. [...]
- if (Image.Width < AWidth) or (Image.Height < AHeight) then
- begin
- TooLarge := True;
- Image.Width := AWidth;
- Image.Height := AHeight;
- Left := 0;
- Top := 0;
- end;
- {$ELSE}
- if (Image.Width < ALeft+AWidth) then
- begin
- if (Image.Width > 0) then
- begin
- TooLarge := True;
- Warning(gsWarning, sBadWidth)
- end;
- Image.Width := ALeft+AWidth;
- end;
- if (Image.Height < ATop+AHeight) then
- begin
- if (Image.Height > 0) then
- begin
- TooLarge := True;
- Warning(gsWarning, sBadHeight)
- end;
- Image.Height := ATop+AHeight;
- end;
- {$ENDIF}
- if (TooLarge) then
- Warning(gsWarning, sScreenSizeExceeded);
- end;
- procedure TGIFSubImage.SetBoundsRect(const Value: TRect);
- begin
- DoSetBounds(Value.Left, Value.Top, Value.Right-Value.Left+1, Value.Bottom-Value.Top+1);
- end;
- function TGIFSubImage.GetClientRect: TRect;
- begin
- Result := Rect(0, 0, FImageDescriptor.Width, FImageDescriptor.Height);
- end;
- function TGIFSubImage.GetPixel(x, y: integer): BYTE;
- begin
- if (x < 0) or (x > Width-1) then
- Error(sBadPixelCoordinates);
- Result := BYTE(PChar(longInt(Scanline[y]) + x)^);
- end;
- function TGIFSubImage.GetScanline(y: integer): pointer;
- begin
- if (y < 0) or (y > Height-1) then
- Error(sBadPixelCoordinates);
- NeedImage;
- Result := pointer(longInt(FData) + y * Width);
- end;
- procedure TGIFSubImage.Prepare;
- var
- Pack : BYTE;
- begin
- Pack := FImageDescriptor.PackedFields;
- if (ColorMap.Count > 0) then
- begin
- Pack := idLocalColorTable;
- if (ColorMap.Optimized) then
- Pack := Pack OR idSort;
- Pack := (Pack AND NOT(idColorTableSize)) OR (ColorResolution AND idColorTableSize);
- end else
- Pack := Pack AND NOT(idLocalColorTable OR idSort OR idColorTableSize);
- FImageDescriptor.PackedFields := Pack;
- end;
- procedure TGIFSubImage.SaveToStream(Stream: TStream);
- begin
- FExtensions.SaveToStream(Stream);
- if (Empty) then
- exit;
- Prepare;
- Stream.Write(FImageDescriptor, sizeof(TImageDescriptor));
- ColorMap.SaveToStream(Stream);
- Compress(Stream);
- end;
- procedure TGIFSubImage.LoadFromStream(Stream: TStream);
- var
- ColorCount : integer;
- b : BYTE;
- begin
- Clear;
- FExtensions.LoadFromStream(Stream, self);
- // Check for extension without image
- if (Stream.Read(b, 1) <> 1) then
- exit;
- Stream.Seek(-1, soFromCurrent);
- if (b = bsTrailer) or (b = 0) then
- exit;
- ReadCheck(Stream, FImageDescriptor, sizeof(TImageDescriptor));
- // From Mozilla source:
- // Work around more broken GIF files that have zero image
- // width or height
- if (FImageDescriptor.Height = 0) or (FImageDescriptor.Width = 0) then
- begin
- FImageDescriptor.Height := Image.Height;
- FImageDescriptor.Width := Image.Width;
- Warning(gsWarning, sScreenSizeExceeded);
- end;
- if (FImageDescriptor.PackedFields AND idLocalColorTable = idLocalColorTable) then
- begin
- ColorCount := 2 SHL (FImageDescriptor.PackedFields AND idColorTableSize);
- if (ColorCount < 2) or (ColorCount > 256) then
- Error(sImageBadColorSize);
- ColorMap.LoadFromStream(Stream, ColorCount);
- end;
- Decompress(Stream);
- // On-load rendering
- if (GIFImageRenderOnLoad) then
- // Touch bitmap to force frame to be rendered
- Bitmap;
- end;
- procedure TGIFSubImage.AssignTo(Dest: TPersistent);
- begin
- if (Dest is TBitmap) then
- Dest.Assign(Bitmap)
- else
- inherited AssignTo(Dest);
- end;
- procedure TGIFSubImage.Assign(Source: TPersistent);
- var
- MemoryStream : TMemoryStream;
- i : integer;
- PixelFormat : TPixelFormat;
- DIBSource : TDIB;
- ABitmap : TBitmap;
- procedure Import8Bit(Dest: PChar);
- var
- y : integer;
- begin
- // Copy colormap
- {$ifdef VER10_PLUS}
- if (FBitmap.HandleType = bmDIB) then
- FColorMap.ImportDIBColors(FBitmap.Canvas.Handle)
- else
- {$ENDIF}
- FColorMap.ImportPalette(FBitmap.Palette);
- // Copy pixels
- for y := 0 to Height-1 do
- begin
- if ((y AND $1F) = 0) then
- Image.Progress(Self, psRunning, MulDiv(y, 100, Height), False, Rect(0,0,0,0), sProgressConverting);
- Move(DIBSource.Scanline[y]^, Dest^, Width);
- inc(Dest, Width);
- end;
- end;
- procedure Import4Bit(Dest: PChar);
- var
- x, y : integer;
- Scanline : PChar;
- begin
- // Copy colormap
- FColorMap.ImportPalette(FBitmap.Palette);
- // Copy pixels
- for y := 0 to Height-1 do
- begin
- if ((y AND $1F) = 0) then
- Image.Progress(Self, psRunning, MulDiv(y, 100, Height), False, Rect(0,0,0,0), sProgressConverting);
- ScanLine := DIBSource.Scanline[y];
- for x := 0 to Width-1 do
- begin
- if (x AND $01 = 0) then
- Dest^ := chr(ord(ScanLine^) SHR 4)
- else
- begin
- Dest^ := chr(ord(ScanLine^) AND $0F);
- inc(ScanLine);
- end;
- inc(Dest);
- end;
- end;
- end;
- procedure Import1Bit(Dest: PChar);
- var
- x, y : integer;
- Scanline : PChar;
- Bit : integer;
- Byte : integer;
- begin
- // Copy colormap
- FColorMap.ImportPalette(FBitmap.Palette);
- // Copy pixels
- for y := 0 to Height-1 do
- begin
- if ((y AND $1F) = 0) then
- Image.Progress(Self, psRunning, MulDiv(y, 100, Height), False, Rect(0,0,0,0), sProgressConverting);
- ScanLine := DIBSource.Scanline[y];
- x := Width;
- Bit := 0;
- Byte := 0; // To avoid compiler warning
- while (x > 0) do
- begin
- if (Bit = 0) then
- begin
- Bit := 8;
- Byte := ord(ScanLine^);
- inc(Scanline);
- end;
- Dest^ := chr((Byte AND $80) SHR 7);
- Byte := Byte SHL 1;
- inc(Dest);
- dec(Bit);
- dec(x);
- end;
- end;
- end;
- procedure Import24Bit(Dest: PChar);
- type
- TCacheEntry = record
- Color : TColor;
- Index : integer;
- end;
- const
- // Size of palette cache. Must be 2^n.
- // The cache holds the palette index of the last "CacheSize" colors
- // processed. Hopefully the cache can speed things up a bit... Initial
- // testing shows that this is indeed the case at least for non-dithered
- // bitmaps.
- // All the same, a small hash table would probably be much better.
- CacheSize = 8;
- var
- i : integer;
- Cache : array[0..CacheSize-1] of TCacheEntry;
- LastEntry : integer;
- Scanline : PRGBTriple;
- Pixel : TColor;
- RGBTriple : TRGBTriple absolute Pixel;
- x, y : integer;
- ColorMap : PColorMap;
- t : byte;
- label
- NextPixel;
- begin
- for i := 0 to CacheSize-1 do
- Cache[i].Index := -1;
- LastEntry := 0;
- // Copy all pixels and build colormap
- for y := 0 to Height-1 do
- begin
- if ((y AND $1F) = 0) then
- Image.Progress(Self, psRunning, MulDiv(y, 100, Height), False, Rect(0,0,0,0), sProgressConverting);
- ScanLine := DIBSource.Scanline[y];
- for x := 0 to Width-1 do
- begin
- Pixel := 0;
- RGBTriple := Scanline^;
- // Scan cache for color from most recently processed color to last
- // recently processed. This is done because TColorMap.AddUnique is very slow.
- i := LastEntry;
- repeat
- if (Cache[i].Index = -1) then
- break;
- if (Cache[i].Color = Pixel) then
- begin
- Dest^ := chr(Cache[i].Index);
- LastEntry := i;
- goto NextPixel;
- end;
- if (i = 0) then
- i := CacheSize-1
- else
- dec(i);
- until (i = LastEntry);
- // Color not found in cache, do it the slow way instead
- Dest^ := chr(FColorMap.AddUnique(Pixel));
- // Add color and index to cache
- LastEntry := (LastEntry + 1) AND (CacheSize-1);
- Cache[LastEntry].Color := Pixel;
- Cache[LastEntry].Index := ord(Dest^);
- NextPixel:
- Inc(Dest);
- Inc(Scanline);
- end;
- end;
- // Convert colors in colormap from BGR to RGB
- ColorMap := FColorMap.Data;
- i := FColorMap.Count;
- while (i > 0) do
- begin
- t := ColorMap^[0].Red;
- ColorMap^[0].Red := ColorMap^[0].Blue;
- ColorMap^[0].Blue := t;
- inc(integer(ColorMap), sizeof(TGIFColor));
- dec(i);
- end;
- end;
- procedure ImportViaDraw(ABitmap: TBitmap; Graphic: TGraphic);
- begin
- ABitmap.Height := Graphic.Height;
- ABitmap.Width := Graphic.Width;
- // Note: Disable the call to SafeSetPixelFormat below to import
- // in max number of colors with the risk of having to use
- // TCanvas.Pixels to do it (very slow).
- // Make things a little easier for TGIFSubImage.Assign by converting
- // pfDevice to a more import friendly format
- {$ifdef SLOW_BUT_SAFE}
- SafeSetPixelFormat(ABitmap, pf8bit);
- {$else}
- {$ifndef VER9x}
- SetPixelFormat(ABitmap, pf24bit);
- {$endif}
- {$endif}
- ABitmap.Canvas.Draw(0, 0, Graphic);
- end;
- procedure AddMask(Mask: TBitmap);
- var
- DIBReader : TDIBReader;
- TransparentIndex : integer;
- i ,
- j : integer;
- GIFPixel ,
- MaskPixel : PChar;
- WasTransparent : boolean;
- GCE : TGIFGraphicControlExtension;
- begin
- // Optimize colormap to make room for transparent color
- ColorMap.Optimize;
- // Can't make transparent if no color or colormap full
- if (ColorMap.Count = 0) or (ColorMap.Count = 256) then
- exit;
- // Add the transparent color to the color map
- TransparentIndex := ColorMap.Add(TColor(0));
- WasTransparent := False;
- DIBReader := TDIBReader.Create(Mask, pf8bit);
- try
- for i := 0 to Height-1 do
- begin
- MaskPixel := DIBReader.Scanline[i];
- GIFPixel := Scanline[i];
- for j := 0 to Width-1 do
- begin
- // Change all unmasked pixels to transparent
- if (MaskPixel^ <> #0) then
- begin
- GIFPixel^ := chr(TransparentIndex);
- WasTransparent := True;
- end;
- inc(MaskPixel);
- inc(GIFPixel);
- end;
- end;
- finally
- DIBReader.Free;
- end;
- // Add a Graphic Control Extension if any part of the mask was transparent
- if (WasTransparent) then
- begin
- GCE := TGIFGraphicControlExtension.Create(self);
- GCE.Transparent := True;
- GCE.TransparentColorIndex := TransparentIndex;
- Extensions.Add(GCE);
- end else
- // Otherwise removed the transparency color since it wasn't used
- ColorMap.Delete(TransparentIndex);
- end;
- procedure AddMaskOnly(hMask: hBitmap);
- var
- Mask : TBitmap;
- begin
- if (hMask = 0) then
- exit;
- // Encapsulate the mask
- Mask := TBitmap.Create;
- try
- // Mask.Handle := hMask; // 2003.08.04
- Mask.Handle := Windows.CopyImage(hMask, IMAGE_BITMAP, 0, 0, LR_COPYRETURNORG); // 2003.08.04
- AddMask(Mask);
- finally
- // Mask.ReleaseHandle; // 2003.08.04
- Mask.Free;
- end;
- end;
- procedure AddIconMask(Icon: TIcon);
- var
- IconInfo : TIconInfo;
- begin
- if (not GetIconInfo(Icon.Handle, IconInfo)) then
- exit;
- // Extract the icon mask
- AddMaskOnly(IconInfo.hbmMask);
- end;
- procedure AddMetafileMask(Metafile: TMetaFile);
- var
- Mask1 ,
- Mask2 : TBitmap;
- procedure DrawMetafile(ABitmap: TBitmap; Background: TColor);
- begin
- ABitmap.Width := Metafile.Width;
- ABitmap.Height := Metafile.Height;
- {$ifndef VER9x}
- SetPixelFormat(ABitmap, pf24bit);
- {$endif}
- ABitmap.Canvas.Brush.Color := Background;
- ABitmap.Canvas.Brush.Style := bsSolid;
- ABitmap.Canvas.FillRect(ABitmap.Canvas.ClipRect);
- ABitmap.Canvas.Draw(0,0, Metafile);
- end;
- begin
- // Create the metafile mask
- Mask1 := TBitmap.Create;
- try
- Mask2 := TBitmap.Create;
- try
- DrawMetafile(Mask1, clWhite);
- DrawMetafile(Mask2, clBlack);
- Mask1.Canvas.CopyMode := cmSrcInvert;
- Mask1.Canvas.Draw(0,0, Mask2);
- AddMask(Mask1);
- finally
- Mask2.Free;
- end;
- finally
- Mask1.Free;
- end;
- end;
- begin
- if (Source = self) then
- exit;
- if (Source = nil) then
- begin
- Clear;
- end else
- //
- // TGIFSubImage import
- //
- if (Source is TGIFSubImage) then
- begin
- // Zap existing colormap, extensions and bitmap
- Clear;
- if (TGIFSubImage(Source).Empty) then
- exit;
- // Copy source data
- FImageDescriptor := TGIFSubImage(Source).FImageDescriptor;
- FTransparent := TGIFSubImage(Source).Transparent;
- // Copy image data
- NewImage;
- if (FData <> nil) and (TGIFSubImage(Source).Data <> nil) then
- Move(TGIFSubImage(Source).Data^, FData^, FDataSize);
- // Copy palette
- FColorMap.Assign(TGIFSubImage(Source).ColorMap);
- // Copy extensions
- if (TGIFSubImage(Source).Extensions.Count > 0) then
- begin
- MemoryStream := TMemoryStream.Create;
- try
- TGIFSubImage(Source).Extensions.SaveToStream(MemoryStream);
- MemoryStream.Seek(0, soFromBeginning);
- Extensions.LoadFromStream(MemoryStream, Self);
- finally
- MemoryStream.Free;
- end;
- end;
- // Copy bitmap representation
- // (Not really nescessary but improves performance if the bitmap is needed
- // later on)
- if (TGIFSubImage(Source).HasBitmap) then
- begin
- NewBitmap;
- FBitmap.Assign(TGIFSubImage(Source).Bitmap);
- end;
- end else
- //
- // Bitmap import
- //
- if (Source is TBitmap) then
- begin
- // Zap existing colormap, extensions and bitmap
- Clear;
- if (TBitmap(Source).Empty) then
- exit;
- Width := TBitmap(Source).Width;
- Height := TBitmap(Source).Height;
- PixelFormat := GetPixelFormat(TBitmap(Source));
- {$ifdef VER9x}
- // Delphi 2 TBitmaps are always DDBs. This means that if a 24 bit
- // bitmap is loaded in 8 bit device mode, TBitmap.PixelFormat will
- // be pf8bit, but TBitmap.Palette will be 0!
- if (TBitmap(Source).Palette = 0) then
- PixelFormat := pfDevice;
- {$endif}
- if (PixelFormat > pf8bit) or (PixelFormat = pfDevice) then
- begin
- // Convert image to 8 bits/pixel or less
- FBitmap := ReduceColors(TBitmap(Source), Image.ColorReduction,
- Image.DitherMode, Image.ReductionBits, 0);
- PixelFormat := GetPixelFormat(FBitmap);
- end else
- begin
- // Create new bitmap and copy
- NewBitmap;
- FBitmap.Assign(TBitmap(Source));
- end;
- // Allocate new buffer
- NewImage;
- Image.Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressConverting);
- try
- {$ifdef VER9x}
- // This shouldn't happen, but better safe...
- if (FBitmap.Palette = 0) then
- PixelFormat := pf24bit;
- {$endif}
- if (not(PixelFormat in [pf1bit, pf4bit, pf8bit, pf24bit])) then
- PixelFormat := pf24bit;
- DIBSource := TDIBReader.Create(FBitmap, PixelFormat);
- try
- // Copy pixels
- case (PixelFormat) of
- pf8bit: Import8Bit(Fdata);
- pf4bit: Import4Bit(Fdata);
- pf1bit: Import1Bit(Fdata);
- else
- // Error(sUnsupportedBitmap);
- Import24Bit(Fdata);
- end;
- finally
- DIBSource.Free;
- end;
- {$ifdef VER10_PLUS}
- // Add mask for transparent bitmaps
- if (TBitmap(Source).Transparent) then
- AddMaskOnly(TBitmap(Source).MaskHandle);
- {$endif}
- finally
- if ExceptObject = nil then
- i := 100
- else
- i := 0;
- Image.Progress(Self, psEnding, i, Image.PaletteModified, Rect(0,0,0,0), sProgressConverting);
- end;
- end else
- //
- // TGraphic import
- //
- if (Source is TGraphic) then
- begin
- // Zap existing colormap, extensions and bitmap
- Clear;
- if (TGraphic(Source).Empty) then
- exit;
- ABitmap := TBitmap.Create;
- try
- // Import TIcon and TMetafile by drawing them onto a bitmap...
- // ...and then importing the bitmap recursively
- if (Source is TIcon) or (Source is TMetafile) then
- begin
- try
- ImportViaDraw(ABitmap, TGraphic(Source))
- except
- // If import via TCanvas.Draw fails (which it shouldn't), we try the
- // Assign mechanism instead
- ABitmap.Assign(Source);
- end;
- end else
- try
- ABitmap.Assign(Source);
- except
- // If automatic conversion to bitmap fails, we try and draw the
- // graphic on the bitmap instead
- ImportViaDraw(ABitmap, TGraphic(Source));
- end;
- // Convert the bitmap to a GIF frame recursively
- Assign(ABitmap);
- finally
- ABitmap.Free;
- end;
- // Import transparency mask
- if (Source is TIcon) then
- AddIconMask(TIcon(Source));
- if (Source is TMetaFile) then
- AddMetafileMask(TMetaFile(Source));
- end else
- //
- // TPicture import
- //
- if (Source is TPicture) then
- begin
- // Recursively import TGraphic
- Assign(TPicture(Source).Graphic);
- end else
- // Unsupported format - fall back to Source.AssignTo
- inherited Assign(Source);
- end;
- // Copied from D3 graphics.pas
- // Fixed by Brian Lowe of Acro Technology Inc. 30Jan98
- function TransparentStretchBlt(DstDC: HDC; DstX, DstY, DstW, DstH: Integer;
- SrcDC: HDC; SrcX, SrcY, SrcW, SrcH: Integer; MaskDC: HDC; MaskX,
- MaskY: Integer): Boolean;
- const
- ROP_DstCopy = $00AA0029;
- var
- MemDC ,
- OrMaskDC : HDC;
- MemBmp ,
- OrMaskBmp : HBITMAP;
- Save ,
- OrMaskSave : THandle;
- crText, crBack : TColorRef;
- SavePal : HPALETTE;
- begin
- Result := True;
- if (Win32Platform = VER_PLATFORM_WIN32_NT) and (SrcW = DstW) and (SrcH = DstH) then
- begin
- MemBmp := GDICheck(CreateCompatibleBitmap(SrcDC, 1, 1));
- MemBmp := SelectObject(MaskDC, MemBmp);
- try
- MaskBlt(DstDC, DstX, DstY, DstW, DstH, SrcDC, SrcX, SrcY, MemBmp, MaskX,
- MaskY, MakeRop4(ROP_DstCopy, SrcCopy));
- finally
- MemBmp := SelectObject(MaskDC, MemBmp);
- DeleteObject(MemBmp);
- end;
- Exit;
- end;
- SavePal := 0;
- MemDC := GDICheck(CreateCompatibleDC(DstDC));
- try
- { Color bitmap for combining OR mask with source bitmap }
- MemBmp := GDICheck(CreateCompatibleBitmap(DstDC, SrcW, SrcH));
- try
- Save := SelectObject(MemDC, MemBmp);
- try
- { This bitmap needs the size of the source but DC of the dest }
- OrMaskDC := GDICheck(CreateCompatibleDC(DstDC));
- try
- { Need a monochrome bitmap for OR mask!! }
- OrMaskBmp := GDICheck(CreateBitmap(SrcW, SrcH, 1, 1, nil));
- try
- OrMaskSave := SelectObject(OrMaskDC, OrMaskBmp);
- try
- // OrMask := 1
- // Original: BitBlt(OrMaskDC, SrcX, SrcY, SrcW, SrcH, OrMaskDC, SrcX, SrcY, WHITENESS);
- // Replacement, but not needed: PatBlt(OrMaskDC, SrcX, SrcY, SrcW, SrcH, WHITENESS);
- // OrMask := OrMask XOR Mask
- // Not needed: BitBlt(OrMaskDC, SrcX, SrcY, SrcW, SrcH, MaskDC, SrcX, SrcY, SrcInvert);
- // OrMask := NOT Mask
- BitBlt(OrMaskDC, SrcX, SrcY, SrcW, SrcH, MaskDC, SrcX, SrcY, NotSrcCopy);
- // Retrieve source palette (with dummy select)
- SavePal := SelectPalette(SrcDC, SystemPalette16, False);
- // Restore source palette
- SelectPalette(SrcDC, SavePal, False);
- // Select source palette into memory buffer
- if SavePal <> 0 then
- SavePal := SelectPalette(MemDC, SavePal, True)
- else
- SavePal := SelectPalette(MemDC, SystemPalette16, True);
- RealizePalette(MemDC);
- // Mem := OrMask
- BitBlt(MemDC, SrcX, SrcY, SrcW, SrcH, OrMaskDC, SrcX, SrcY, SrcCopy);
- // Mem := Mem AND Src
- {$IFNDEF GIF_TESTMASK} // Define GIF_TESTMASK if you want to know what it does...
- BitBlt(MemDC, SrcX, SrcY, SrcW, SrcH, SrcDC, SrcX, SrcY, SrcAnd);
- {$ELSE}
- StretchBlt(DstDC, DstX, DstY, DstW DIV 2, DstH, MemDC, SrcX, SrcY, SrcW, SrcH, SrcCopy);
- StretchBlt(DstDC, DstX+DstW DIV 2, DstY, DstW DIV 2, DstH, SrcDC, SrcX, SrcY, SrcW, SrcH, SrcCopy);
- exit;
- {$ENDIF}
- finally
- if (OrMaskSave <> 0) then
- SelectObject(OrMaskDC, OrMaskSave);
- end;
- finally
- DeleteObject(OrMaskBmp);
- end;
- finally
- DeleteDC(OrMaskDC);
- end;
- crText := SetTextColor(DstDC, $00000000);
- crBack := SetBkColor(DstDC, $00FFFFFF);
- { All color rendering is done at 1X (no stretching),
- then final 2 masks are stretched to dest DC }
- // Neat trick!
- // Dst := Dst AND Mask
- StretchBlt(DstDC, DstX, DstY, DstW, DstH, MaskDC, SrcX, SrcY, SrcW, SrcH, SrcAnd);
- // Dst := Dst OR Mem
- StretchBlt(DstDC, DstX, DstY, DstW, DstH, MemDC, SrcX, SrcY, SrcW, SrcH, SrcPaint);
- SetTextColor(DstDC, crText);
- SetTextColor(DstDC, crBack);
- finally
- if (Save <> 0) then
- SelectObject(MemDC, Save);
- end;
- finally
- DeleteObject(MemBmp);
- end;
- finally
- if (SavePal <> 0) then
- SelectPalette(MemDC, SavePal, False);
- DeleteDC(MemDC);
- end;
- end;
- procedure TGIFSubImage.Draw(ACanvas: TCanvas; const Rect: TRect;
- DoTransparent, DoTile: boolean);
- begin
- if (DoTile) then
- StretchDraw(ACanvas, Rect, DoTransparent, DoTile)
- else
- StretchDraw(ACanvas, ScaleRect(Rect), DoTransparent, DoTile);
- end;
- type
- // Dummy class used to gain access to protected method TCanvas.Changed
- TChangableCanvas = class(TCanvas)
- end;
- procedure TGIFSubImage.StretchDraw(ACanvas: TCanvas; const Rect: TRect;
- DoTransparent, DoTile: boolean);
- var
- MaskDC : HDC;
- Save : THandle;
- Tile : TRect;
- {$ifdef DEBUG_DRAWPERFORMANCE}
- ImageCount ,
- TimeStart ,
- TimeStop : DWORD;
- {$endif}
- begin
- {$ifdef DEBUG_DRAWPERFORMANCE}
- TimeStart := timeGetTime;
- ImageCount := 0;
- {$endif}
- if (DoTransparent) and (Transparent) and (HasMask) then
- begin
- // Draw transparent using mask
- Save := 0;
- MaskDC := 0;
- try
- MaskDC := GDICheck(CreateCompatibleDC(0));
- Save := SelectObject(MaskDC, FMask);
- if (DoTile) then
- begin
- Tile.Left := Rect.Left+Left;
- Tile.Right := Tile.Left + Width;
- while (Tile.Left < Rect.Right) do
- begin
- Tile.Top := Rect.Top+Top;
- Tile.Bottom := Tile.Top + Height;
- while (Tile.Top < Rect.Bottom) do
- begin
- TransparentStretchBlt(ACanvas.Handle, Tile.Left, Tile.Top, Width, Height,
- Bitmap.Canvas.Handle, 0, 0, Width, Height, MaskDC, 0, 0);
- Tile.Top := Tile.Top + Image.Height;
- Tile.Bottom := Tile.Bottom + Image.Height;
- {$ifdef DEBUG_DRAWPERFORMANCE}
- inc(ImageCount);
- {$endif}
- end;
- Tile.Left := Tile.Left + Image.Width;
- Tile.Right := Tile.Right + Image.Width;
- end;
- end else
- TransparentStretchBlt(ACanvas.Handle, Rect.Left, Rect.Top,
- Rect.Right - Rect.Left, Rect.Bottom - Rect.Top,
- Bitmap.Canvas.Handle, 0, 0, Width, Height, MaskDC, 0, 0);
- // Since we are not using any of the TCanvas functions (only handle)
- // we need to fire the TCanvas.Changed method "manually".
- TChangableCanvas(ACanvas).Changed;
- finally
- if (Save <> 0) then
- SelectObject(MaskDC, Save);
- if (MaskDC <> 0) then
- DeleteDC(MaskDC);
- end;
- end else
- begin
- if (DoTile) then
- begin
- Tile.Left := Rect.Left+Left;
- Tile.Right := Tile.Left + Width;
- while (Tile.Left < Rect.Right) do
- begin
- Tile.Top := Rect.Top+Top;
- Tile.Bottom := Tile.Top + Height;
- while (Tile.Top < Rect.Bottom) do
- begin
- ACanvas.StretchDraw(Tile, Bitmap);
- Tile.Top := Tile.Top + Image.Height;
- Tile.Bottom := Tile.Bottom + Image.Height;
- {$ifdef DEBUG_DRAWPERFORMANCE}
- inc(ImageCount);
- {$endif}
- end;
- Tile.Left := Tile.Left + Image.Width;
- Tile.Right := Tile.Right + Image.Width;
- end;
- end else
- ACanvas.StretchDraw(Rect, Bitmap);
- end;
- {$ifdef DEBUG_DRAWPERFORMANCE}
- if (GetAsyncKeyState(VK_CONTROL) <> 0) then
- begin
- TimeStop := timeGetTime;
- ShowMessage(format('Draw %d images in %d mS, Rate %d images/mS (%d images/S)',
- [ImageCount, TimeStop-TimeStart,
- ImageCount DIV (TimeStop-TimeStart+1),
- MulDiv(ImageCount, 1000, TimeStop-TimeStart+1)]));
- end;
- {$endif}
- end;
- // Given a destination rect (DestRect) calculates the
- // area covered by this sub image
- function TGIFSubImage.ScaleRect(DestRect: TRect): TRect;
- var
- HeightMul ,
- HeightDiv : integer;
- WidthMul ,
- WidthDiv : integer;
- begin
- HeightDiv := Image.Height;
- HeightMul := DestRect.Bottom-DestRect.Top;
- WidthDiv := Image.Width;
- WidthMul := DestRect.Right-DestRect.Left;
- Result.Left := DestRect.Left + muldiv(Left, WidthMul, WidthDiv);
- Result.Top := DestRect.Top + muldiv(Top, HeightMul, HeightDiv);
- Result.Right := DestRect.Left + muldiv(Left+Width, WidthMul, WidthDiv);
- Result.Bottom := DestRect.Top + muldiv(Top+Height, HeightMul, HeightDiv);
- end;
- procedure TGIFSubImage.Crop;
- var
- TransparentColorIndex : byte;
- CropLeft ,
- CropTop ,
- CropRight ,
- CropBottom : integer;
- WasTransparent : boolean;
- i : integer;
- NewSize : integer;
- NewData : PChar;
- NewWidth ,
- NewHeight : integer;
- pSource ,
- pDest : PChar;
- begin
- if (Empty) or (not Transparent) then
- exit;
- TransparentColorIndex := GraphicControlExtension.TransparentColorIndex;
- CropLeft := 0;
- CropRight := Width - 1;
- CropTop := 0;
- CropBottom := Height - 1;
- // Find left edge
- WasTransparent := True;
- while (CropLeft <= CropRight) and (WasTransparent) do
- begin
- for i := CropTop to CropBottom do
- if (Pixels[CropLeft, i] <> TransparentColorIndex) then
- begin
- WasTransparent := False;
- break;
- end;
- if (WasTransparent) then
- inc(CropLeft);
- end;
- // Find right edge
- WasTransparent := True;
- while (CropLeft <= CropRight) and (WasTransparent) do
- begin
- for i := CropTop to CropBottom do
- if (pixels[CropRight, i] <> TransparentColorIndex) then
- begin
- WasTransparent := False;
- break;
- end;
- if (WasTransparent) then
- dec(CropRight);
- end;
- if (CropLeft <= CropRight) then
- begin
- // Find top edge
- WasTransparent := True;
- while (CropTop <= CropBottom) and (WasTransparent) do
- begin
- for i := CropLeft to CropRight do
- if (pixels[i, CropTop] <> TransparentColorIndex) then
- begin
- WasTransparent := False;
- break;
- end;
- if (WasTransparent) then
- inc(CropTop);
- end;
- // Find bottom edge
- WasTransparent := True;
- while (CropTop <= CropBottom) and (WasTransparent) do
- begin
- for i := CropLeft to CropRight do
- if (pixels[i, CropBottom] <> TransparentColorIndex) then
- begin
- WasTransparent := False;
- break;
- end;
- if (WasTransparent) then
- dec(CropBottom);
- end;
- end;
- if (CropLeft > CropRight) or (CropTop > CropBottom) then
- begin
- // Cropped to nothing - frame is invisible
- Clear;
- end else
- begin
- // Crop frame - move data
- NewWidth := CropRight - CropLeft + 1;
- Newheight := CropBottom - CropTop + 1;
- NewSize := NewWidth * NewHeight;
- GetMem(NewData, NewSize);
- pSource := PChar(integer(FData) + CropTop * Width + CropLeft);
- pDest := NewData;
- for i := 0 to NewHeight-1 do
- begin
- Move(pSource^, pDest^, NewWidth);
- inc(pSource, Width);
- inc(pDest, NewWidth);
- end;
- FreeImage;
- FData := NewData;
- FDataSize := NewSize;
- inc(FImageDescriptor.Left, CropLeft);
- inc(FImageDescriptor.Top, CropTop);
- FImageDescriptor.Width := NewWidth;
- FImageDescriptor.Height := NewHeight;
- FreeBitmap;
- FreeMask
- end;
- end;
- procedure TGIFSubImage.Merge(Previous: TGIFSubImage);
- var
- SourceIndex ,
- DestIndex : byte;
- SourceTransparent : boolean;
- NeedTransparentColorIndex: boolean;
- PreviousRect ,
- ThisRect ,
- MergeRect : TRect;
- PreviousY ,
- X ,
- Y : integer;
- pSource ,
- pDest : PChar;
- pSourceMap ,
- pDestMap : PColorMap;
- GCE : TGIFGraphicControlExtension;
- function CanMakeTransparent: boolean;
- begin
- // Is there a local color map...
- if (ColorMap.Count > 0) then
- // ...and is there room in it?
- Result := (ColorMap.Count < 256)
- // Is there a global color map...
- else if (Image.GlobalColorMap.Count > 0) then
- // ...and is there room in it?
- Result := (Image.GlobalColorMap.Count < 256)
- else
- Result := False;
- end;
- function GetTransparentColorIndex: byte;
- var
- i : integer;
- begin
- if (ColorMap.Count > 0) then
- begin
- // Get the transparent color from the local color map
- Result := ColorMap.Add(TColor(0));
- end else
- begin
- // Are any other frames using the global color map for transparency
- for i := 0 to Image.Images.Count-1 do
- if (Image.Images[i] <> self) and (Image.Images[i].Transparent) and
- (Image.Images[i].ColorMap.Count = 0) then
- begin
- // Use the same transparency color as the other frame
- Result := Image.Images[i].GraphicControlExtension.TransparentColorIndex;
- exit;
- end;
- // Get the transparent color from the global color map
- Result := Image.GlobalColorMap.Add(TColor(0));
- end;
- end;
- begin
- // Determine if it is possible to merge this frame
- if (Empty) or (Previous = nil) or (Previous.Empty) or
- ((Previous.GraphicControlExtension <> nil) and
- (Previous.GraphicControlExtension.Disposal in [dmBackground, dmPrevious])) then
- exit;
- PreviousRect := Previous.BoundsRect;
- ThisRect := BoundsRect;
- // Cannot merge unless the frames intersect
- if (not IntersectRect(MergeRect, PreviousRect, ThisRect)) then
- exit;
- // If the frame isn't already transparent, determine
- // if it is possible to make it so
- if (Transparent) then
- begin
- DestIndex := GraphicControlExtension.TransparentColorIndex;
- NeedTransparentColorIndex := False;
- end else
- begin
- if (not CanMakeTransparent) then
- exit;
- DestIndex := 0; // To avoid compiler warning
- NeedTransparentColorIndex := True;
- end;
- SourceTransparent := Previous.Transparent;
- if (SourceTransparent) then
- SourceIndex := Previous.GraphicControlExtension.TransparentColorIndex
- else
- SourceIndex := 0; // To avoid compiler warning
- PreviousY := MergeRect.Top - Previous.Top;
- pSourceMap := Previous.ActiveColorMap.Data;
- pDestMap := ActiveColorMap.Data;
- for Y := MergeRect.Top - Top to MergeRect.Bottom - Top-1 do
- begin
- pSource := PChar(integer(Previous.Scanline[PreviousY]) + MergeRect.Left - Previous.Left);
- pDest := PChar(integer(Scanline[Y]) + MergeRect.Left - Left);
- for X := MergeRect.Left to MergeRect.Right-1 do
- begin
- // Ignore pixels if either this frame's or the previous frame's pixel is transparent
- if (
- not(
- ((not NeedTransparentColorIndex) and (pDest^ = char(DestIndex))) or
- ((SourceTransparent) and (pSource^ = char(SourceIndex)))
- )
- ) and (
- // Replace same colored pixels with transparency
- ((pDestMap = pSourceMap) and (pDest^ = pSource^)) or
- (CompareMem(@(pDestMap^[ord(pDest^)]), @(pSourceMap^[ord(pSource^)]), sizeof(TGIFColor)))
- ) then
- begin
- if (NeedTransparentColorIndex) then
- begin
- NeedTransparentColorIndex := False;
- DestIndex := GetTransparentColorIndex;
- end;
- pDest^ := char(DestIndex);
- end;
- inc(pDest);
- inc(pSource);
- end;
- inc(PreviousY);
- end;
- (*
- ** Create a GCE if the frame wasn't already transparent and any
- ** pixels were made transparent
- *)
- if (not Transparent) and (not NeedTransparentColorIndex) then
- begin
- if (GraphicControlExtension = nil) then
- begin
- GCE := TGIFGraphicControlExtension.Create(self);
- Extensions.Add(GCE);
- end else
- GCE := GraphicControlExtension;
- GCE.Transparent := True;
- GCE.TransparentColorIndex := DestIndex;
- end;
- FreeBitmap;
- FreeMask
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFTrailer
- //
- ////////////////////////////////////////////////////////////////////////////////
- procedure TGIFTrailer.SaveToStream(Stream: TStream);
- begin
- WriteByte(Stream, bsTrailer);
- end;
- procedure TGIFTrailer.LoadFromStream(Stream: TStream);
- var
- b : BYTE;
- begin
- if (Stream.Read(b, 1) <> 1) then
- exit;
- if (b <> bsTrailer) then
- Warning(gsWarning, sBadTrailer);
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFExtension registration database
- //
- ////////////////////////////////////////////////////////////////////////////////
- type
- TExtensionLeadIn = packed record
- Introducer: byte; { always $21 }
- ExtensionLabel: byte;
- end;
- PExtRec = ^TExtRec;
- TExtRec = record
- ExtClass: TGIFExtensionClass;
- ExtLabel: BYTE;
- end;
- TExtensionList = class(TList)
- public
- constructor Create;
- destructor Destroy; override;
- procedure Add(eLabel: BYTE; eClass: TGIFExtensionClass);
- function FindExt(eLabel: BYTE): TGIFExtensionClass;
- procedure Remove(eClass: TGIFExtensionClass);
- end;
- constructor TExtensionList.Create;
- begin
- inherited Create;
- Add(bsPlainTextExtension, TGIFTextExtension);
- Add(bsGraphicControlExtension, TGIFGraphicControlExtension);
- Add(bsCommentExtension, TGIFCommentExtension);
- Add(bsApplicationExtension, TGIFApplicationExtension);
- end;
- destructor TExtensionList.Destroy;
- var
- I: Integer;
- begin
- for I := 0 to Count-1 do
- Dispose(PExtRec(Items[I]));
- inherited Destroy;
- end;
- procedure TExtensionList.Add(eLabel: BYTE; eClass: TGIFExtensionClass);
- var
- NewRec: PExtRec;
- begin
- New(NewRec);
- with NewRec^ do
- begin
- ExtLabel := eLabel;
- ExtClass := eClass;
- end;
- inherited Add(NewRec);
- end;
- function TExtensionList.FindExt(eLabel: BYTE): TGIFExtensionClass;
- var
- I: Integer;
- begin
- for I := Count-1 downto 0 do
- with PExtRec(Items[I])^ do
- if ExtLabel = eLabel then
- begin
- Result := ExtClass;
- Exit;
- end;
- Result := nil;
- end;
- procedure TExtensionList.Remove(eClass: TGIFExtensionClass);
- var
- I: Integer;
- P: PExtRec;
- begin
- for I := Count-1 downto 0 do
- begin
- P := PExtRec(Items[I]);
- if P^.ExtClass.InheritsFrom(eClass) then
- begin
- Dispose(P);
- Delete(I);
- end;
- end;
- end;
- var
- ExtensionList: TExtensionList = nil;
- function GetExtensionList: TExtensionList;
- begin
- if (ExtensionList = nil) then
- ExtensionList := TExtensionList.Create;
- Result := ExtensionList;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFExtension
- //
- ////////////////////////////////////////////////////////////////////////////////
- function TGIFExtension.GetVersion: TGIFVersion;
- begin
- Result := gv89a;
- end;
- class procedure TGIFExtension.RegisterExtension(eLabel: BYTE; eClass: TGIFExtensionClass);
- begin
- GetExtensionList.Add(eLabel, eClass);
- end;
- class function TGIFExtension.FindExtension(Stream: TStream): TGIFExtensionClass;
- var
- eLabel : BYTE;
- SubClass : TGIFExtensionClass;
- Pos : LongInt;
- begin
- Pos := Stream.Position;
- if (Stream.Read(eLabel, 1) <> 1) then
- begin
- Result := nil;
- exit;
- end;
- Result := GetExtensionList.FindExt(eLabel);
- while (Result <> nil) do
- begin
- SubClass := Result.FindSubExtension(Stream);
- if (SubClass = Result) then
- break;
- Result := SubClass;
- end;
- Stream.Position := Pos;
- end;
- class function TGIFExtension.FindSubExtension(Stream: TStream): TGIFExtensionClass;
- begin
- Result := self;
- end;
- constructor TGIFExtension.Create(ASubImage: TGIFSubImage);
- begin
- inherited Create(ASubImage.Image);
- FSubImage := ASubImage;
- end;
- destructor TGIFExtension.Destroy;
- begin
- if (FSubImage <> nil) then
- FSubImage.Extensions.Remove(self);
- inherited Destroy;
- end;
- procedure TGIFExtension.SaveToStream(Stream: TStream);
- var
- ExtensionLeadIn : TExtensionLeadIn;
- begin
- ExtensionLeadIn.Introducer := bsExtensionIntroducer;
- ExtensionLeadIn.ExtensionLabel := ExtensionType;
- Stream.Write(ExtensionLeadIn, sizeof(ExtensionLeadIn));
- end;
- function TGIFExtension.DoReadFromStream(Stream: TStream): TGIFExtensionType;
- var
- ExtensionLeadIn : TExtensionLeadIn;
- begin
- ReadCheck(Stream, ExtensionLeadIn, sizeof(ExtensionLeadIn));
- if (ExtensionLeadIn.Introducer <> bsExtensionIntroducer) then
- Error(sBadExtensionLabel);
- Result := ExtensionLeadIn.ExtensionLabel;
- end;
- procedure TGIFExtension.LoadFromStream(Stream: TStream);
- begin
- // Seek past lead-in
- // Stream.Seek(sizeof(TExtensionLeadIn), soFromCurrent);
- if (DoReadFromStream(Stream) <> ExtensionType) then
- Error(sBadExtensionInstance);
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFGraphicControlExtension
- //
- ////////////////////////////////////////////////////////////////////////////////
- const
- { Extension flag bit masks }
- efInputFlag = $02; { 00000010 }
- efDisposal = $1C; { 00011100 }
- efTransparent = $01; { 00000001 }
- efReserved = $E0; { 11100000 }
- constructor TGIFGraphicControlExtension.Create(ASubImage: TGIFSubImage);
- begin
- inherited Create(ASubImage);
- FGCExtension.BlockSize := 4;
- FGCExtension.PackedFields := $00;
- FGCExtension.DelayTime := 0;
- FGCExtension.TransparentColorIndex := 0;
- FGCExtension.Terminator := 0;
- if (ASubImage.FGCE = nil) then
- ASubImage.FGCE := self;
- end;
- destructor TGIFGraphicControlExtension.Destroy;
- begin
- // Clear transparent flag in sub image
- if (Transparent) then
- SubImage.FTransparent := False;
- if (SubImage.FGCE = self) then
- SubImage.FGCE := nil;
- inherited Destroy;
- end;
- function TGIFGraphicControlExtension.GetExtensionType: TGIFExtensionType;
- begin
- Result := bsGraphicControlExtension;
- end;
- function TGIFGraphicControlExtension.GetTransparent: boolean;
- begin
- Result := (FGCExtension.PackedFields AND efTransparent) <> 0;
- end;
- procedure TGIFGraphicControlExtension.SetTransparent(Value: boolean);
- begin
- // Set transparent flag in sub image
- SubImage.FTransparent := Value;
- if (Value) then
- FGCExtension.PackedFields := FGCExtension.PackedFields OR efTransparent
- else
- FGCExtension.PackedFields := FGCExtension.PackedFields AND NOT(efTransparent);
- end;
- function TGIFGraphicControlExtension.GetTransparentColor: TColor;
- begin
- Result := SubImage.ActiveColorMap[TransparentColorIndex];
- end;
- procedure TGIFGraphicControlExtension.SetTransparentColor(Color: TColor);
- begin
- FGCExtension.TransparentColorIndex := Subimage.ActiveColorMap.AddUnique(Color);
- end;
- function TGIFGraphicControlExtension.GetTransparentColorIndex: BYTE;
- begin
- Result := FGCExtension.TransparentColorIndex;
- end;
- procedure TGIFGraphicControlExtension.SetTransparentColorIndex(Value: BYTE);
- begin
- if ((Value >= SubImage.ActiveColorMap.Count) and (SubImage.ActiveColorMap.Count > 0)) then
- begin
- Warning(gsWarning, sBadColorIndex);
- Value := 0;
- end;
- FGCExtension.TransparentColorIndex := Value;
- end;
- function TGIFGraphicControlExtension.GetDelay: WORD;
- begin
- Result := FGCExtension.DelayTime;
- end;
- procedure TGIFGraphicControlExtension.SetDelay(Value: WORD);
- begin
- FGCExtension.DelayTime := Value;
- end;
- function TGIFGraphicControlExtension.GetUserInput: boolean;
- begin
- Result := (FGCExtension.PackedFields AND efInputFlag) <> 0;
- end;
- procedure TGIFGraphicControlExtension.SetUserInput(Value: boolean);
- begin
- if (Value) then
- FGCExtension.PackedFields := FGCExtension.PackedFields OR efInputFlag
- else
- FGCExtension.PackedFields := FGCExtension.PackedFields AND NOT(efInputFlag);
- end;
- function TGIFGraphicControlExtension.GetDisposal: TDisposalMethod;
- begin
- Result := TDisposalMethod((FGCExtension.PackedFields AND efDisposal) SHR 2);
- end;
- procedure TGIFGraphicControlExtension.SetDisposal(Value: TDisposalMethod);
- begin
- FGCExtension.PackedFields := FGCExtension.PackedFields AND NOT(efDisposal)
- OR ((ord(Value) SHL 2) AND efDisposal);
- end;
- procedure TGIFGraphicControlExtension.SaveToStream(Stream: TStream);
- begin
- inherited SaveToStream(Stream);
- Stream.Write(FGCExtension, sizeof(FGCExtension));
- end;
- procedure TGIFGraphicControlExtension.LoadFromStream(Stream: TStream);
- begin
- inherited LoadFromStream(Stream);
- if (Stream.Read(FGCExtension, sizeof(FGCExtension)) <> sizeof(FGCExtension)) then
- begin
- Warning(gsWarning, sOutOfData);
- exit;
- end;
- // Set transparent flag in sub image
- if (Transparent) then
- SubImage.FTransparent := True;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFTextExtension
- //
- ////////////////////////////////////////////////////////////////////////////////
- constructor TGIFTextExtension.Create(ASubImage: TGIFSubImage);
- begin
- inherited Create(ASubImage);
- FText := TStringList.Create;
- FPlainTextExtension.BlockSize := 12;
- FPlainTextExtension.Left := 0;
- FPlainTextExtension.Top := 0;
- FPlainTextExtension.Width := 0;
- FPlainTextExtension.Height := 0;
- FPlainTextExtension.CellWidth := 0;
- FPlainTextExtension.CellHeight := 0;
- FPlainTextExtension.TextFGColorIndex := 0;
- FPlainTextExtension.TextBGColorIndex := 0;
- end;
- destructor TGIFTextExtension.Destroy;
- begin
- FText.Free;
- inherited Destroy;
- end;
- function TGIFTextExtension.GetExtensionType: TGIFExtensionType;
- begin
- Result := bsPlainTextExtension;
- end;
- function TGIFTextExtension.GetForegroundColor: TColor;
- begin
- Result := SubImage.ColorMap[ForegroundColorIndex];
- end;
- procedure TGIFTextExtension.SetForegroundColor(Color: TColor);
- begin
- ForegroundColorIndex := SubImage.ActiveColorMap.AddUnique(Color);
- end;
- function TGIFTextExtension.GetBackgroundColor: TColor;
- begin
- Result := SubImage.ActiveColorMap[BackgroundColorIndex];
- end;
- procedure TGIFTextExtension.SetBackgroundColor(Color: TColor);
- begin
- BackgroundColorIndex := SubImage.ColorMap.AddUnique(Color);
- end;
- function TGIFTextExtension.GetBounds(Index: integer): WORD;
- begin
- case (Index) of
- 1: Result := FPlainTextExtension.Left;
- 2: Result := FPlainTextExtension.Top;
- 3: Result := FPlainTextExtension.Width;
- 4: Result := FPlainTextExtension.Height;
- else
- Result := 0; // To avoid compiler warnings
- end;
- end;
- procedure TGIFTextExtension.SetBounds(Index: integer; Value: WORD);
- begin
- case (Index) of
- 1: FPlainTextExtension.Left := Value;
- 2: FPlainTextExtension.Top := Value;
- 3: FPlainTextExtension.Width := Value;
- 4: FPlainTextExtension.Height := Value;
- end;
- end;
- function TGIFTextExtension.GetCharWidthHeight(Index: integer): BYTE;
- begin
- case (Index) of
- 1: Result := FPlainTextExtension.CellWidth;
- 2: Result := FPlainTextExtension.CellHeight;
- else
- Result := 0; // To avoid compiler warnings
- end;
- end;
- procedure TGIFTextExtension.SetCharWidthHeight(Index: integer; Value: BYTE);
- begin
- case (Index) of
- 1: FPlainTextExtension.CellWidth := Value;
- 2: FPlainTextExtension.CellHeight := Value;
- end;
- end;
- function TGIFTextExtension.GetColorIndex(Index: integer): BYTE;
- begin
- case (Index) of
- 1: Result := FPlainTextExtension.TextFGColorIndex;
- 2: Result := FPlainTextExtension.TextBGColorIndex;
- else
- Result := 0; // To avoid compiler warnings
- end;
- end;
- procedure TGIFTextExtension.SetColorIndex(Index: integer; Value: BYTE);
- begin
- case (Index) of
- 1: FPlainTextExtension.TextFGColorIndex := Value;
- 2: FPlainTextExtension.TextBGColorIndex := Value;
- end;
- end;
- procedure TGIFTextExtension.SaveToStream(Stream: TStream);
- begin
- inherited SaveToStream(Stream);
- Stream.Write(FPlainTextExtension, sizeof(FPlainTextExtension));
- WriteStrings(Stream, FText);
- end;
- procedure TGIFTextExtension.LoadFromStream(Stream: TStream);
- begin
- inherited LoadFromStream(Stream);
- ReadCheck(Stream, FPlainTextExtension, sizeof(FPlainTextExtension));
- ReadStrings(Stream, FText);
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFCommentExtension
- //
- ////////////////////////////////////////////////////////////////////////////////
- constructor TGIFCommentExtension.Create(ASubImage: TGIFSubImage);
- begin
- inherited Create(ASubImage);
- FText := TStringList.Create;
- end;
- destructor TGIFCommentExtension.Destroy;
- begin
- FText.Free;
- inherited Destroy;
- end;
- function TGIFCommentExtension.GetExtensionType: TGIFExtensionType;
- begin
- Result := bsCommentExtension;
- end;
- procedure TGIFCommentExtension.SaveToStream(Stream: TStream);
- begin
- inherited SaveToStream(Stream);
- WriteStrings(Stream, FText);
- end;
- procedure TGIFCommentExtension.LoadFromStream(Stream: TStream);
- begin
- inherited LoadFromStream(Stream);
- ReadStrings(Stream, FText);
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFApplicationExtension registration database
- //
- ////////////////////////////////////////////////////////////////////////////////
- type
- PAppExtRec = ^TAppExtRec;
- TAppExtRec = record
- AppClass: TGIFAppExtensionClass;
- Ident: TGIFApplicationRec;
- end;
- TAppExtensionList = class(TList)
- public
- constructor Create;
- destructor Destroy; override;
- procedure Add(eIdent: TGIFApplicationRec; eClass: TGIFAppExtensionClass);
- function FindExt(eIdent: TGIFApplicationRec): TGIFAppExtensionClass;
- procedure Remove(eClass: TGIFAppExtensionClass);
- end;
- constructor TAppExtensionList.Create;
- const
- NSLoopIdent: array[0..1] of TGIFApplicationRec =
- ((Identifier: 'NETSCAPE'; Authentication: '2.0'),
- (Identifier: 'ANIMEXTS'; Authentication: '1.0'));
- begin
- inherited Create;
- Add(NSLoopIdent[0], TGIFAppExtNSLoop);
- Add(NSLoopIdent[1], TGIFAppExtNSLoop);
- end;
- destructor TAppExtensionList.Destroy;
- var
- I: Integer;
- begin
- for I := 0 to Count-1 do
- Dispose(PAppExtRec(Items[I]));
- inherited Destroy;
- end;
- procedure TAppExtensionList.Add(eIdent: TGIFApplicationRec; eClass: TGIFAppExtensionClass);
- var
- NewRec: PAppExtRec;
- begin
- New(NewRec);
- NewRec^.Ident := eIdent;
- NewRec^.AppClass := eClass;
- inherited Add(NewRec);
- end;
- function TAppExtensionList.FindExt(eIdent: TGIFApplicationRec): TGIFAppExtensionClass;
- var
- I: Integer;
- begin
- for I := Count-1 downto 0 do
- with PAppExtRec(Items[I])^ do
- if CompareMem(@Ident, @eIdent, sizeof(TGIFApplicationRec)) then
- begin
- Result := AppClass;
- Exit;
- end;
- Result := nil;
- end;
- procedure TAppExtensionList.Remove(eClass: TGIFAppExtensionClass);
- var
- I: Integer;
- P: PAppExtRec;
- begin
- for I := Count-1 downto 0 do
- begin
- P := PAppExtRec(Items[I]);
- if P^.AppClass.InheritsFrom(eClass) then
- begin
- Dispose(P);
- Delete(I);
- end;
- end;
- end;
- var
- AppExtensionList: TAppExtensionList = nil;
- function GetAppExtensionList: TAppExtensionList;
- begin
- if (AppExtensionList = nil) then
- AppExtensionList := TAppExtensionList.Create;
- Result := AppExtensionList;
- end;
- class procedure TGIFApplicationExtension.RegisterExtension(eIdent: TGIFApplicationRec;
- eClass: TGIFAppExtensionClass);
- begin
- GetAppExtensionList.Add(eIdent, eClass);
- end;
- class function TGIFApplicationExtension.FindSubExtension(Stream: TStream): TGIFExtensionClass;
- var
- eIdent : TGIFApplicationRec;
- OldPos : longInt;
- Size : BYTE;
- begin
- OldPos := Stream.Position;
- Result := nil;
- if (Stream.Read(Size, 1) <> 1) then
- exit;
- // Some old Adobe export filters mistakenly uses a value of 10
- if (Size = 10) then
- begin
- { TODO -oanme -cImprovement : replace with seek or read and check contents = 'Adobe' }
- if (Stream.Read(eIdent, 10) <> 10) then
- exit;
- Result := TGIFUnknownAppExtension;
- exit;
- end else
- if (Size <> sizeof(TGIFApplicationRec)) or
- (Stream.Read(eIdent, sizeof(eIdent)) <> sizeof(eIdent)) then
- begin
- Stream.Position := OldPos;
- Result := inherited FindSubExtension(Stream);
- end else
- begin
- Result := GetAppExtensionList.FindExt(eIdent);
- if (Result = nil) then
- Result := TGIFUnknownAppExtension;
- end;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFApplicationExtension
- //
- ////////////////////////////////////////////////////////////////////////////////
- constructor TGIFApplicationExtension.Create(ASubImage: TGIFSubImage);
- begin
- inherited Create(ASubImage);
- FillChar(FIdent, sizeof(FIdent), 0);
- end;
- destructor TGIFApplicationExtension.Destroy;
- begin
- inherited Destroy;
- end;
- function TGIFApplicationExtension.GetExtensionType: TGIFExtensionType;
- begin
- Result := bsApplicationExtension;
- end;
- function TGIFApplicationExtension.GetAuthentication: string;
- begin
- Result := FIdent.Authentication;
- end;
- procedure TGIFApplicationExtension.SetAuthentication(const Value: string);
- begin
- if (Length(Value) < sizeof(TGIFAuthenticationCode)) then
- FillChar(FIdent.Authentication, sizeof(TGIFAuthenticationCode), 32);
- StrLCopy(@(FIdent.Authentication[0]), PChar(Value), sizeof(TGIFAuthenticationCode));
- end;
- function TGIFApplicationExtension.GetIdentifier: string;
- begin
- Result := FIdent.Identifier;
- end;
- procedure TGIFApplicationExtension.SetIdentifier(const Value: string);
- begin
- if (Length(Value) < sizeof(TGIFIdentifierCode)) then
- FillChar(FIdent.Identifier, sizeof(TGIFIdentifierCode), 32);
- StrLCopy(@(FIdent.Identifier[0]), PChar(Value), sizeof(TGIFIdentifierCode));
- end;
- procedure TGIFApplicationExtension.SaveToStream(Stream: TStream);
- begin
- inherited SaveToStream(Stream);
- WriteByte(Stream, sizeof(FIdent)); // Block size
- Stream.Write(FIdent, sizeof(FIdent));
- SaveData(Stream);
- end;
- procedure TGIFApplicationExtension.LoadFromStream(Stream: TStream);
- var
- i : integer;
- begin
- inherited LoadFromStream(Stream);
- i := ReadByte(Stream);
- // Some old Adobe export filters mistakenly uses a value of 10
- if (i = 10) then
- FillChar(FIdent, sizeOf(FIdent), 0)
- else
- if (i < 11) then
- Error(sBadBlockSize);
- ReadCheck(Stream, FIdent, sizeof(FIdent));
- Dec(i, sizeof(FIdent));
- // Ignore extra data
- Stream.Seek(i, soFromCurrent);
- // ***FIXME***
- // If self class is TGIFApplicationExtension, this will cause an "abstract
- // error".
- // TGIFApplicationExtension.LoadData should read and ignore rest of block.
- LoadData(Stream);
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFUnknownAppExtension
- //
- ////////////////////////////////////////////////////////////////////////////////
- constructor TGIFBlock.Create(ASize: integer);
- begin
- inherited Create;
- FSize := ASize;
- GetMem(FData, FSize);
- FillChar(FData^, FSize, 0);
- end;
- destructor TGIFBlock.Destroy;
- begin
- FreeMem(FData);
- inherited Destroy;
- end;
- procedure TGIFBlock.SaveToStream(Stream: TStream);
- begin
- Stream.Write(FSize, 1);
- Stream.Write(FData^, FSize);
- end;
- procedure TGIFBlock.LoadFromStream(Stream: TStream);
- begin
- ReadCheck(Stream, FData^, FSize);
- end;
- constructor TGIFUnknownAppExtension.Create(ASubImage: TGIFSubImage);
- begin
- inherited Create(ASubImage);
- FBlocks := TList.Create;
- end;
- destructor TGIFUnknownAppExtension.Destroy;
- var
- i : integer;
- begin
- for i := 0 to FBlocks.Count-1 do
- TGIFBlock(FBlocks[i]).Free;
- FBlocks.Free;
- inherited Destroy;
- end;
- procedure TGIFUnknownAppExtension.SaveData(Stream: TStream);
- var
- i : integer;
- begin
- for i := 0 to FBlocks.Count-1 do
- TGIFBlock(FBlocks[i]).SaveToStream(Stream);
- // Terminating zero
- WriteByte(Stream, 0);
- end;
- procedure TGIFUnknownAppExtension.LoadData(Stream: TStream);
- var
- b : BYTE;
- Block : TGIFBlock;
- i : integer;
- begin
- // Zap old blocks
- for i := 0 to FBlocks.Count-1 do
- TGIFBlock(FBlocks[i]).Free;
- FBlocks.Clear;
- // Read blocks
- if (Stream.Read(b, 1) <> 1) then
- exit;
- while (b <> 0) do
- begin
- Block := TGIFBlock.Create(b);
- try
- Block.LoadFromStream(Stream);
- except
- Block.Free;
- raise;
- end;
- FBlocks.Add(Block);
- if (Stream.Read(b, 1) <> 1) then
- exit;
- end;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFAppExtNSLoop
- //
- ////////////////////////////////////////////////////////////////////////////////
- const
- // Netscape sub block types
- nbLoopExtension = 1;
- nbBufferExtension = 2;
- constructor TGIFAppExtNSLoop.Create(ASubImage: TGIFSubImage);
- const
- NSLoopIdent: TGIFApplicationRec = (Identifier: 'NETSCAPE'; Authentication: '2.0');
- begin
- inherited Create(ASubImage);
- FIdent := NSLoopIdent;
- end;
- procedure TGIFAppExtNSLoop.SaveData(Stream: TStream);
- begin
- // Write loop count
- WriteByte(Stream, 1 + sizeof(FLoops)); // Size of block
- WriteByte(Stream, nbLoopExtension); // Identify sub block as looping extension data
- Stream.Write(FLoops, sizeof(FLoops)); // Loop count
- // Write buffer size if specified
- if (FBufferSize > 0) then
- begin
- WriteByte(Stream, 1 + sizeof(FBufferSize)); // Size of block
- WriteByte(Stream, nbBufferExtension); // Identify sub block as buffer size data
- Stream.Write(FBufferSize, sizeof(FBufferSize)); // Buffer size
- end;
- WriteByte(Stream, 0); // Terminating zero
- end;
- procedure TGIFAppExtNSLoop.LoadData(Stream: TStream);
- var
- BlockSize : integer;
- BlockType : integer;
- begin
- // Read size of first block or terminating zero
- BlockSize := ReadByte(Stream);
- while (BlockSize <> 0) do
- begin
- BlockType := ReadByte(Stream);
- dec(BlockSize);
- case (BlockType AND $07) of
- nbLoopExtension:
- begin
- if (BlockSize < sizeof(FLoops)) then
- Error(sInvalidData);
- // Read loop count
- ReadCheck(Stream, FLoops, sizeof(FLoops));
- dec(BlockSize, sizeof(FLoops));
- end;
- nbBufferExtension:
- begin
- if (BlockSize < sizeof(FBufferSize)) then
- Error(sInvalidData);
- // Read buffer size
- ReadCheck(Stream, FBufferSize, sizeof(FBufferSize));
- dec(BlockSize, sizeof(FBufferSize));
- end;
- end;
- // Skip/ignore unread data
- if (BlockSize > 0) then
- Stream.Seek(BlockSize, soFromCurrent);
- // Read size of next block or terminating zero
- BlockSize := ReadByte(Stream);
- end;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFImageList
- //
- ////////////////////////////////////////////////////////////////////////////////
- function TGIFImageList.GetImage(Index: Integer): TGIFSubImage;
- begin
- Result := TGIFSubImage(Items[Index]);
- end;
- procedure TGIFImageList.SetImage(Index: Integer; SubImage: TGIFSubImage);
- begin
- Items[Index] := SubImage;
- end;
- procedure TGIFImageList.LoadFromStream(Stream: TStream; Parent: TObject);
- var
- b : BYTE;
- SubImage : TGIFSubImage;
- begin
- // Peek ahead to determine block type
- repeat
- if (Stream.Read(b, 1) <> 1) then
- exit;
- until (b <> 0); // Ignore 0 padding (non-compliant)
- while (b <> bsTrailer) do
- begin
- Stream.Seek(-1, soFromCurrent);
- if (b in [bsExtensionIntroducer, bsImageDescriptor]) then
- begin
- SubImage := TGIFSubImage.Create(Parent as TGIFImage);
- try
- SubImage.LoadFromStream(Stream);
- Add(SubImage);
- Image.Progress(Self, psRunning, MulDiv(Stream.Position, 100, Stream.Size),
- GIFImageRenderOnLoad, Rect(0,0,0,0), sProgressLoading);
- except
- SubImage.Free;
- raise;
- end;
- end else
- begin
- Warning(gsWarning, sBadBlock);
- break;
- end;
- repeat
- if (Stream.Read(b, 1) <> 1) then
- exit;
- until (b <> 0); // Ignore 0 padding (non-compliant)
- end;
- Stream.Seek(-1, soFromCurrent);
- end;
- procedure TGIFImageList.SaveToStream(Stream: TStream);
- var
- i : integer;
- begin
- for i := 0 to Count-1 do
- begin
- TGIFItem(Items[i]).SaveToStream(Stream);
- Image.Progress(Self, psRunning, MulDiv((i+1), 100, Count), False, Rect(0,0,0,0), sProgressSaving);
- end;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFPainter
- //
- ////////////////////////////////////////////////////////////////////////////////
- constructor TGIFPainter.CreateRef(Painter: PGIFPainter; AImage: TGIFImage;
- ACanvas: TCanvas; ARect: TRect; Options: TGIFDrawOptions);
- begin
- Create(AImage, ACanvas, ARect, Options);
- PainterRef := Painter;
- if (PainterRef <> nil) then
- PainterRef^ := self;
- end;
- constructor TGIFPainter.Create(AImage: TGIFImage; ACanvas: TCanvas; ARect: TRect;
- Options: TGIFDrawOptions);
- var
- i : integer;
- BackgroundColor : TColor;
- Disposals : set of TDisposalMethod;
- begin
- inherited Create(True);
- FreeOnTerminate := True;
- Onterminate := DoOnTerminate;
- FImage := AImage;
- FCanvas := ACanvas;
- FRect := ARect;
- FActiveImage := -1;
- FDrawOptions := Options;
- FStarted := False;
- BackupBuffer := nil;
- FrameBuffer := nil;
- Background := nil;
- FEventHandle := 0;
- // This should be a parameter, but I think I've got enough of them already...
- FAnimationSpeed := FImage.AnimationSpeed;
- // An event handle is used for animation delays
- if (FDrawOptions >= [goAnimate, goAsync]) and (FImage.Images.Count > 1) and
- (FAnimationSpeed >= 0) then
- FEventHandle := CreateEvent(nil, False, False, nil);
- // Preprocessing of extensions to determine if we need frame buffers
- Disposals := [];
- if (FImage.DrawBackgroundColor = clNone) then
- begin
- if (FImage.GlobalColorMap.Count > 0) then
- BackgroundColor := FImage.BackgroundColor
- else
- BackgroundColor := ColorToRGB(clWindow);
- end else
- BackgroundColor := ColorToRGB(FImage.DrawBackgroundColor);
- // Need background buffer to clear on loop
- if (goClearOnLoop in FDrawOptions) then
- Include(Disposals, dmBackground);
- for i := 0 to FImage.Images.Count-1 do
- if (FImage.Images[i].GraphicControlExtension <> nil) then
- with (FImage.Images[i].GraphicControlExtension) do
- Include(Disposals, Disposal);
- // Need background buffer to draw transparent on background
- if (dmBackground in Disposals) and (goTransparent in FDrawOptions) then
- begin
- Background := TBitmap.Create;
- Background.Height := FRect.Bottom-FRect.Top;
- Background.Width := FRect.Right-FRect.Left;
- // Copy background immediately
- Background.Canvas.CopyMode := cmSrcCopy;
- Background.Canvas.CopyRect(Background.Canvas.ClipRect, FCanvas, FRect);
- end;
- // Need frame- and backup buffer to restore to previous and background
- if ((Disposals * [dmPrevious, dmBackground]) <> []) then
- begin
- BackupBuffer := TBitmap.Create;
- BackupBuffer.Height := FRect.Bottom-FRect.Top;
- BackupBuffer.Width := FRect.Right-FRect.Left;
- BackupBuffer.Canvas.CopyMode := cmSrcCopy;
- BackupBuffer.Canvas.Brush.Color := BackgroundColor;
- BackupBuffer.Canvas.Brush.Style := bsSolid;
- {$IFDEF DEBUG}
- BackupBuffer.Canvas.Brush.Color := clBlack;
- BackupBuffer.Canvas.Brush.Style := bsDiagCross;
- {$ENDIF}
- // Step 1: Copy destination to backup buffer
- // Always executed before first frame and only once.
- BackupBuffer.Canvas.CopyRect(BackupBuffer.Canvas.ClipRect, FCanvas, FRect);
- FrameBuffer := TBitmap.Create;
- FrameBuffer.Height := FRect.Bottom-FRect.Top;
- FrameBuffer.Width := FRect.Right-FRect.Left;
- FrameBuffer.Canvas.CopyMode := cmSrcCopy;
- FrameBuffer.Canvas.Brush.Color := BackgroundColor;
- FrameBuffer.Canvas.Brush.Style := bsSolid;
- {$IFDEF DEBUG}
- FrameBuffer.Canvas.Brush.Color := clBlack;
- FrameBuffer.Canvas.Brush.Style := bsDiagCross;
- {$ENDIF}
- end;
- end;
- destructor TGIFPainter.Destroy;
- begin
- // OnTerminate isn't called if we are running in main thread, so we must call
- // it manually
- if not(goAsync in DrawOptions) then
- DoOnTerminate(self);
- // Reraise any exptions that were eaten in the Execute method
- if (ExceptObject <> nil) then
- raise ExceptObject at ExceptAddress;
- inherited Destroy;
- end;
- procedure TGIFPainter.SetAnimationSpeed(Value: integer);
- begin
- if (Value < 0) then
- Value := 0
- else if (Value > 1000) then
- Value := 1000;
- if (Value <> FAnimationSpeed) then
- begin
- FAnimationSpeed := Value;
- // Signal WaitForSingleObject delay to abort
- if (FEventHandle <> 0) then
- SetEvent(FEventHandle)
- else
- DoRestart := True;
- end;
- end;
- procedure TGIFPainter.SetActiveImage(const Value: integer);
- begin
- if (Value >= 0) and (Value < FImage.Images.Count) then
- FActiveImage := Value;
- end;
- // Conditional Synchronize
- procedure TGIFPainter.DoSynchronize(Method: TThreadMethod);
- begin
- if (Terminated) then
- exit;
- if (goAsync in FDrawOptions) then
- // Execute Synchronized if requested...
- Synchronize(Method)
- else
- // ...Otherwise just execute in current thread (probably main thread)
- Method;
- end;
- // Delete frame buffers - Executed in main thread
- procedure TGIFPainter.DoOnTerminate(Sender: TObject);
- begin
- // It shouldn't really be nescessary to protect PainterRef in this manner
- // since we are running in the main thread at this point, but I'm a little
- // paranoid about the way PainterRef is being used...
- if Image <> nil then // 2001.02.23
- begin // 2001.02.23
- with Image.Painters.LockList do
- try
- // Zap pointer to self and remove from painter list
- if (PainterRef <> nil) and (PainterRef^ = self) then
- PainterRef^ := nil;
- finally
- Image.Painters.UnLockList;
- end;
- Image.Painters.Remove(self);
- FImage := nil;
- end; // 2001.02.23
- // Free buffers
- if (BackupBuffer <> nil) then
- BackupBuffer.Free;
- if (FrameBuffer <> nil) then
- FrameBuffer.Free;
- if (Background <> nil) then
- Background.Free;
- // Delete event handle
- if (FEventHandle <> 0) then
- CloseHandle(FEventHandle);
- end;
- // Event "dispatcher" - Executed in main thread
- procedure TGIFPainter.DoEvent;
- begin
- if (Assigned(FEvent)) then
- FEvent(self);
- end;
- // Non-buffered paint - Executed in main thread
- procedure TGIFPainter.DoPaint;
- begin
- FImage.Images[ActiveImage].Draw(FCanvas, FRect, (goTransparent in FDrawOptions),
- (goTile in FDrawOptions));
- FStarted := True;
- end;
- // Buffered paint - Executed in main thread
- procedure TGIFPainter.DoPaintFrame;
- var
- DrawDestination : TCanvas;
- DrawRect : TRect;
- DoStep2 ,
- DoStep3 ,
- DoStep5 ,
- DoStep6 : boolean;
- SavePal ,
- SourcePal : HPALETTE;
- procedure ClearBackup;
- var
- r ,
- Tile : TRect;
- FrameTop ,
- FrameHeight : integer;
- ImageWidth ,
- ImageHeight : integer;
- begin
- if (goTransparent in FDrawOptions) then
- begin
- // If the frame is transparent, we must remove it by copying the
- // background buffer over it
- if (goTile in FDrawOptions) then
- begin
- FrameTop := FImage.Images[ActiveImage].Top;
- FrameHeight := FImage.Images[ActiveImage].Height;
- ImageWidth := FImage.Width;
- ImageHeight := FImage.Height;
- Tile.Left := FRect.Left + FImage.Images[ActiveImage].Left;
- Tile.Right := Tile.Left + FImage.Images[ActiveImage].Width;
- while (Tile.Left < FRect.Right) do
- begin
- Tile.Top := FRect.Top + FrameTop;
- Tile.Bottom := Tile.Top + FrameHeight;
- while (Tile.Top < FRect.Bottom) do
- begin
- BackupBuffer.Canvas.CopyRect(Tile, Background.Canvas, Tile);
- Tile.Top := Tile.Top + ImageHeight;
- Tile.Bottom := Tile.Bottom + ImageHeight;
- end;
- Tile.Left := Tile.Left + ImageWidth;
- Tile.Right := Tile.Right + ImageWidth;
- end;
- end else
- begin
- r := FImage.Images[ActiveImage].ScaleRect(BackupBuffer.Canvas.ClipRect);
- BackupBuffer.Canvas.CopyRect(r, Background.Canvas, r)
- end;
- end else
- begin
- // If the frame isn't transparent, we just clear the area covered by
- // it to the background color.
- // Tile the background unless the frame covers all of the image
- if (goTile in FDrawOptions) and
- ((FImage.Width <> FImage.Images[ActiveImage].Width) and
- (FImage.height <> FImage.Images[ActiveImage].Height)) then
- begin
- FrameTop := FImage.Images[ActiveImage].Top;
- FrameHeight := FImage.Images[ActiveImage].Height;
- ImageWidth := FImage.Width;
- ImageHeight := FImage.Height;
- // ***FIXME*** I don't think this does any difference
- BackupBuffer.Canvas.Brush.Color := FImage.DrawBackgroundColor;
- Tile.Left := FRect.Left + FImage.Images[ActiveImage].Left;
- Tile.Right := Tile.Left + FImage.Images[ActiveImage].Width;
- while (Tile.Left < FRect.Right) do
- begin
- Tile.Top := FRect.Top + FrameTop;
- Tile.Bottom := Tile.Top + FrameHeight;
- while (Tile.Top < FRect.Bottom) do
- begin
- BackupBuffer.Canvas.FillRect(Tile);
- Tile.Top := Tile.Top + ImageHeight;
- Tile.Bottom := Tile.Bottom + ImageHeight;
- end;
- Tile.Left := Tile.Left + ImageWidth;
- Tile.Right := Tile.Right + ImageWidth;
- end;
- end else
- BackupBuffer.Canvas.FillRect(FImage.Images[ActiveImage].ScaleRect(FRect));
- end;
- end;
- begin
- if (goValidateCanvas in FDrawOptions) then
- if (GetObjectType(ValidateDC) <> OBJ_DC) then
- begin
- Terminate;
- exit;
- end;
- DrawDestination := nil;
- DoStep2 := (goClearOnLoop in FDrawOptions) and (FActiveImage = 0);
- DoStep3 := False;
- DoStep5 := False;
- DoStep6 := False;
- {
- Disposal mode algorithm:
- Step 1: Copy destination to backup buffer
- Always executed before first frame and only once.
- Done in constructor.
- Step 2: Clear previous frame (implementation is same as step 6)
- Done implicitly by implementation.
- Only done explicitly on first frame if goClearOnLoop option is set.
- Step 3: Copy backup buffer to frame buffer
- Step 4: Draw frame
- Step 5: Copy buffer to destination
- Step 6: Clear frame from backup buffer
- +------------+------------------+---------------------+------------------------+
- |New \ Old | dmNone | dmBackground | dmPrevious |
- +------------+------------------+---------------------+------------------------+
- |dmNone | | | |
- | |4. Paint on backup|4. Paint on backup |4. Paint on backup |
- | |5. Restore |5. Restore |5. Restore |
- +------------+------------------+---------------------+------------------------+
- |dmBackground| | | |
- | |4. Paint on backup|4. Paint on backup |4. Paint on backup |
- | |5. Restore |5. Restore |5. Restore |
- | |6. Clear backup |6. Clear backup |6. Clear backup |
- +------------+------------------+---------------------+------------------------+
- |dmPrevious | | | |
- | | |3. Copy backup to buf|3. Copy backup to buf |
- | |4. Paint on dest |4. Paint on buf |4. Paint on buf |
- | | |5. Copy buf to dest |5. Copy buf to dest |
- +------------+------------------+---------------------+------------------------+
- }
- case (Disposal) of
- dmNone, dmNoDisposal:
- begin
- DrawDestination := BackupBuffer.Canvas;
- DrawRect := BackupBuffer.Canvas.ClipRect;
- DoStep5 := True;
- end;
- dmBackground:
- begin
- DrawDestination := BackupBuffer.Canvas;
- DrawRect := BackupBuffer.Canvas.ClipRect;
- DoStep5 := True;
- DoStep6 := True;
- end;
- dmPrevious:
- case (OldDisposal) of
- dmNone, dmNoDisposal:
- begin
- DrawDestination := FCanvas;
- DrawRect := FRect;
- end;
- dmBackground, dmPrevious:
- begin
- DrawDestination := FrameBuffer.Canvas;
- DrawRect := FrameBuffer.Canvas.ClipRect;
- DoStep3 := True;
- DoStep5 := True;
- end;
- end;
- end;
- // Find source palette
- SourcePal := FImage.Images[ActiveImage].Palette;
- if (SourcePal = 0) then
- SourcePal := SystemPalette16; // This should never happen
- SavePal := SelectPalette(DrawDestination.Handle, SourcePal, False);
- RealizePalette(DrawDestination.Handle);
- // Step 2: Clear previous frame
- if (DoStep2) then
- ClearBackup;
- // Step 3: Copy backup buffer to frame buffer
- if (DoStep3) then
- FrameBuffer.Canvas.CopyRect(FrameBuffer.Canvas.ClipRect,
- BackupBuffer.Canvas, BackupBuffer.Canvas.ClipRect);
- // Step 4: Draw frame
- if (DrawDestination <> nil) then
- FImage.Images[ActiveImage].Draw(DrawDestination, DrawRect,
- (goTransparent in FDrawOptions), (goTile in FDrawOptions));
- // Step 5: Copy buffer to destination
- if (DoStep5) then
- begin
- FCanvas.CopyMode := cmSrcCopy;
- FCanvas.CopyRect(FRect, DrawDestination, DrawRect);
- end;
- if (SavePal <> 0) then
- SelectPalette(DrawDestination.Handle, SavePal, False);
- // Step 6: Clear frame from backup buffer
- if (DoStep6) then
- ClearBackup;
- FStarted := True;
- end;
- // Prefetch bitmap
- // Used to force the GIF image to be rendered as a bitmap
- {$ifdef SERIALIZE_RENDER}
- procedure TGIFPainter.PrefetchBitmap;
- begin
- // Touch current bitmap to force bitmap to be rendered
- if not((FImage.Images[ActiveImage].Empty) or (FImage.Images[ActiveImage].HasBitmap)) then
- FImage.Images[ActiveImage].Bitmap;
- end;
- {$endif}
- // Main thread execution loop - This is where it all happens...
- procedure TGIFPainter.Execute;
- var
- i : integer;
- LoopCount ,
- LoopPoint : integer;
- Looping : boolean;
- Ext : TGIFExtension;
- Msg : TMsg;
- Delay ,
- OldDelay ,
- DelayUsed : longInt;
- DelayStart ,
- NewDelayStart : DWORD;
- procedure FireEvent(Event: TNotifyEvent);
- begin
- if not(Assigned(Event)) then
- exit;
- FEvent := Event;
- try
- DoSynchronize(DoEvent);
- finally
- FEvent := nil;
- end;
- end;
- begin
- {
- Disposal:
- dmNone: Same as dmNodisposal
- dmNoDisposal: Do not dispose
- dmBackground: Clear with background color *)
- dmPrevious: Previous image
- *) Note: Background color should either be a BROWSER SPECIFIED Background
- color (DrawBackgroundColor) or the background image if any frames are
- transparent.
- }
- try
- try
- if (goValidateCanvas in FDrawOptions) then
- ValidateDC := FCanvas.Handle;
- DoRestart := True;
- // Loop to restart paint
- while (DoRestart) and not(Terminated) do
- begin
- FActiveImage := 0;
- // Fire OnStartPaint event
- // Note: ActiveImage may be altered by the event handler
- FireEvent(FOnStartPaint);
- FStarted := False;
- DoRestart := False;
- LoopCount := 1;
- LoopPoint := FActiveImage;
- Looping := False;
- if (goAsync in DrawOptions) then
- Delay := 0
- else
- Delay := 1; // Dummy to process messages
- OldDisposal := dmNoDisposal;
- // Fetch delay start time
- DelayStart := timeGetTime;
- OldDelay := 0;
- // Loop to loop - duh!
- while ((LoopCount <> 0) or (goLoopContinously in DrawOptions)) and
- not(Terminated or DoRestart) do
- begin
- FActiveImage := LoopPoint;
- // Fire OnLoopPaint event
- // Note: ActiveImage may be altered by the event handler
- if (FStarted) then
- FireEvent(FOnLoop);
- // Loop to animate
- while (ActiveImage < FImage.Images.Count) and not(Terminated or DoRestart) do
- begin
- // Ignore empty images
- if (FImage.Images[ActiveImage].Empty) then
- break;
- // Delay from previous image
- if (Delay > 0) then
- begin
- // Prefetch frame bitmap
- {$ifdef SERIALIZE_RENDER}
- DoSynchronize(PrefetchBitmap);
- {$else}
- FImage.Images[ActiveImage].Bitmap;
- {$endif}
- // Calculate inter frame delay
- NewDelayStart := timeGetTime;
- if (FAnimationSpeed > 0) then
- begin
- // Calculate number of mS used in prefetch and display
- try
- DelayUsed := integer(NewDelayStart-DelayStart)-OldDelay;
- // Prevent feedback oscillations caused by over/undercompensation.
- DelayUsed := DelayUsed DIV 2;
- // Convert delay value to mS and...
- // ...Adjust for time already spent converting GIF to bitmap and...
- // ...Adjust for Animation Speed factor.
- Delay := MulDiv(Delay * GIFDelayExp - DelayUsed, 100, FAnimationSpeed);
- OldDelay := Delay;
- except
- Delay := GIFMaximumDelay * GIFDelayExp;
- OldDelay := 0;
- end;
- end else
- begin
- if (goAsync in DrawOptions) then
- Delay := longInt(INFINITE)
- else
- Delay := GIFMaximumDelay * GIFDelayExp;
- end;
- // Fetch delay start time
- DelayStart := NewDelayStart;
- // Sleep in one chunk if we are running in a thread
- if (goAsync in DrawOptions) then
- begin
- // Use of WaitForSingleObject allows TGIFPainter.Stop to wake us up
- if (Delay > 0) or (FAnimationSpeed = 0) then
- begin
- if (WaitForSingleObject(FEventHandle, DWORD(Delay)) <> WAIT_TIMEOUT) then
- begin
- // Don't use interframe delay feedback adjustment if delay
- // were prematurely aborted (e.g. because the animation
- // speed were changed)
- OldDelay := 0;
- DelayStart := longInt(timeGetTime);
- end;
- end;
- end else
- begin
- if (Delay <= 0) then
- Delay := 1;
- // Fetch start time
- NewDelayStart := timeGetTime;
- // If we are not running in a thread we Sleep in small chunks
- // and give the user a chance to abort
- while (Delay > 0) and not(Terminated or DoRestart) do
- begin
- if (Delay < 100) then
- Sleep(Delay)
- else
- Sleep(100);
- // Calculate number of mS delayed in this chunk
- DelayUsed := integer(timeGetTime - NewDelayStart);
- dec(Delay, DelayUsed);
- // Reset start time for chunk
- NewDelaySTart := timeGetTime;
- // Application.ProcessMessages wannabe
- while (not(Terminated or DoRestart)) and
- (PeekMessage(Msg, 0, 0, 0, PM_REMOVE)) do
- begin
- if (Msg.Message <> WM_QUIT) then
- begin
- TranslateMessage(Msg);
- DispatchMessage(Msg);
- end else
- begin
- // Put WM_QUIT back in queue and get out of here fast
- PostQuitMessage(Msg.WParam);
- Terminate;
- end;
- end;
- end;
- end;
- end else
- Sleep(0); // Yield
- if (Terminated) then
- break;
- // Fire OnPaint event
- // Note: ActiveImage may be altered by the event handler
- FireEvent(FOnPaint);
- if (Terminated) then
- break;
- // Pre-draw processing of extensions
- Disposal := dmNoDisposal;
- for i := 0 to FImage.Images[ActiveImage].Extensions.Count-1 do
- begin
- Ext := FImage.Images[ActiveImage].Extensions[i];
- if (Ext is TGIFAppExtNSLoop) then
- begin
- // Recursive loops not supported (or defined)
- if (Looping) then
- continue;
- Looping := True;
- LoopCount := TGIFAppExtNSLoop(Ext).Loops;
- if ((LoopCount = 0) or (goLoopContinously in DrawOptions)) and
- (goAsync in DrawOptions) then
- LoopCount := -1; // Infinite if running in separate thread
- {$IFNDEF STRICT_MOZILLA}
- // Loop from this image and on
- // Note: This is not standard behavior
- LoopPoint := ActiveImage;
- {$ENDIF}
- end else
- if (Ext is TGIFGraphicControlExtension) then
- Disposal := TGIFGraphicControlExtension(Ext).Disposal;
- end;
- // Paint the image
- if (BackupBuffer <> nil) then
- DoSynchronize(DoPaintFrame)
- else
- DoSynchronize(DoPaint);
- OldDisposal := Disposal;
- if (Terminated) then
- break;
- Delay := GIFDefaultDelay; // Default delay
- // Post-draw processing of extensions
- if (FImage.Images[ActiveImage].GraphicControlExtension <> nil) then
- if (FImage.Images[ActiveImage].GraphicControlExtension.Delay > 0) then
- begin
- Delay := FImage.Images[ActiveImage].GraphicControlExtension.Delay;
- // Enforce minimum animation delay in compliance with Mozilla
- if (Delay < GIFMinimumDelay) then
- Delay := GIFMinimumDelay;
- // Do not delay more than 10 seconds if running in main thread
- if (Delay > GIFMaximumDelay) and not(goAsync in DrawOptions) then
- Delay := GIFMaximumDelay; // Max 10 seconds
- end;
- // Fire OnAfterPaint event
- // Note: ActiveImage may be altered by the event handler
- i := FActiveImage;
- FireEvent(FOnAfterPaint);
- if (Terminated) then
- break;
- // Don't increment frame counter if event handler modified
- // current frame
- if (FActiveImage = i) then
- Inc(FActiveImage);
- // Nothing more to do unless we are animating
- if not(goAnimate in DrawOptions) then
- break;
- end;
- if (LoopCount > 0) then
- Dec(LoopCount);
- if ([goAnimate, goLoop] * DrawOptions <> [goAnimate, goLoop]) then
- break;
- end;
- if (Terminated) then // 2001.07.23
- break; // 2001.07.23
- end;
- FActiveImage := -1;
- // Fire OnEndPaint event
- FireEvent(FOnEndPaint);
- finally
- // If we are running in the main thread we will have to zap our self
- if not(goAsync in DrawOptions) then
- Free;
- end;
- except
- on E: Exception do
- begin
- // Eat exception and terminate thread...
- // If we allow the exception to abort the thread at this point, the
- // application will hang since the thread destructor will never be called
- // and the application will wait forever for the thread to die!
- Terminate;
- // Clone exception
- ExceptObject := E.Create(E.Message);
- ExceptAddress := ExceptAddr;
- end;
- end;
- end;
- procedure TGIFPainter.Start;
- begin
- if (goAsync in FDrawOptions) then
- Resume;
- end;
- procedure TGIFPainter.Stop;
- begin
- Terminate;
- if (goAsync in FDrawOptions) then
- begin
- // Signal WaitForSingleObject delay to abort
- if (FEventHandle <> 0) then
- SetEvent(FEventHandle);
- Priority := tpNormal;
- if (Suspended) then
- Resume; // Must be running before we can terminate
- end;
- end;
- procedure TGIFPainter.Restart;
- begin
- DoRestart := True;
- if (Suspended) and (goAsync in FDrawOptions) then
- Resume; // Must be running before we can terminate
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TColorMapOptimizer
- //
- ////////////////////////////////////////////////////////////////////////////////
- // Used by TGIFImage to optimize local color maps to a single global color map.
- // The following algorithm is used:
- // 1) Build a histogram for each image
- // 2) Merge histograms
- // 3) Sum equal colors and adjust max # of colors
- // 4) Map entries > max to entries <= 256
- // 5) Build new color map
- // 6) Map images to new color map
- ////////////////////////////////////////////////////////////////////////////////
- type
- POptimizeEntry = ^TOptimizeEntry;
- TColorRec = record
- case byte of
- 0: (Value: integer);
- 1: (Color: TGIFColor);
- 2: (SameAs: POptimizeEntry); // Used if TOptimizeEntry.Count = 0
- end;
- TOptimizeEntry = record
- Count : integer; // Usage count
- OldIndex : integer; // Color OldIndex
- NewIndex : integer; // NewIndex color OldIndex
- Color : TColorRec; // Color value
- end;
- TOptimizeEntries = array[0..255] of TOptimizeEntry;
- POptimizeEntries = ^TOptimizeEntries;
- THistogram = class(TObject)
- private
- PHistogram : POptimizeEntries;
- FCount : integer;
- FColorMap : TGIFColorMap;
- FList : TList;
- FImages : TList;
- public
- constructor Create(AColorMap: TGIFColorMap);
- destructor Destroy; override;
- function ProcessSubImage(Image: TGIFSubImage): boolean;
- function Prune: integer;
- procedure MapImages(UseTransparency: boolean; NewTransparentColorIndex: byte);
- property Count: integer read FCount;
- property ColorMap: TGIFColorMap read FColorMap;
- property List: TList read FList;
- end;
- TColorMapOptimizer = class(TObject)
- private
- FImage : TGIFImage;
- FHistogramList : TList;
- FHistogram : TList;
- FColorMap : TColorMap;
- FFinalCount : integer;
- FUseTransparency : boolean;
- FNewTransparentColorIndex: byte;
- protected
- procedure ProcessImage;
- procedure MergeColors;
- procedure MapColors;
- procedure ReplaceColorMaps;
- public
- constructor Create(AImage: TGIFImage);
- destructor Destroy; override;
- procedure Optimize;
- end;
- function CompareColor(Item1, Item2: Pointer): integer;
- begin
- Result := POptimizeEntry(Item2)^.Color.Value - POptimizeEntry(Item1)^.Color.Value;
- end;
- function CompareCount(Item1, Item2: Pointer): integer;
- begin
- Result := POptimizeEntry(Item2)^.Count - POptimizeEntry(Item1)^.Count;
- end;
- constructor THistogram.Create(AColorMap: TGIFColorMap);
- var
- i : integer;
- begin
- inherited Create;
- FCount := AColorMap.Count;
- FColorMap := AColorMap;
- FImages := TList.Create;
- // Allocate memory for histogram
- GetMem(PHistogram, FCount * sizeof(TOptimizeEntry));
- FList := TList.Create;
- FList.Capacity := FCount;
- // Move data to histogram and initialize
- for i := 0 to FCount-1 do
- with PHistogram^[i] do
- begin
- FList.Add(@PHistogram^[i]);
- OldIndex := i;
- Count := 0;
- Color.Value := 0;
- Color.Color := AColorMap.Data^[i];
- NewIndex := 256; // Used to signal unmapped
- end;
- end;
- destructor THistogram.Destroy;
- begin
- FImages.Free;
- FList.Free;
- FreeMem(PHistogram);
- inherited Destroy;
- end;
- //: Build a color histogram
- function THistogram.ProcessSubImage(Image: TGIFSubImage): boolean;
- var
- Size : integer;
- Pixel : PChar;
- IsTransparent ,
- WasTransparent : boolean;
- OldTransparentColorIndex: byte;
- begin
- Result := False;
- if (Image.Empty) then
- exit;
- FImages.Add(Image);
- Pixel := Image.data;
- Size := Image.Width * Image.Height;
- IsTransparent := Image.Transparent;
- if (IsTransparent) then
- OldTransparentColorIndex := Image.GraphicControlExtension.TransparentColorIndex
- else
- OldTransparentColorIndex := 0; // To avoid compiler warning
- WasTransparent := False;
- (*
- ** Sum up usage count for each color
- *)
- while (Size > 0) do
- begin
- // Ignore transparent pixels
- if (not IsTransparent) or (ord(Pixel^) <> OldTransparentColorIndex) then
- begin
- // Check for invalid color index
- if (ord(Pixel^) >= FCount) then
- begin
- Pixel^ := #0; // ***FIXME*** Isn't this an error condition?
- Image.Warning(gsWarning, sInvalidColor);
- end;
- with PHistogram^[ord(Pixel^)] do
- begin
- // Stop if any color reaches the max count
- if (Count = high(integer)) then
- break;
- inc(Count);
- end;
- end else
- WasTransparent := WasTransparent or IsTransparent;
- inc(Pixel);
- dec(Size);
- end;
- (*
- ** Clear frames transparency flag if the frame claimed to
- ** be transparent, but wasn't
- *)
- if (IsTransparent and not WasTransparent) then
- begin
- Image.GraphicControlExtension.TransparentColorIndex := 0;
- Image.GraphicControlExtension.Transparent := False;
- end;
- Result := WasTransparent;
- end;
- //: Removed unused color entries from the histogram
- function THistogram.Prune: integer;
- var
- i, j : integer;
- begin
- (*
- ** Sort by usage count
- *)
- FList.Sort(CompareCount);
- (*
- ** Determine number of used colors
- *)
- for i := 0 to FCount-1 do
- // Find first unused color entry
- if (POptimizeEntry(FList[i])^.Count = 0) then
- begin
- // Zap unused colors
- for j := i to FCount-1 do
- POptimizeEntry(FList[j])^.Count := -1; // Use -1 to signal unused entry
- // Remove unused entries
- FCount := i;
- FList.Count := FCount;
- break;
- end;
- Result := FCount;
- end;
- //: Convert images from old color map to new color map
- procedure THistogram.MapImages(UseTransparency: boolean; NewTransparentColorIndex: byte);
- var
- i : integer;
- Size : integer;
- Pixel : PChar;
- ReverseMap : array[byte] of byte;
- IsTransparent : boolean;
- OldTransparentColorIndex: byte;
- begin
- (*
- ** Build NewIndex map
- *)
- for i := 0 to List.Count-1 do
- ReverseMap[POptimizeEntry(List[i])^.OldIndex] := POptimizeEntry(List[i])^.NewIndex;
- (*
- ** Reorder all images using this color map
- *)
- for i := 0 to FImages.Count-1 do
- with TGIFSubImage(FImages[i]) do
- begin
- Pixel := Data;
- Size := Width * Height;
- // Determine frame transparency
- IsTransparent := (Transparent) and (UseTransparency);
- if (IsTransparent) then
- begin
- OldTransparentColorIndex := GraphicControlExtension.TransparentColorIndex;
- // Map transparent color
- GraphicControlExtension.TransparentColorIndex := NewTransparentColorIndex;
- end else
- OldTransparentColorIndex := 0; // To avoid compiler warning
- // Map all pixels to new color map
- while (Size > 0) do
- begin
- // Map transparent pixels to the new transparent color index and...
- if (IsTransparent) and (ord(Pixel^) = OldTransparentColorIndex) then
- Pixel^ := char(NewTransparentColorIndex)
- else
- // ... all other pixels to their new color index
- Pixel^ := char(ReverseMap[ord(Pixel^)]);
- dec(size);
- inc(Pixel);
- end;
- end;
- end;
- constructor TColorMapOptimizer.Create(AImage: TGIFImage);
- begin
- inherited Create;
- FImage := AImage;
- FHistogramList := TList.Create;
- FHistogram := TList.Create;
- end;
- destructor TColorMapOptimizer.Destroy;
- var
- i : integer;
- begin
- FHistogram.Free;
- for i := FHistogramList.Count-1 downto 0 do
- THistogram(FHistogramList[i]).Free;
- FHistogramList.Free;
- inherited Destroy;
- end;
- procedure TColorMapOptimizer.ProcessImage;
- var
- Hist : THistogram;
- i : integer;
- ProcessedImage : boolean;
- begin
- FUseTransparency := False;
- (*
- ** First process images using global color map
- *)
- if (FImage.GlobalColorMap.Count > 0) then
- begin
- Hist := THistogram.Create(FImage.GlobalColorMap);
- ProcessedImage := False;
- // Process all images that are using the global color map
- for i := 0 to FImage.Images.Count-1 do
- if (FImage.Images[i].ColorMap.Count = 0) and (not FImage.Images[i].Empty) then
- begin
- ProcessedImage := True;
- // Note: Do not change order of statements. Shortcircuit evaluation not desired!
- FUseTransparency := Hist.ProcessSubImage(FImage.Images[i]) or FUseTransparency;
- end;
- // Keep the histogram if any images used the global color map...
- if (ProcessedImage) then
- FHistogramList.Add(Hist)
- else // ... otherwise delete it
- Hist.Free;
- end;
- (*
- ** Next process images that have a local color map
- *)
- for i := 0 to FImage.Images.Count-1 do
- if (FImage.Images[i].ColorMap.Count > 0) and (not FImage.Images[i].Empty) then
- begin
- Hist := THistogram.Create(FImage.Images[i].ColorMap);
- FHistogramList.Add(Hist);
- // Note: Do not change order of statements. Shortcircuit evaluation not desired!
- FUseTransparency := Hist.ProcessSubImage(FImage.Images[i]) or FUseTransparency;
- end;
- end;
- procedure TColorMapOptimizer.MergeColors;
- var
- Entry, SameEntry : POptimizeEntry;
- i : integer;
- begin
- (*
- ** Sort by color value
- *)
- FHistogram.Sort(CompareColor);
- (*
- ** Merge same colors
- *)
- SameEntry := POptimizeEntry(FHistogram[0]);
- for i := 1 to FHistogram.Count-1 do
- begin
- Entry := POptimizeEntry(FHistogram[i]);
- ASSERT(Entry^.Count > 0, 'Unused entry exported from THistogram');
- if (Entry^.Color.Value = SameEntry^.Color.Value) then
- begin
- // Transfer usage count to first entry
- inc(SameEntry^.Count, Entry^.Count);
- Entry^.Count := 0; // Use 0 to signal merged entry
- Entry^.Color.SameAs := SameEntry; // Point to master
- end else
- SameEntry := Entry;
- end;
- end;
- procedure TColorMapOptimizer.MapColors;
- var
- i, j : integer;
- Delta, BestDelta : integer;
- BestIndex : integer;
- MaxColors : integer;
- begin
- (*
- ** Sort by usage count
- *)
- FHistogram.Sort(CompareCount);
- (*
- ** Handle transparency
- *)
- if (FUseTransparency) then
- MaxColors := 255
- else
- MaxColors := 256;
- (*
- ** Determine number of colors used (max 256)
- *)
- FFinalCount := FHistogram.Count;
- for i := 0 to FFinalCount-1 do
- if (i >= MaxColors) or (POptimizeEntry(FHistogram[i])^.Count = 0) then
- begin
- FFinalCount := i;
- break;
- end;
- (*
- ** Build color map and reverse map for final entries
- *)
- for i := 0 to FFinalCount-1 do
- begin
- POptimizeEntry(FHistogram[i])^.NewIndex := i;
- FColorMap[i] := POptimizeEntry(FHistogram[i])^.Color.Color;
- end;
- (*
- ** Map colors > 256 to colors <= 256 and build NewIndex color map
- *)
- for i := FFinalCount to FHistogram.Count-1 do
- with POptimizeEntry(FHistogram[i])^ do
- begin
- // Entries with a usage count of -1 is unused
- ASSERT(Count <> -1, 'Internal error: Unused entry exported');
- // Entries with a usage count of 0 has been merged with another entry
- if (Count = 0) then
- begin
- // Use mapping of master entry
- ASSERT(Color.SameAs.NewIndex < 256, 'Internal error: Mapping to unmapped color');
- NewIndex := Color.SameAs.NewIndex;
- end else
- begin
- // Search for entry with nearest color value
- BestIndex := 0;
- BestDelta := 255*3;
- for j := 0 to FFinalCount-1 do
- begin
- Delta := ABS((POptimizeEntry(FHistogram[j])^.Color.Color.Red - Color.Color.Red) +
- (POptimizeEntry(FHistogram[j])^.Color.Color.Green - Color.Color.Green) +
- (POptimizeEntry(FHistogram[j])^.Color.Color.Blue - Color.Color.Blue));
- if (Delta < BestDelta) then
- begin
- BestDelta := Delta;
- BestIndex := j;
- end;
- end;
- NewIndex := POptimizeEntry(FHistogram[BestIndex])^.NewIndex;;
- end;
- end;
- (*
- ** Add transparency color to new color map
- *)
- if (FUseTransparency) then
- begin
- FNewTransparentColorIndex := FFinalCount;
- FColorMap[FFinalCount].Red := 0;
- FColorMap[FFinalCount].Green := 0;
- FColorMap[FFinalCount].Blue := 0;
- inc(FFinalCount);
- end;
- end;
- procedure TColorMapOptimizer.ReplaceColorMaps;
- var
- i : integer;
- begin
- // Zap all local color maps
- for i := 0 to FImage.Images.Count-1 do
- if (FImage.Images[i].ColorMap <> nil) then
- FImage.Images[i].ColorMap.Clear;
- // Store optimized global color map
- FImage.GlobalColorMap.ImportColorMap(FColorMap, FFinalCount);
- FImage.GlobalColorMap.Optimized := True;
- end;
- procedure TColorMapOptimizer.Optimize;
- var
- Total : integer;
- i, j : integer;
- begin
- // Stop all painters during optimize...
- FImage.PaintStop;
- // ...and prevent any new from starting while we are doing our thing
- FImage.Painters.LockList;
- try
- (*
- ** Process all sub images
- *)
- ProcessImage;
- // Prune histograms and calculate total number of colors
- Total := 0;
- for i := 0 to FHistogramList.Count-1 do
- inc(Total, THistogram(FHistogramList[i]).Prune);
- // Allocate global histogram
- FHistogram.Clear;
- FHistogram.Capacity := Total;
- // Move data pointers from local histograms to global histogram
- for i := 0 to FHistogramList.Count-1 do
- with THistogram(FHistogramList[i]) do
- for j := 0 to Count-1 do
- begin
- ASSERT(POptimizeEntry(List[j])^.Count > 0, 'Unused entry exported from THistogram');
- FHistogram.Add(List[j]);
- end;
- (*
- ** Merge same colors
- *)
- MergeColors;
- (*
- ** Build color map and NewIndex map for final entries
- *)
- MapColors;
- (*
- ** Replace local colormaps with global color map
- *)
- ReplaceColorMaps;
- (*
- ** Process images for each color map
- *)
- for i := 0 to FHistogramList.Count-1 do
- THistogram(FHistogramList[i]).MapImages(FUseTransparency, FNewTransparentColorIndex);
- (*
- ** Delete the frame's old bitmaps and palettes
- *)
- for i := 0 to FImage.Images.Count-1 do
- begin
- FImage.Images[i].HasBitmap := False;
- FImage.Images[i].Palette := 0;
- end;
- finally
- FImage.Painters.UnlockList;
- end;
- end;
- ////////////////////////////////////////////////////////////////////////////////
- //
- // TGIFImage
- //
- ////////////////////////////////////////////////////////////////////////////////
- constructor TGIFImage.Create;
- begin
- inherited Create;
- FImages := TGIFImageList.Create(self);
- FHeader := TGIFHeader.Create(self);
- FPainters := TThreadList.Create;
- FGlobalPalette := 0;
- // Load defaults
- FDrawOptions := GIFImageDefaultDrawOptions;
- ColorReduction := GIFImageDefaultColorReduction;
- FReductionBits := GIFImageDefaultColorReductionBits;
- FDitherMode := GIFImageDefaultDitherMode;
- FCompression := GIFImageDefaultCompression;
- FThreadPriority := GIFImageDefaultThreadPriority;
- FAnimationSpeed := GIFImageDefaultAnimationSpeed;
- FDrawBackgroundColor := clNone;
- IsDrawing := False;
- IsInsideGetPalette := False;
- FForceFrame := -1; // 2004.03.09
- NewImage;
- end;
- destructor TGIFImage.Destroy;
- var
- i : integer;
- begin
- PaintStop;
- with FPainters.LockList do
- try
- for i := Count-1 downto 0 do
- TGIFPainter(Items[i]).FImage := nil;
- finally
- FPainters.UnLockList;
- end;
- Clear;
- FPainters.Free;
- FImages.Free;
- FHeader.Free;
- inherited Destroy;
- end;
- procedure TGIFImage.Clear;
- begin
- PaintStop;
- FreeBitmap;
- FImages.Clear;
- FHeader.ColorMap.Clear;
- FHeader.Height := 0;
- FHeader.Width := 0;
- FHeader.Prepare;
- Palette := 0;
- end;
- procedure TGIFImage.NewImage;
- begin
- Clear;
- end;
- function TGIFImage.GetVersion: TGIFVersion;
- var
- v : TGIFVersion;
- i : integer;
- begin
- Result := gvUnknown;
- for i := 0 to FImages.Count-1 do
- begin
- v := FImages[i].Version;
- if (v > Result) then
- Result := v;
- if (v >= high(TGIFVersion)) then
- break;
- end;
- end;
- function TGIFImage.GetColorResolution: integer;
- var
- i : integer;
- begin
- Result := FHeader.ColorResolution;
- for i := 0 to FImages.Count-1 do
- if (FImages[i].ColorResolution > Result) then
- Result := FImages[i].ColorResolution;
- end;
- function TGIFImage.GetBitsPerPixel: integer;
- var
- i : integer;
- begin
- Result := FHeader.BitsPerPixel;
- for i := 0 to FImages.Count-1 do
- if (FImages[i].BitsPerPixel > Result) then
- Result := FImages[i].BitsPerPixel;
- end;
- function TGIFImage.GetBackgroundColorIndex: BYTE;
- begin
- Result := FHeader.BackgroundColorIndex;
- end;
- procedure TGIFImage.SetBackgroundColorIndex(const Value: BYTE);
- begin
- FHeader.BackgroundColorIndex := Value;
- end;
- function TGIFImage.GetBackgroundColor: TColor;
- begin
- Result := FHeader.BackgroundColor;
- end;
- procedure TGIFImage.SetBackgroundColor(const Value: TColor);
- begin
- FHeader.BackgroundColor := Value;
- end;
- function TGIFImage.GetAspectRatio: BYTE;
- begin
- Result := FHeader.AspectRatio;
- end;
- procedure TGIFImage.SetAspectRatio(const Value: BYTE);
- begin
- FHeader.AspectRatio := Value;
- end;
- procedure TGIFImage.SetDrawOptions(Value: TGIFDrawOptions);
- begin
- if (FDrawOptions = Value) then
- exit;
- if (DrawPainter <> nil) then
- DrawPainter.Stop;
- FDrawOptions := Value;
- // Zap all bitmaps
- Pack;
- Changed(self);
- end;
- function TGIFImage.GetAnimate: Boolean;
- begin // 2002.07.07
- Result:= goAnimate in DrawOptions;
- end;
- procedure TGIFImage.SetAnimate(const Value: Boolean);
- begin // 2002.07.07
- if Value then
- DrawOptions:= DrawOptions + [goAnimate]
- else
- DrawOptions:= DrawOptions - [goAnimate];
- end;
- procedure TGIFImage.SetForceFrame(const Value: Integer);
- begin // 2004.03.09
- FForceFrame := Value;
- Changed(Self);
- end;
- procedure TGIFImage.SetAnimationSpeed(Value: integer);
- begin
- if (Value < 0) then
- Value := 0
- else if (Value > 1000) then
- Value := 1000;
- if (Value <> FAnimationSpeed) then
- begin
- FAnimationSpeed := Value;
- // Use the FPainters threadlist to protect FDrawPainter from being modified
- // by the thread while we mess with it
- with FPainters.LockList do
- try
- if (FDrawPainter <> nil) then
- FDrawPainter.AnimationSpeed := FAnimationSpeed;
- finally
- // Release the lock on FPainters to let paint thread kill itself
- FPainters.UnLockList;
- end;
- end;
- end;
- procedure TGIFImage.SetReductionBits(Value: integer);
- begin
- if (Value < 3) or (Value > 8) then
- Error(sInvalidBitSize);
- FReductionBits := Value;
- end;
- procedure TGIFImage.OptimizeColorMap;
- var
- ColorMapOptimizer : TColorMapOptimizer;
- begin
- ColorMapOptimizer := TColorMapOptimizer.Create(self);
- try
- ColorMapOptimizer.Optimize;
- finally
- ColorMapOptimizer.Free;
- end;
- end;
- procedure TGIFImage.Optimize(Options: TGIFOptimizeOptions;
- ColorReduction: TColorReduction; DitherMode: TDitherMode;
- ReductionBits: integer);
- var
- i ,
- j : integer;
- Delay : integer;
- GCE : TGIFGraphicControlExtension;
- ThisRect ,
- NextRect ,
- MergeRect : TRect;
- Prog ,
- MaxProg : integer;
- function Scan(Buf: PChar; Value: Byte; Count: integer): boolean; assembler;
- asm
- PUSH EDI
- MOV EDI, Buf
- MOV ECX, Count
- MOV AL, Value
- REPNE SCASB
- MOV EAX, False
- JNE @@1
- MOV EAX, True
- @@1:POP EDI
- end;
- begin
- if (Empty) then
- exit;
- // Stop all painters during optimize...
- PaintStop;
- // ...and prevent any new from starting while we are doing our thing
- FPainters.LockList;
- try
- Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressOptimizing);
- try
- Prog := 0;
- MaxProg := Images.Count*6;
- // Sort color map by usage and remove unused entries
- if (ooColorMap in Options) then
- begin
- // Optimize global color map
- if (GlobalColorMap.Count > 0) then
- GlobalColorMap.Optimize;
- // Optimize local color maps
- for i := 0 to Images.Count-1 do
- begin
- inc(Prog);
- if (Images[i].ColorMap.Count > 0) then
- begin
- Images[i].ColorMap.Optimize;
- Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False,
- Rect(0,0,0,0), sProgressOptimizing);
- end;
- end;
- end;
- // Remove passive elements, pass 1
- if (ooCleanup in Options) then
- begin
- // Check for transparency flag without any transparent pixels
- for i := 0 to Images.Count-1 do
- begin
- inc(Prog);
- if (Images[i].Transparent) then
- begin
- if not(Scan(Images[i].Data,
- Images[i].GraphicControlExtension.TransparentColorIndex,
- Images[i].DataSize)) then
- begin
- Images[i].GraphicControlExtension.Transparent := False;
- Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False,
- Rect(0,0,0,0), sProgressOptimizing);
- end;
- end;
- end;
- // Change redundant disposal modes
- for i := 0 to Images.Count-2 do
- begin
- inc(Prog);
- if (Images[i].GraphicControlExtension <> nil) and
- (Images[i].GraphicControlExtension.Disposal in [dmPrevious, dmBackground]) and
- (not Images[i+1].Transparent) then
- begin
- ThisRect := Images[i].BoundsRect;
- NextRect := Images[i+1].BoundsRect;
- if (not IntersectRect(MergeRect, ThisRect, NextRect)) then
- continue;
- // If the next frame completely covers the current frame,
- // change the disposal mode to dmNone
- if (EqualRect(MergeRect, NextRect)) then
- Images[i].GraphicControlExtension.Disposal := dmNone;
- Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False,
- Rect(0,0,0,0), sProgressOptimizing);
- end;
- end;
- end else
- inc(Prog, 2*Images.Count);
- // Merge layers of equal pixels (remove redundant pixels)
- if (ooMerge in Options) then
- begin
- // Merge from last to first to avoid intefering with merge
- for i := Images.Count-1 downto 1 do
- begin
- inc(Prog);
- j := i-1;
- // If the "previous" frames uses dmPrevious disposal mode, we must
- // instead merge with the frame before the previous
- while (j > 0) and
- ((Images[j].GraphicControlExtension <> nil) and
- (Images[j].GraphicControlExtension.Disposal = dmPrevious)) do
- dec(j);
- // Merge
- Images[i].Merge(Images[j]);
- Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False,
- Rect(0,0,0,0), sProgressOptimizing);
- end;
- end else
- inc(Prog, Images.Count);
- // Crop transparent areas
- if (ooCrop in Options) then
- begin
- for i := Images.Count-1 downto 0 do
- begin
- inc(Prog);
- if (not Images[i].Empty) and (Images[i].Transparent) then
- begin
- // Remember frames delay in case frame is deleted
- Delay := Images[i].GraphicControlExtension.Delay;
- // Crop
- Images[i].Crop;
- // If the frame was completely transparent we remove it
- if (Images[i].Empty) then
- begin
- // Transfer delay to previous frame in case frame was deleted
- if (i > 0) and (Images[i-1].Transparent) then
- Images[i-1].GraphicControlExtension.Delay :=
- Images[i-1].GraphicControlExtension.Delay + Delay;
- Images.Delete(i);
- end;
- Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False,
- Rect(0,0,0,0), sProgressOptimizing);
- end;
- end;
- end else
- inc(Prog, Images.Count);
- // Remove passive elements, pass 2
- inc(Prog, Images.Count);
- if (ooCleanup in Options) then
- begin
- for i := Images.Count-1 downto 0 do
- begin
- // Remove comments and application extensions
- for j := Images[i].Extensions.Count-1 downto 0 do
- if (Images[i].Extensions[j] is TGIFCommentExtension) or
- (Images[i].Extensions[j] is TGIFTextExtension) or
- (Images[i].Extensions[j] is TGIFUnknownAppExtension) or
- ((Images[i].Extensions[j] is TGIFAppExtNSLoop) and
- ((i > 0) or (Images.Count = 1))) then
- Images[i].Extensions.Delete(j);
- if (Images[i].GraphicControlExtension <> nil) then
- begin
- GCE := Images[i].GraphicControlExtension;
- // Zap GCE if all of the following are true:
- // * No delay or only one image
- // * Not transparent
- // * No prompt
- // * No disposal or only one image
- if ((GCE.Delay = 0) or (Images.Count = 1)) and
- (not GCE.Transparent) and
- (not GCE.UserInput) and
- ((GCE.Disposal in [dmNone, dmNoDisposal]) or (Images.Count = 1)) then
- begin
- GCE.Free;
- end;
- end;
- // Zap frame if it has become empty
- if (Images[i].Empty) and (Images[i].Extensions.Count = 0) then
- Images[i].Free;
- end;
- Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False,
- Rect(0,0,0,0), sProgressOptimizing);
- end else
- // Reduce color depth
- if (ooReduceColors in Options) then
- begin
- if (ColorReduction = rmPalette) then
- Error(sInvalidReduction);
- { TODO -oanme -cFeature : Implement ooReduceColors option. }
- // Not implemented!
- end;
- finally
- if ExceptObject = nil then
- i := 100
- else
- i := 0;
- Progress(Self, psEnding, i, False, Rect(0,0,0,0), sProgressOptimizing);
- end;
- finally
- FPainters.UnlockList;
- end;
- end;
- procedure TGIFImage.Pack;
- var
- i : integer;
- begin
- // Zap bitmaps and palettes
- FreeBitmap;
- Palette := 0;
- for i := 0 to FImages.Count-1 do
- begin
- FImages[i].Bitmap := nil;
- FImages[i].Palette := 0;
- end;
- // Only pack if no global colormap and a single image
- if (FHeader.ColorMap.Count > 0) or (FImages.Count <> 1) then
- exit;
- // Copy local colormap to global
- FHeader.ColorMap.Assign(FImages[0].ColorMap);
- // Zap local colormap
- FImages[0].ColorMap.Clear;
- end;
- procedure TGIFImage.SaveToStream(Stream: TStream);
- var
- n : Integer;
- begin
- Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressSaving);
- try
- // Write header
- FHeader.SaveToStream(Stream);
- // Write images
- FImages.SaveToStream(Stream);
- // Write trailer
- with TGIFTrailer.Create(self) do
- try
- SaveToStream(Stream);
- finally
- Free;
- end;
- finally
- if ExceptObject = nil then
- n := 100
- else
- n := 0;
- Progress(Self, psEnding, n, True, Rect(0,0,0,0), sProgressSaving);
- end;
- end;
- procedure TGIFImage.LoadFromStream(Stream: TStream);
- var
- n : Integer;
- Position : integer;
- begin
- Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressLoading);
- try
- // Zap old image
- Clear;
- Position := Stream.Position;
- try
- // Read header
- FHeader.LoadFromStream(Stream);
- // Read images
- FImages.LoadFromStream(Stream, self);
- // Read trailer
- with TGIFTrailer.Create(self) do
- try
- LoadFromStream(Stream);
- finally
- Free;
- end;
- except
- // Restore stream position in case of error.
- // Not required, but "a nice thing to do"
- Stream.Position := Position;
- raise;
- end;
- finally
- if ExceptObject = nil then
- n := 100
- else
- n := 0;
- Progress(Self, psEnding, n, True, Rect(0,0,0,0), sProgressLoading);
- end;
- end;
- procedure TGIFImage.LoadFromResourceName(Instance: THandle; const ResName: String);
- // 2002.07.07
- var
- Stream: TCustomMemoryStream;
- begin
- Stream := TResourceStream.Create(Instance, ResName, RT_RCDATA);
- try
- LoadFromStream(Stream);
- finally
- Stream.Free;
- end;
- end;
- function TGIFImage.GetBitmap: TBitmap;
- begin
- if not(Empty) then
- begin
- Result := FBitmap;
- if (Result <> nil) then
- exit;
- FBitmap := TBitmap.Create;
- Result := FBitmap;
- FBitmap.OnChange := Changed;
- // Use first image as default
- if (Images.Count > 0) then
- begin
- if (Images[0].Width = Width) and (Images[0].Height = Height) then
- begin
- // Use first image as it has same dimensions
- FBitmap.Assign(Images[0].Bitmap);
- end else
- begin
- // Draw first image on bitmap
- FBitmap.Palette := CopyPalette(Palette);
- FBitmap.Height := Height;
- FBitmap.Width := Width;
- Images[0].Draw(FBitmap.Canvas, FBitmap.Canvas.ClipRect, False, False);
- end;
- end;
- end else
- Result := nil
- end;
- // Create a new (empty) bitmap
- function TGIFImage.NewBitmap: TBitmap;
- begin
- Result := FBitmap;
- if (Result <> nil) then
- exit;
- FBitmap := TBitmap.Create;
- Result := FBitmap;
- FBitmap.OnChange := Changed;
- // Draw first image on bitmap
- FBitmap.Palette := CopyPalette(Palette);
- FBitmap.Height := Height;
- FBitmap.Width := Width;
- end;
- procedure TGIFImage.FreeBitmap;
- begin
- if (DrawPainter <> nil) then
- DrawPainter.Stop;
- if (FBitmap <> nil) then
- begin
- FBitmap.Free;
- FBitmap := nil;
- end;
- end;
- function TGIFImage.Add(Source: TPersistent): integer;
- var
- Image : TGIFSubImage;
- begin
- Image := nil; // To avoid compiler warning - not needed.
- if (Source is TGraphic) then
- begin
- Image := TGIFSubImage.Create(self);
- try
- Image.Assign(Source);
- // ***FIXME*** Documentation should explain the inconsistency here:
- // TGIFimage does not take ownership of Source after TGIFImage.Add() and
- // therefore does not delete Source.
- except
- Image.Free;
- raise;
- end;
- end else
- if (Source is TGIFSubImage) then
- Image := TGIFSubImage(Source)
- else
- Error(sUnsupportedClass);
- Result := FImages.Add(Image);
- FreeBitmap;
- Changed(self);
- end;
- function TGIFImage.GetEmpty: Boolean;
- begin
- Result := (FImages.Count = 0);
- end;
- function TGIFImage.GetHeight: Integer;
- begin
- Result := FHeader.Height;
- end;
- function TGIFImage.GetWidth: Integer;
- begin
- Result := FHeader.Width;
- end;
- function TGIFImage.GetIsTransparent: Boolean;
- var
- i : integer;
- begin
- Result := False;
- for i := 0 to Images.Count-1 do
- if (Images[i].GraphicControlExtension <> nil) and
- (Images[i].GraphicControlExtension.Transparent) then
- begin
- Result := True;
- exit;
- end;
- end;
- function TGIFImage.Equals(Graphic: TGraphic): Boolean;
- begin
- Result := (Graphic = self);
- end;
- function TGIFImage.GetPalette: HPALETTE;
- begin
- // Check for recursion
- // (TGIFImage.GetPalette->TGIFSubImage.GetPalette->TGIFImage.GetPalette etc...)
- if (IsInsideGetPalette) then
- Error(sNoColorTable);
- IsInsideGetPalette := True;
- try
- Result := 0;
- if (FBitmap <> nil) and (FBitmap.Palette <> 0) then
- // Use bitmaps own palette if possible
- Result := FBitmap.Palette
- else if (FGlobalPalette <> 0) then
- // Or a previously exported global palette
- Result := FGlobalPalette
- else if (DoDither) then
- begin
- // or create a new dither palette
- FGlobalPalette := WebPalette;
- Result := FGlobalPalette;
- end else
- if (FHeader.ColorMap.Count > 0) then
- begin
- // or create a new if first time
- FGlobalPalette := FHeader.ColorMap.ExportPalette;
- Result := FGlobalPalette;
- end else
- if (FImages.Count > 0) then
- // This can cause a recursion if no global palette exist and image[0]
- // hasn't got one either. Checked by the IsInsideGetPalette semaphor.
- Result := FImages[0].Palette;
- finally
- IsInsideGetPalette := False;
- end;
- end;
- procedure TGIFImage.SetPalette(Value: HPalette);
- var
- NeedNewBitmap : boolean;
- begin
- if (Value <> FGlobalPalette) then
- begin
- // Zap old palette
- if (FGlobalPalette <> 0) then
- DeleteObject(FGlobalPalette);
- // Zap bitmap unless new palette is same as bitmaps own
- NeedNewBitmap := (FBitmap <> nil) and (Value <> FBitmap.Palette);
- // Use new palette
- FGlobalPalette := Value;
- if (NeedNewBitmap) then
- begin
- // Need to create new bitmap and repaint
- FreeBitmap;
- PaletteModified := True;
- Changed(Self);
- end;
- end;
- end;
- // Obsolete
- // procedure TGIFImage.Changed(Sender: TObject);
- // begin
- // inherited Changed(Sender);
- // end;
- procedure TGIFImage.SetHeight(Value: Integer);
- var
- i : integer;
- begin
- for i := 0 to Images.Count-1 do
- if (Images[i].Top + Images[i].Height > Value) then
- Error(sBadHeight);
- if (Value <> Header.Height) then
- begin
- Header.Height := Value;
- FreeBitmap;
- Changed(self);
- end;
- end;
- procedure TGIFImage.SetWidth(Value: Integer);
- var
- i : integer;
- begin
- for i := 0 to Images.Count-1 do
- if (Images[i].Left + Images[i].Width > Value) then
- Error(sBadWidth);
- if (Value <> Header.Width) then
- begin
- Header.Width := Value;
- FreeBitmap;
- Changed(self);
- end;
- end;
- procedure TGIFImage.WriteData(Stream: TStream);
- begin
- if (GIFImageOptimizeOnStream) then
- Optimize([ooCrop, ooMerge, ooCleanup, ooColorMap, ooReduceColors], rmNone, dmNearest, 8);
- inherited WriteData(Stream);
- end;
- procedure TGIFImage.AssignTo(Dest: TPersistent);
- begin
- if (Dest is TBitmap) then
- Dest.Assign(Bitmap)
- else
- inherited AssignTo(Dest);
- end;
- { TODO 1 -oanme -cImprovement : Better handling of TGIFImage.Assign(Empty TBitmap). }
- procedure TGIFImage.Assign(Source: TPersistent);
- var
- i : integer;
- Image : TGIFSubImage;
- begin
- if (Source = self) then
- exit;
- if (Source = nil) then
- begin
- Clear;
- end else
- //
- // TGIFImage import
- //
- if (Source is TGIFImage) then
- begin
- Clear;
- // Temporarily copy event handlers to be able to generate progress events
- // during the copy and handle copy errors
- OnProgress := TGIFImage(Source).OnProgress;
- try
- FOnWarning := TGIFImage(Source).OnWarning;
- Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressCopying);
- try
- FHeader.Assign(TGIFImage(Source).Header);
- FThreadPriority := TGIFImage(Source).ThreadPriority;
- FDrawBackgroundColor := TGIFImage(Source).DrawBackgroundColor;
- FDrawOptions := TGIFImage(Source).DrawOptions;
- FColorReduction := TGIFImage(Source).ColorReduction;
- FDitherMode := TGIFImage(Source).DitherMode;
- FForceFrame := TGIFImage(Source).ForceFrame; // 2004.03.09
- // 2002.07.07 ->
- FOnWarning:= TGIFImage(Source).FOnWarning;
- FOnStartPaint:= TGIFImage(Source).FOnStartPaint;
- FOnPaint:= TGIFImage(Source).FOnPaint;
- FOnEndPaint:= TGIFImage(Source).FOnEndPaint;
- FOnAfterPaint:= TGIFImage(Source).FOnAfterPaint;
- FOnLoop:= TGIFImage(Source).FOnLoop;
- // 2002.07.07 <-
- for i := 0 to TGIFImage(Source).Images.Count-1 do
- begin
- Image := TGIFSubImage.Create(self);
- Image.Assign(TGIFImage(Source).Images[i]);
- Add(Image);
- Progress(Self, psRunning, MulDiv((i+1), 100, TGIFImage(Source).Images.Count),
- False, Rect(0,0,0,0), sProgressCopying);
- end;
- finally
- if ExceptObject = nil then
- i := 100
- else
- i := 0;
- Progress(Self, psEnding, i, False, Rect(0,0,0,0), sProgressCopying);
- end;
- finally
- // Reset event handlers
- FOnWarning := nil;
- OnProgress := nil;
- end;
- end else
- //
- // Import via TGIFSubImage.Assign
- //
- begin
- Clear;
- Image := TGIFSubImage.Create(self);
- try
- Image.Assign(Source);
- Add(Image);
- except
- on E: EConvertError do
- begin
- Image.Free;
- // Unsupported format - fall back to Source.AssignTo
- inherited Assign(Source);
- end;
- else
- // Unknown conversion error
- Image.Free;
- raise;
- end;
- end;
- end;
- procedure TGIFImage.LoadFromClipboardFormat(AFormat: Word; AData: THandle;
- APalette: HPALETTE);
- {$IFDEF REGISTER_TGIFIMAGE}
- var
- Size : Longint;
- Buffer : Pointer;
- Stream : TMemoryStream;
- Bmp : TBitmap;
- {$ENDIF} // 2002.07.07
- begin // 2002.07.07
- {$IFDEF REGISTER_TGIFIMAGE} // 2002.07.07
- if (AData = 0) then
- AData := GetClipboardData(AFormat);
- if (AData <> 0) and (AFormat = CF_GIF) then
- begin
- // Get size and pointer to data
- Size := GlobalSize(AData);
- Buffer := GlobalLock(AData);
- try
- Stream := TMemoryStream.Create;
- try
- // Copy data to a stream
- Stream.SetSize(Size);
- Move(Buffer^, Stream.Memory^, Size);
- // Load GIF from stream
- LoadFromStream(Stream);
- finally
- Stream.Free;
- end;
- finally
- GlobalUnlock(AData);
- end;
- end else
- if (AData <> 0) and (AFormat = CF_BITMAP) then
- begin
- // No GIF on clipboard - try loading a bitmap instead
- Bmp := TBitmap.Create;
- try
- Bmp.LoadFromClipboardFormat(AFormat, AData, APalette);
- Assign(Bmp);
- finally
- Bmp.Free;
- end;
- end else
- Error(sUnknownClipboardFormat);
- {$ELSE} // 2002.07.07
- Error(sGIFToClipboard); // 2002.07.07
- {$ENDIF} // 2002.07.07
- end;
- procedure TGIFImage.SaveToClipboardFormat(var AFormat: Word; var AData: THandle;
- var APalette: HPALETTE);
- {$IFDEF REGISTER_TGIFIMAGE}
- var
- Stream : TMemoryStream;
- Data : THandle;
- Buffer : Pointer;
- {$ENDIF} // 2002.07.07
- begin // 2002.07.07
- {$IFDEF REGISTER_TGIFIMAGE} // 2002.07.07
- if (Empty) then
- exit;
- // First store a bitmap version on the clipboard...
- Bitmap.SaveToClipboardFormat(AFormat, AData, APalette);
- // ...then store a GIF
- Stream := TMemoryStream.Create;
- try
- // Save the GIF to a memory stream
- SaveToStream(Stream);
- Stream.Position := 0;
- // Allocate some memory for the GIF data
- Data := GlobalAlloc(HeapAllocFlags, Stream.Size);
- try
- if (Data <> 0) then
- begin
- Buffer := GlobalLock(Data);
- try
- // Copy GIF data from stream memory to clipboard memory
- Move(Stream.Memory^, Buffer^, Stream.Size);
- finally
- GlobalUnlock(Data);
- end;
- // Transfer data to clipboard
- if (SetClipboardData(CF_GIF, Data) = 0) then
- Error(sFailedPaste);
- end;
- except
- GlobalFree(Data);
- raise;
- end;
- finally
- Stream.Free;
- end;
- {$ELSE} // 2002.07.07
- Error(sGIFToClipboard); // 2002.07.07
- {$ENDIF} // 2002.07.07
- end;
- function TGIFImage.GetColorMap: TGIFColorMap;
- begin
- Result := FHeader.ColorMap;
- end;
- function TGIFImage.GetDoDither: boolean;
- begin
- Result := (goDither in DrawOptions) and
- (((goAutoDither in DrawOptions) and DoAutoDither) or
- not(goAutoDither in DrawOptions));
- end;
- {$IFDEF VER9x}
- procedure TGIFImage.Progress(Sender: TObject; Stage: TProgressStage;
- PercentDone: Byte; RedrawNow: Boolean; const R: TRect; const Msg: string);
- begin
- if Assigned(FOnProgress) then
- FOnProgress(Sender, Stage, PercentDone, RedrawNow, R, Msg);
- end;
- {$ENDIF}
- procedure TGIFImage.StopDraw;
- {$IFNDEF VER14_PLUS} // 2001.07.23
- var
- Msg : TMsg;
- ThreadWindow : HWND;
- {$ENDIF} // 2001.07.23
- begin
- repeat
- // Use the FPainters threadlist to protect FDrawPainter from being modified
- // by the thread while we mess with it
- with FPainters.LockList do
- try
- if (FDrawPainter = nil) then
- break;
- // Tell thread to terminate
- FDrawPainter.Stop;
- // No need to wait for "thread" to terminate if running in main thread
- if not(goAsync in FDrawPainter.DrawOptions) then
- break;
- finally
- // Release the lock on FPainters to let paint thread kill itself
- FPainters.UnLockList;
- end;
- {$IFDEF VER14_PLUS}
- // 2002.07.07
- if (GetCurrentThreadID = MainThreadID) then
- while CheckSynchronize do {loop};
- {$ELSE}
- // Process Messages to make Synchronize work
- // (Instead of Application.ProcessMessages)
- //{$IFDEF VER14_PLUS} // 2001.07.23
- // Break; // 2001.07.23
- // Sleep(0); // Yield // 2001.07.23
- //{$ELSE} // 2001.07.23
- ThreadWindow := FindWindow('TThreadWindow', nil);
- while PeekMessage(Msg, ThreadWindow, CM_DESTROYWINDOW, CM_EXECPROC, PM_REMOVE) do
- begin
- if (Msg.Message <> WM_QUIT) then
- begin
- TranslateMessage(Msg);
- DispatchMessage(Msg);
- end else
- begin
- PostQuitMessage(Msg.WParam);
- exit;
- end;
- end;
- {$ENDIF} // 2001.07.23
- Sleep(0); // Yield
- until (False);
- FreeBitmap;
- end;
- procedure TGIFImage.Draw(ACanvas: TCanvas; const Rect: TRect);
- var
- Canvas : TCanvas;
- DestRect : TRect;
- {$IFNDEF VER14_PLUS} // 2001.07.23
- Msg : TMsg;
- ThreadWindow : HWND;
- {$ENDIF} // 2001.07.23
- procedure DrawTile(Rect: TRect; Bitmap: TBitmap);
- var
- Tile : TRect;
- begin
- if (goTile in FDrawOptions) then
- begin
- // Note: This design does not handle transparency correctly!
- Tile.Left := Rect.Left;
- Tile.Right := Tile.Left + Width;
- while (Tile.Left < Rect.Right) do
- begin
- Tile.Top := Rect.Top;
- Tile.Bottom := Tile.Top + Height;
- while (Tile.Top < Rect.Bottom) do
- begin
- ACanvas.StretchDraw(Tile, Bitmap);
- Tile.Top := Tile.Top + Height;
- Tile.Bottom := Tile.Top + Height;
- end;
- Tile.Left := Tile.Left + Width;
- Tile.Right := Tile.Left + Width;
- end;
- end else
- ACanvas.StretchDraw(Rect, Bitmap);
- end;
- begin
- // Prevent recursion(s(s(s)))
- if (IsDrawing) or (FImages.Count = 0) then
- exit;
- IsDrawing := True;
- try
- // Copy bitmap to canvas if we are already drawing
- // (or have drawn but are finished)
- if (FImages.Count = 1) or // Only one image
- (not (goAnimate in FDrawOptions)) then // Don't animate
- begin
- // 2004.03.09 ->
- if (FForceFrame >= 0) and (FForceFrame < FImages.Count) then
- FImages[FForceFrame].Draw(ACanvas, Rect, (goTransparent in FDrawOptions), (goTile in FDrawOptions))
- else
- // 2004.03.09 <-
- FImages[0].Draw(ACanvas, Rect, (goTransparent in FDrawOptions), (goTile in FDrawOptions));
- exit;
- end else
- if (FBitmap <> nil) and not(goDirectDraw in FDrawOptions) then
- begin
- DrawTile(Rect, Bitmap);
- exit;
- end;
- // Use the FPainters threadlist to protect FDrawPainter from being modified
- // by the thread while we mess with it
- with FPainters.LockList do
- try
- // If we are already painting on the canvas in goDirectDraw mode
- // and at the same location, just exit and let the painter do
- // its thing when it's ready
- if (FDrawPainter <> nil) and (FDrawPainter.Canvas = ACanvas) and
- EqualRect(FDrawPainter.Rect, Rect) then
- exit;
- // Kill the current paint thread
- StopDraw;
- if not(goDirectDraw in FDrawOptions) then
- begin
- // Create a bitmap to draw on
- NewBitmap;
- Canvas := FBitmap.Canvas;
- DestRect := Canvas.ClipRect;
- // Initialize bitmap canvas with background image
- Canvas.CopyRect(DestRect, ACanvas, Rect);
- end else
- begin
- Canvas := ACanvas;
- DestRect := Rect;
- end;
- // Create new paint thread
- InternalPaint(@FDrawPainter, Canvas, DestRect, FDrawOptions);
- if (FDrawPainter <> nil) then
- begin
- // Launch thread
- FDrawPainter.Start;
- if not(goDirectDraw in FDrawOptions) then
- begin
- {$IFDEF VER14_PLUS}
- // 2002.07.07
- while (FDrawPainter <> nil) and (not FDrawPainter.Terminated) and
- (not FDrawPainter.Started) do
- begin
- if not CheckSynchronize then
- Sleep(0); // Yield
- end;
- {$ELSE}
- //{$IFNDEF VER14_PLUS} // 2001.07.23
- ThreadWindow := FindWindow('TThreadWindow', nil);
- // Wait for thread to render first frame
- while (FDrawPainter <> nil) and (not FDrawPainter.Terminated) and
- (not FDrawPainter.Started) do
- // Process Messages to make Synchronize work
- // (Instead of Application.ProcessMessages)
- if PeekMessage(Msg, ThreadWindow, CM_DESTROYWINDOW, CM_EXECPROC, PM_REMOVE) then
- begin
- if (Msg.Message <> WM_QUIT) then
- begin
- TranslateMessage(Msg);
- DispatchMessage(Msg);
- end else
- begin
- PostQuitMessage(Msg.WParam);
- exit;
- end;
- end else
- Sleep(0); // Yield
- {$ENDIF} // 2001.07.23
- // Draw frame to destination
- DrawTile(Rect, Bitmap);
- end;
- end;
- finally
- FPainters.UnLockList;
- end;
- finally
- IsDrawing := False;
- end;
- end;
- // Internal pain(t) routine used by Draw()
- function TGIFImage.InternalPaint(Painter: PGifPainter; ACanvas: TCanvas;
- const Rect: TRect; Options: TGIFDrawOptions): TGIFPainter;
- begin
- if (Empty) or (Rect.Left >= Rect.Right) or (Rect.Top >= Rect.Bottom) then
- begin
- Result := nil;
- if (Painter <> nil) then
- Painter^ := Result;
- exit;
- end;
- // Draw in main thread if only one image
- if (Images.Count = 1) then
- Options := Options - [goAsync, goAnimate];
- Result := TGIFPainter.CreateRef(Painter, self, ACanvas, Rect, Options);
- FPainters.Add(Result);
- Result.OnStartPaint := FOnStartPaint;
- Result.OnPaint := FOnPaint;
- Result.OnAfterPaint := FOnAfterPaint;
- Result.OnLoop := FOnLoop;
- Result.OnEndPaint := FOnEndPaint;
- if not(goAsync in Options) then
- begin
- // Run in main thread
- Result.Execute;
- // Note: Painter threads executing in the main thread are freed upon exit
- // from the Execute method, so no need to do it here.
- Result := nil;
- if (Painter <> nil) then
- Painter^ := Result;
- end else
- Result.Priority := FThreadPriority;
- end;
- function TGIFImage.Paint(ACanvas: TCanvas; const Rect: TRect;
- Options: TGIFDrawOptions): TGIFPainter;
- begin
- Result := InternalPaint(nil, ACanvas, Rect, Options);
- if (Result <> nil) then
- // Run in separate thread
- Result.Start;
- end;
- procedure TGIFImage.PaintStart;
- var
- i : integer;
- begin
- with FPainters.LockList do
- try
- for i := 0 to Count-1 do
- TGIFPainter(Items[i]).Start;
- finally
- FPainters.UnLockList;
- end;
- end;
- procedure TGIFImage.PaintStop;
- var
- Ghosts : integer;
- i : integer;
- {$IFNDEF VER14_PLUS} // 2001.07.23
- Msg : TMsg;
- ThreadWindow : HWND;
- {$ENDIF} // 2001.07.23
- {$IFNDEF VER14_PLUS} // 2001.07.23
- procedure KillThreads;
- var
- i : integer;
- begin
- with FPainters.LockList do
- try
- for i := Count-1 downto 0 do
- if (goAsync in TGIFPainter(Items[i]).DrawOptions) then
- begin
- TerminateThread(TGIFPainter(Items[i]).Handle, 0);
- Delete(i);
- end;
- finally
- FPainters.UnLockList;
- end;
- end;
- {$ENDIF} // 2001.07.23
- begin
- try
- // Loop until all have died
- repeat
- with FPainters.LockList do
- try
- if (Count = 0) then
- exit;
- // Signal painters to terminate
- // Painters will attempt to remove them self from the
- // painter list when they die
- Ghosts := Count;
- for i := Ghosts-1 downto 0 do
- begin
- if not(goAsync in TGIFPainter(Items[i]).DrawOptions) then
- dec(Ghosts);
- TGIFPainter(Items[i]).Stop;
- end;
- finally
- FPainters.UnLockList;
- end;
- // If all painters were synchronous, there's no purpose waiting for them
- // to terminate, because they are running in the main thread.
- if (Ghosts = 0) then
- exit;
- {$IFDEF VER14_PLUS}
- // 2002.07.07
- if (GetCurrentThreadID = MainThreadID) then
- while CheckSynchronize do {loop};
- {$ELSE}
- // Process Messages to make TThread.Synchronize work
- // (Instead of Application.ProcessMessages)
- //{$IFDEF VER14_PLUS} // 2001.07.23
- // Exit; // 2001.07.23
- //{$ELSE} // 2001.07.23
- ThreadWindow := FindWindow('TThreadWindow', nil);
- if (ThreadWindow = 0) then
- begin
- KillThreads;
- Exit;
- end;
- while PeekMessage(Msg, ThreadWindow, CM_DESTROYWINDOW, CM_EXECPROC, PM_REMOVE) do
- begin
- if (Msg.Message <> WM_QUIT) then
- begin
- TranslateMessage(Msg);
- DispatchMessage(Msg);
- end else
- begin
- KillThreads;
- Exit;
- end;
- end;
- {$ENDIF} // 2001.07.23
- Sleep(0);
- until (False);
- finally
- FreeBitmap;
- end;
- end;
- procedure TGIFImage.PaintPause;
- var
- i : integer;
- begin
- with FPainters.LockList do
- try
- for i := 0 to Count-1 do
- TGIFPainter(Items[i]).Suspend;
- finally
- FPainters.UnLockList;
- end;
- end;
- procedure TGIFImage.PaintResume;
- var
- i : integer;
- begin
- // Implementation is currently same as PaintStart, but don't call PaintStart
- // in case its implementation changes
- with FPainters.LockList do
- try
- for i := 0 to Count-1 do
- TGIFPainter(Items[i]).Start;
- finally
- FPainters.UnLockList;
- end;
- end;
- procedure TGIFImage.PaintRestart;
- var
- i : integer;
- begin
- with FPainters.LockList do
- try
- for i := 0 to Count-1 do
- TGIFPainter(Items[i]).Restart;
- finally
- FPainters.UnLockList;
- end;
- end;
- procedure TGIFImage.Warning(Sender: TObject; Severity: TGIFSeverity; Message: string);
- begin
- if (Assigned(FOnWarning)) then
- FOnWarning(Sender, Severity, Message);
- end;
- {$IFDEF VER12_PLUS}
- {$IFNDEF VER14_PLUS} // not anymore need for Delphi 6 and up // 2001.07.23
- type
- TDummyThread = class(TThread)
- protected
- procedure Execute; override;
- end;
- procedure TDummyThread.Execute;
- begin
- end;
- {$ENDIF} // 2001.07.23
- {$ENDIF}
- var
- DesktopDC: HDC;
- {$IFDEF VER12_PLUS}
- {$IFNDEF VER14_PLUS} // not anymore need for Delphi 6 and up // 2001.07.23
- DummyThread: TThread;
- {$ENDIF} // 2001.07.23
- {$ENDIF}
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Initialization
- //
- ////////////////////////////////////////////////////////////////////////////////
- initialization
- {$IFDEF REGISTER_TGIFIMAGE}
- TPicture.RegisterFileFormat('GIF', sGIFImageFile, TGIFImage);
- CF_GIF := RegisterClipboardFormat(PChar(sGIFImageFile));
- TPicture.RegisterClipboardFormat(CF_GIF, TGIFImage);
- {$ENDIF}
- DesktopDC := GetDC(0);
- try
- PaletteDevice := (GetDeviceCaps(DesktopDC, BITSPIXEL) * GetDeviceCaps(DesktopDC, PLANES) <= 8);
- DoAutoDither := PaletteDevice;
- finally
- ReleaseDC(0, DesktopDC);
- end;
- {$IFDEF VER9x}
- // Note: This doesn't return the same palette as the Delphi 3 system palette
- // since the true system palette contains 20 entries and the Delphi 3 system
- // palette only contains 16.
- // For our purpose this doesn't matter since we do not care about the actual
- // colors (or their number) in the palette.
- // Stock objects doesn't have to be deleted.
- SystemPalette16 := GetStockObject(DEFAULT_PALETTE);
- {$ENDIF}
- {$IFDEF VER12_PLUS}
- // Make sure that at least one thread always exist.
- // This is done to circumvent a race condition bug in Delphi 4.x and later:
- // When threads are deleted and created in rapid succesion, a situation might
- // arise where the thread window is deleted *after* the threads it controls
- // has been created. See the Delphi Bug Lists for more information.
- {$IFNDEF VER14_PLUS} // not anymore need for Delphi 6 and up // 2001.07.23
- DummyThread := TDummyThread.Create(True);
- {$ENDIF} // 2001.07.23
- {$ENDIF}
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Finalization
- //
- ////////////////////////////////////////////////////////////////////////////////
- finalization
- ExtensionList.Free;
- AppExtensionList.Free;
- {$IFNDEF VER9x}
- {$IFDEF REGISTER_TGIFIMAGE}
- TPicture.UnregisterGraphicClass(TGIFImage);
- {$ENDIF}
- {$IFDEF VER100}
- if (pf8BitBitmap <> nil) then
- pf8BitBitmap.Free;
- {$ENDIF}
- {$ENDIF}
- {$IFDEF VER12_PLUS}
- {$IFNDEF VER14_PLUS} // not anymore need for Delphi 6 and up // 2001.07.23
- if (DummyThread <> nil) then
- DummyThread.Free;
- {$ENDIF} // 2001.07.23
- {$ENDIF}
- end.
|