| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352 |
- {
- Vampyre Imaging Library
- by Marek Mauder
- http://imaginglib.sourceforge.net
- The contents of this file are used with permission, subject to the Mozilla
- Public License Version 1.1 (the "License"); you may not use this file except
- in compliance with the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/MPL-1.1.html
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
- the specific language governing rights and limitations under the License.
- Alternatively, the contents of this file may be used under the terms of the
- GNU Lesser General Public License (the "LGPL License"), in which case the
- provisions of the LGPL License are applicable instead of those above.
- If you wish to allow use of your version of this file only under the terms
- of the LGPL License and not to allow others to use your version of this file
- under the MPL, indicate your decision by deleting the provisions above and
- replace them with the notice and other provisions required by the LGPL
- License. If you do not delete the provisions above, a recipient may use
- your version of this file under either the MPL or the LGPL License.
- For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
- }
- { This unit is heart of Imaging library. It contains basic functions for
- manipulating image data as well as various image file format support.}
- unit Imaging;
- {$I ImagingOptions.inc}
- interface
- uses
- SysUtils, Classes, Types, ImagingTypes;
- type
- { Default Imaging excepton class }
- EImagingError = class(Exception);
- { Raised when function receives bad image (not passed TestImage).}
- EImagingBadImage = class(Exception)
- public
- constructor Create;
- end;
- { Dynamic array of TImageData records }
- TDynImageDataArray = array of TImageData;
- { ------------------------------------------------------------------------
- Low Level Interface Functions
- ------------------------------------------------------------------------}
- { General Functions }
- { Initializes image (all is set to zeroes). Call this for each image
- before using it (before calling every other function) to be sure there
- are no random-filled bytes (which would cause errors later).}
- procedure InitImage(out Image: TImageData);
- { Creates empty image of given dimensions and format. Image is filled with
- transparent black color (A=0, R=0, G=0, B=0).}
- function NewImage(Width, Height: LongInt; Format: TImageFormat;
- var Image: TImageData): Boolean;
- { Returns True if given TImageData record is valid.}
- function TestImage(const Image: TImageData): Boolean;
- { Frees given image data. Ater this call image is in the same state
- as after calling InitImage. If image is not valid (dost not pass TestImage
- test) it is only zeroed by calling InitImage.}
- procedure FreeImage(var Image: TImageData);
- { Call FreeImage() on all images in given dynamic array and sets its
- length to zero.}
- procedure FreeImagesInArray(var Images: TDynImageDataArray);
- { Returns True if all TImageData records in given array are valid. Returns False
- if at least one is invalid or if array is empty.}
- function TestImagesInArray(const Images: TDynImageDataArray): Boolean;
- { Checks given file for every supported image file format and if
- the file is in one of them returns its string identifier
- (which can be used in LoadFromStream/LoadFromMem type functions).
- If file is not in any of the supported formats empty string is returned.}
- function DetermineFileFormat(const FileName: string): string;
- { Checks given stream for every supported image file format and if
- the stream is in one of them returns its string identifier
- (which can be used in LoadFromStream/LoadFromMem type functions).
- If stream is not in any of the supported formats empty string is returned.}
- function DetermineStreamFormat(Stream: TStream): string;
- { Checks given memory for every supported image file format and if
- the memory is in one of them returns its string identifier
- (which can be used in LoadFromStream/LoadFromMem type functions).
- If memory is not in any of the supported formats empty string is returned.}
- function DetermineMemoryFormat(Data: Pointer; Size: LongInt): string;
- { Checks that an apropriate file format is supported purely from inspecting
- the given file name's extension (not contents of the file itself).
- The file need not exist.}
- function IsFileFormatSupported(const FileName: string): Boolean;
- { Enumerates all registered image file formats. Descriptive name,
- default extension, masks (like '*.jpg,*.jfif') and some capabilities
- of each format are returned. To enumerate all formats start with Index at 0 and
- call EnumFileFormats with given Index in loop until it returns False (Index is
- automatically increased by 1 in function's body on successful call).}
- function EnumFileFormats(var Index: LongInt; var Name, DefaultExt, Masks: string;
- var CanSaveImages, IsMultiImageFormat: Boolean): Boolean;
- { Loading Functions }
- { Loads single image from given file.}
- function LoadImageFromFile(const FileName: string; var Image: TImageData): Boolean;
- { Loads single image from given stream. If function fails stream position
- is not changed.}
- function LoadImageFromStream(Stream: TStream; var Image: TImageData): Boolean;
- { Loads single image from given memory location.}
- function LoadImageFromMemory(Data: Pointer; Size: LongInt; var Image: TImageData): Boolean;
- { Loads multiple images from given file.}
- function LoadMultiImageFromFile(const FileName: string;
- var Images: TDynImageDataArray): Boolean;
- { Loads multiple images from given stream. If function fails stream position
- is not changed.}
- function LoadMultiImageFromStream(Stream: TStream;
- var Images: TDynImageDataArray): Boolean;
- { Loads multiple images from given memory location.}
- function LoadMultiImageFromMemory(Data: Pointer; Size: LongInt;
- var Images: TDynImageDataArray): Boolean;
- { Saving Functions }
- { Saves single image to given file.}
- function SaveImageToFile(const FileName: string; const Image: TImageData): Boolean;
- { Saves single image to given stream. If function fails stream position
- is not changed. Ext identifies desired image file format (jpg, png, dds, ...).}
- function SaveImageToStream(const Ext: string; Stream: TStream;
- const Image: TImageData): Boolean;
- { Saves single image to given memory location. Memory must be allocated and its
- size is passed in Size parameter in which number of written bytes is returned.
- Ext identifies desired image file format (jpg, png, dds, ...).}
- function SaveImageToMemory(const Ext: string; Data: Pointer; var Size: LongInt;
- const Image: TImageData): Boolean;
- { Saves multiple images to given file. If format supports
- only single level images and there are multiple images to be saved,
- they are saved as sequence of files img000.jpg, img001.jpg ....).}
- function SaveMultiImageToFile(const FileName: string;
- const Images: TDynImageDataArray): Boolean;
- { Saves multiple images to given stream. If format supports
- only single level images and there are multiple images to be saved,
- they are saved one after another to the stream. If function fails stream
- position is not changed. Ext identifies desired image file format (jpg, png, dds, ...).}
- function SaveMultiImageToStream(const Ext: string; Stream: TStream;
- const Images: TDynImageDataArray): Boolean;
- { Saves multiple images to given memory location. If format supports
- only single level images and there are multiple images to be saved,
- they are saved one after another to the memory. Memory must be allocated and
- its size is passed in Size parameter in which number of written bytes is returned.
- Ext identifies desired image file format (jpg, png, dds, ...).}
- function SaveMultiImageToMemory(const Ext: string; Data: Pointer;
- var Size: LongInt; const Images: TDynImageDataArray): Boolean;
- { Manipulation Functions }
- { Creates identical copy of image data. Clone should be initialized
- by InitImage or it should be vaild image which will be freed by CloneImage.}
- function CloneImage(const Image: TImageData; var Clone: TImageData): Boolean;
- { Converts image to the given format.}
- function ConvertImage(var Image: TImageData; DestFormat: TImageFormat): Boolean;
- { Flips given image. Reverses the image along its horizontal axis - the top
- becomes the bottom and vice versa.}
- function FlipImage(var Image: TImageData): Boolean;
- { Mirrors given image. Reverses the image along its vertical axis — the left
- side becomes the right and vice versa.}
- function MirrorImage(var Image: TImageData): Boolean;
- { Resizes given image to new dimensions. Nearest, bilinear, or bicubic filtering
- can be used. Input Image must already be created - use NewImage to create new images.}
- function ResizeImage(var Image: TImageData; NewWidth, NewHeight: LongInt;
- Filter: TResizeFilter): Boolean;
- { Swaps SrcChannel and DstChannel color or alpha channels of image.
- Use ChannelRed, ChannelBlue, ChannelGreen, ChannelAlpha constants to
- identify channels.}
- function SwapChannels(var Image: TImageData; SrcChannel, DstChannel: LongInt): Boolean;
- { Reduces the number of colors of the Image. Currently MaxColors must be in
- range <2, 4096>. Color reduction works also for alpha channel. Note that for
- large images and big number of colors it can be very slow.
- Output format of the image is the same as input format.}
- function ReduceColors(var Image: TImageData; MaxColors: LongInt): Boolean;
- { Generates mipmaps for image. Levels is the number of desired mipmaps levels
- with zero (or some invalid number) meaning all possible levels.}
- function GenerateMipMaps(const Image: TImageData; Levels: LongInt;
- var MipMaps: TDynImageDataArray): Boolean;
- { Maps image to existing palette producing image in ifIndex8 format.
- Pal must be allocated to at least Entries * SizeOf(TColor32Rec) bytes.
- As resulting image is in 8bit indexed format Entries must be lower or
- equal to 256.}
- function MapImageToPalette(var Image: TImageData; Pal: PPalette32;
- Entries: LongInt): Boolean;
- { Splits image into XChunks x YChunks subimages. Default size of each chunk is
- ChunkWidth x ChunkHeight. If PreserveSize si True chunks at the edges of
- the image are also ChunkWidth x ChunkHeight sized and empty space is filled
- with optional Fill pixels. After calling this function XChunks contains number of
- chunks along x axis and YChunks along y axis. To access chunk [X, Y] use this
- index: Chunks[Y * XChunks + X].}
- function SplitImage(var Image: TImageData; var Chunks: TDynImageDataArray;
- ChunkWidth, ChunkHeight: LongInt; var XChunks, YChunks: LongInt;
- PreserveSize: Boolean; Fill: Pointer = nil): Boolean;
- { Creates palette with MaxColors based on the colors of images in Images array.
- Use it when you want to convert several images to indexed format using
- single palette for all of them. If ConvertImages is True images in array
- are converted to indexed format using resulting palette. if it is False
- images are left intact and only resulting palatte is returned in Pal.
- Pal must be allocated to have at least MaxColors entries.}
- function MakePaletteForImages(var Images: TDynImageDataArray; Pal: PPalette32;
- MaxColors: LongInt; ConvertImages: Boolean): Boolean;
- { Rotates image by Angle degrees counterclockwise. All angles are allowed.}
- procedure RotateImage(var Image: TImageData; Angle: Single);
- { Drawing/Pixel functions }
- { Copies rectangular part of SrcImage to DstImage. No blending is performed -
- alpha is simply copied to destination image. Operates also with
- negative X and Y coordinates.
- Note that copying is fastest for images in the same data format
- (and slowest for images in special formats).}
- function CopyRect(const SrcImage: TImageData; SrcX, SrcY, Width, Height: LongInt;
- var DstImage: TImageData; DstX, DstY: LongInt): Boolean;
- { Fills given rectangle of image with given pixel fill data. Fill should point
- to the pixel in the same format as the given image is in.}
- function FillRect(var Image: TImageData; X, Y, Width, Height: LongInt; FillColor: Pointer): Boolean;
- { Replaces pixels with OldPixel in the given rectangle by NewPixel.
- OldPixel and NewPixel should point to the pixels in the same format
- as the given image is in.}
- function ReplaceColor(var Image: TImageData; X, Y, Width, Height: LongInt;
- OldColor, NewColor: Pointer): Boolean;
- { Stretches the contents of the source rectangle to the destination rectangle
- with optional resampling. No blending is performed - alpha is
- simply copied/resampled to destination image. Note that stretching is
- fastest for images in the same data format (and slowest for
- images in special formats).}
- function StretchRect(const SrcImage: TImageData; SrcX, SrcY, SrcWidth,
- SrcHeight: LongInt; var DstImage: TImageData; DstX, DstY, DstWidth,
- DstHeight: LongInt; Filter: TResizeFilter): Boolean;
- { Copies pixel of Image at [X, Y] to memory pointed at by Pixel. Doesn't
- work with special formats.}
- procedure GetPixelDirect(const Image: TImageData; X, Y: LongInt; Pixel: Pointer);
- { Copies pixel from memory pointed at by Pixel to Image at position [X, Y].
- Doesn't work with special formats.}
- procedure SetPixelDirect(const Image: TImageData; X, Y: LongInt; Pixel: Pointer);
- { Function for getting pixel colors. Native pixel is read from Image and
- then translated to 32 bit ARGB. Works for all image formats (except special)
- so it is not very fast.}
- function GetPixel32(const Image: TImageData; X, Y: LongInt): TColor32Rec;
- { Procedure for setting pixel colors. Input 32 bit ARGB color is translated to
- native format and then written to Image. Works for all image formats (except special)
- so it is not very fast.}
- procedure SetPixel32(const Image: TImageData; X, Y: LongInt; const Color: TColor32Rec);
- { Function for getting pixel colors. Native pixel is read from Image and
- then translated to FP ARGB. Works for all image formats (except special)
- so it is not very fast.}
- function GetPixelFP(const Image: TImageData; X, Y: LongInt): TColorFPRec;
- { Procedure for setting pixel colors. Input FP ARGB color is translated to
- native format and then written to Image. Works for all image formats (except special)
- so it is not very fast.}
- procedure SetPixelFP(const Image: TImageData; X, Y: LongInt; const Color: TColorFPRec);
- { Palette Functions }
- { Allocates new palette with Entries ARGB color entries.}
- procedure NewPalette(Entries: LongInt; var Pal: PPalette32);
- { Frees given palette.}
- procedure FreePalette(var Pal: PPalette32);
- { Copies Count palette entries from SrcPal starting at index SrcIdx to
- DstPal at index DstPal.}
- procedure CopyPalette(SrcPal, DstPal: PPalette32; SrcIdx, DstIdx, Count: LongInt);
- { Returns index of color in palette or index of nearest color if exact match
- is not found. Pal must have at least Entries color entries.}
- function FindColor(Pal: PPalette32; Entries: LongInt; Color: TColor32): LongInt;
- { Creates grayscale palette where each color channel has the same value.
- Pal must have at least Entries color entries.}
- procedure FillGrayscalePalette(Pal: PPalette32; Entries: LongInt);
- { Creates palette with given bitcount for each channel.
- 2^(RBits + GBits + BBits) should be equl to Entries. Examples:
- (3, 3, 2) will create palette with all possible colors of R3G3B2 format
- and (8, 0, 0) will create palette with 256 shades of red.
- Pal must be allocated to at least Entries * SizeOf(TColor32Rec) bytes.}
- procedure FillCustomPalette(Pal: PPalette32; Entries: LongInt; RBits, GBits,
- BBits: Byte; Alpha: Byte = $FF);
- { Swaps SrcChannel and DstChannel color or alpha channels of palette.
- Use ChannelRed, ChannelBlue, ChannelGreen, ChannelAlpha constants to
- identify channels. Pal must be allocated to at least
- Entries * SizeOf(TColor32Rec) bytes.}
- procedure SwapChannelsOfPalette(Pal: PPalette32; Entries, SrcChannel,
- DstChannel: LongInt);
- { Options Functions }
- { Sets value of integer option specified by OptionId parameter.
- Option Ids are constans starting ImagingXXX.}
- function SetOption(OptionId, Value: LongInt): Boolean;
- { Returns value of integer option specified by OptionId parameter. If OptionId is
- invalid, InvalidOption is returned. Option Ids are constans
- starting ImagingXXX.}
- function GetOption(OptionId: LongInt): LongInt;
- { Pushes current values of all options on the stack. Returns True
- if successfull (max stack depth is 8 now). }
- function PushOptions: Boolean;
- { Pops back values of all options from the top of the stack. Returns True
- if successfull (max stack depth is 8 now). }
- function PopOptions: Boolean;
- { Image Format Functions }
- { Returns short information about given image format.}
- function GetImageFormatInfo(Format: TImageFormat; out Info: TImageFormatInfo): Boolean;
- { Returns size in bytes of Width x Height area of pixels. Works for all formats.}
- function GetPixelsSize(Format: TImageFormat; Width, Height: LongInt): LongInt;
- { IO Functions }
- { User can set his own file IO functions used when loading from/saving to
- files by this function.}
- procedure SetUserFileIO(OpenProc: TOpenProc; CloseProc: TCloseProc; EofProc: TEofProc; SeekProc:
- TSeekProc; TellProc: TTellProc; ReadProc: TReadProc; WriteProc: TWriteProc);
- { Sets file IO functions to Imaging default.}
- procedure ResetFileIO;
- { Raw Image IO Functions }
- procedure ReadRawImageFromFile(const FileName: string; Width, Height: Integer;
- Format: TImageFormat; var Image: TImageData; Offset: Integer = 0; RowLength: Integer = 0);
- procedure ReadRawImageFromStream(Stream: TStream; Width, Height: Integer;
- Format: TImageFormat; var Image: TImageData; Offset: Integer = 0; RowLength: Integer = 0);
- procedure ReadRawImageFromMemory(Data: Pointer; DataSize: Integer; Width, Height: Integer;
- Format: TImageFormat; var Image: TImageData; Offset: Integer = 0; RowLength: Integer = 0);
- procedure ReadRawImageRect(Data: Pointer; Left, Top, Width, Height: Integer;
- var Image: TImageData; Offset: Integer = 0; RowLength: Integer = 0);
- procedure WriteRawImageToFile(const FileName: string; const Image: TImageData;
- Offset: Integer = 0; RowLength: Integer = 0);
- procedure WriteRawImageToStream(Stream: TStream; const Image: TImageData;
- Offset: Integer = 0; RowLength: Integer = 0);
- procedure WriteRawImageToMemory(Data: Pointer; DataSize: Integer; const Image: TImageData;
- Offset: Integer = 0; RowLength: Integer = 0);
- procedure WriteRawImageRect(Data: Pointer; Left, Top, Width, Height: Integer;
- const Image: TImageData; Offset: Integer = 0; RowLength: Integer = 0);
- { Convenience/helper Functions }
- procedure ResizeImageToFit(const SrcImage: TImageData; FitWidth, FitHeight: Integer;
- Filter: TResizeFilter; var DestImage: TImageData);
- { Color functions }
- { Constructs TColor24Rec color.}
- function Color24(R, G, B: Byte): TColor24Rec; {$IFDEF USE_INLINE}inline;{$ENDIF}
- { Constructs TColor32Rec color.}
- function Color32(A, R, G, B: Byte): TColor32Rec; {$IFDEF USE_INLINE}inline;{$ENDIF}
- { Constructs TColor48Rec color.}
- function Color48(R, G, B: Word): TColor48Rec; {$IFDEF USE_INLINE}inline;{$ENDIF}
- { Constructs TColor64Rec color.}
- function Color64(A, R, G, B: Word): TColor64Rec; {$IFDEF USE_INLINE}inline;{$ENDIF}
- { Constructs TColorFPRec color.}
- function ColorFP(A, R, G, B: Single): TColorFPRec; {$IFDEF USE_INLINE}inline;{$ENDIF}
- { Constructs TColorHFRec color.}
- function ColorHF(A, R, G, B: THalfFloat): TColorHFRec; {$IFDEF USE_INLINE}inline;{$ENDIF}
- { Convenience function for getting alpha component of TColor32.}
- function GetAlphaValue(Color32: TColor32): Byte; {$IFDEF USE_INLINE}inline;{$ENDIF}
- { Convenience function for getting red component of TColor32.}
- function GetRedValue(Color32: TColor32): Byte; {$IFDEF USE_INLINE}inline;{$ENDIF}
- { Convenience function for getting green component of TColor32.}
- function GetGreenValue(Color32: TColor32): Byte; {$IFDEF USE_INLINE}inline;{$ENDIF}
- { Convenience function for getting blue component of TColor32.}
- function GetBlueValue(Color32: TColor32): Byte; {$IFDEF USE_INLINE}inline;{$ENDIF}
- { ------------------------------------------------------------------------
- Other Imaging Stuff
- ------------------------------------------------------------------------}
- type
- { Set of TImageFormat enum.}
- TImageFormats = set of TImageFormat;
- { Record containg set of IO functions internaly used by image loaders/savers.}
- TIOFunctions = record
- Open: TOpenProc;
- Close: TCloseProc;
- Eof: TEofProc;
- Seek: TSeekProc;
- Tell: TTellProc;
- Read: TReadProc;
- Write: TWriteProc;
- end;
- PIOFunctions = ^TIOFunctions;
- type
- TFileFormatFeature = (
- ffLoad,
- ffSave,
- ffMultiImage,
- ffReadOnSave,
- ffProgress,
- ffReadScanlines);
- TFileFormatFeatures = set of TFileFormatFeature;
- TMetadata = class;
- { Base class for various image file format loaders/savers which
- descend from this class. If you want to add support for new image file
- format the best way is probably to look at TImageFileFormat descendants'
- implementations that are already part of Imaging.}
- {$TYPEINFO ON}
- TImageFileFormat = class
- private
- FExtensions: TStringList;
- FMasks: TStringList;
- function GetCanLoad: Boolean;
- function GetCanSave: Boolean;
- function GetIsMultiImageFormat: Boolean;
- { Does various checks and actions before LoadData method is called.}
- function PrepareLoad(Handle: TImagingHandle; var Images: TDynImageDataArray;
- OnlyFirstFrame: Boolean): Boolean;
- { Processes some actions according to result of LoadData.}
- function PostLoadCheck(var Images: TDynImageDataArray; LoadResult: Boolean): Boolean;
- { Helper function to be called in SaveData methods of descendants (ensures proper
- index and sets FFirstIdx and FLastIdx for multi-images).}
- function PrepareSave(Handle: TImagingHandle; const Images: TDynImageDataArray;
- var Index: LongInt): Boolean;
- { Returns file open mode used for saving images. Depends on defined Features.}
- function GetSaveOpenMode: TOpenMode;
- protected
- FName: string;
- FFeatures: TFileFormatFeatures;
- FSupportedFormats: TImageFormats;
- FFirstIdx, FLastIdx: LongInt;
- FMetadata: TMetadata;
- { Descendants must override this method and define file format name and
- capabilities.}
- procedure Define; virtual;
- { Defines filename masks for this image file format. AMasks should be
- in format '*.ext1,*.ext2,umajo.*'.}
- procedure AddMasks(const AMasks: string);
- function GetFormatInfo(Format: TImageFormat): TImageFormatInfo;
- { Returns set of TImageData formats that can be saved in this file format
- without need for conversion.}
- function GetSupportedFormats: TImageFormats; virtual;
- { Method which must be overrided in descendants if they' are be capable
- of loading images. Images are already freed and length is set to zero
- whenever this method gets called. Also Handle is assured to be valid
- and contains data that passed TestFormat method's check.}
- function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
- OnlyFirstFrame: Boolean): Boolean; virtual;
- { Method which must be overriden in descendants if they are be capable
- of saving images. Images are checked to have length >0 and
- that they contain valid images. For single-image file formats
- Index contain valid index to Images array (to image which should be saved).
- Multi-image formats should use FFirstIdx and FLastIdx fields to
- to get all images that are to be saved.}
- function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
- Index: LongInt): Boolean; virtual;
- { This method is called internaly by MakeCompatible when input image
- is in format not supported by this file format. Image is clone of
- MakeCompatible's input and Info is its extended format info.}
- procedure ConvertToSupported(var Image: TImageData;
- const Info: TImageFormatInfo); virtual;
- { Returns True if given image is supported for saving by this file format.
- Most file formats don't need to override this method. It checks
- (in this base class) if Image's format is in SupportedFromats set.
- But you may override it if you want further checks
- (proper widht and height for example).}
- function IsSupported(const Image: TImageData): Boolean; virtual;
- public
- constructor Create(AMetadata: TMetadata = nil); virtual;
- destructor Destroy; override;
- { Loads images from file source.}
- function LoadFromFile(const FileName: string; var Images: TDynImageDataArray;
- OnlyFirstLevel: Boolean = False): Boolean;
- { Loads images from stream source.}
- function LoadFromStream(Stream: TStream; var Images: TDynImageDataArray;
- OnlyFirstLevel: Boolean = False): Boolean;
- { Loads images from memory source.}
- function LoadFromMemory(Data: Pointer; Size: LongInt;
- var Images: TDynImageDataArray; OnlyFirstLevel: Boolean = False): Boolean;
- { Saves images to file. If format supports only single level images and
- there are multiple images to be saved, they are saved as sequence of
- independent images (for example SaveToFile saves sequence of
- files img000.jpg, img001.jpg ....).}
- function SaveToFile(const FileName: string; const Images: TDynImageDataArray;
- OnlyFirstLevel: Boolean = False): Boolean;
- { Saves images to stream. If format supports only single level images and
- there are multiple images to be saved, they are saved as sequence of
- independent images.}
- function SaveToStream(Stream: TStream; const Images: TDynImageDataArray;
- OnlyFirstLevel: Boolean = False): Boolean;
- { Saves images to memory. If format supports only single level images and
- there are multiple images to be saved, they are saved as sequence of
- independent images. Data must be already allocated and their size passed
- as Size parameter, number of written bytes is then returned in the same
- parameter.}
- function SaveToMemory(Data: Pointer; var Size: LongInt;
- const Images: TDynImageDataArray; OnlyFirstLevel: Boolean = False): Boolean;
- { Makes Image compatible with this file format (that means it is in one
- of data formats in Supported formats set). If input is already
- in supported format then Compatible just use value from input
- (Compatible := Image) so must not free it after you are done with it
- (image bits pointer points to input image's bits).
- If input is not in supported format then it is cloned to Compatible
- and concerted to one of supported formats (which one dependeds on
- this file format). If image is cloned MustBeFreed is set to True
- to indicated that you must free Compatible after you are done with it.}
- function MakeCompatible(const Image: TImageData; var Compatible: TImageData;
- out MustBeFreed: Boolean): Boolean;
- { Returns True if data located in source identified by Handle
- represent valid image in current format.}
- function TestFormat(Handle: TImagingHandle): Boolean; virtual;
- { Resturns True if the given FileName matches filter for this file format.
- For most formats it just checks filename extensions.
- It uses filename masks in from Masks property so it can recognize
- filenames like this 'umajoXXXumajo.j0j' if one of themasks is
- 'umajo*umajo.j?j'.}
- function TestFileName(const FileName: string): Boolean;
- { Descendants use this method to check if their options (registered with
- constant Ids for SetOption/GetOption interface or accessible as properties
- of descendants) have valid values and make necessary changes.}
- procedure CheckOptionsValidity; virtual;
- { Description of this format.}
- property Name: string read FName;
- { Indicates whether images in this format can be loaded.}
- property CanLoad: Boolean read GetCanLoad;
- { Indicates whether images in this format can be saved.}
- property CanSave: Boolean read GetCanSave;
- { Indicates whether images in this format can contain multiple image levels.}
- property IsMultiImageFormat: Boolean read GetIsMultiImageFormat;
- { List of filename extensions for this format.}
- property Extensions: TStringList read FExtensions;
- { List of filename masks that are used to associate filenames
- with TImageFileFormat descendants. Typical mask looks like
- '*.bmp' or 'texture.*' (supports file formats which use filename instead
- of extension to identify image files).}
- property Masks: TStringList read FMasks;
- { Set of TImageFormats supported by saving functions of this format. Images
- can be saved only in one those formats.}
- property SupportedFormats: TImageFormats read GetSupportedFormats;
- end;
- {$TYPEINFO OFF}
- { Class reference for TImageFileFormat class}
- TImageFileFormatClass = class of TImageFileFormat;
- { Physical resolution unit.}
- TResolutionUnit = (
- ruSizeInMicroMeters, // value is pixel size in micrometers
- ruDpi, // value is pixels/dots per inch
- ruDpm, // value is pixels/dots per meter
- ruDpcm // value is pixels/dots per centimeter
- );
- { Class for storage of single metadata item.}
- TMetadataItem = class
- public
- Id: string;
- ImageIndex: Integer;
- Value: Variant;
- end;
- { Metadata manager class.}
- TMetadata = class
- private
- FLoadMetaItems: TStringList;
- FSaveMetaItems: TStringList;
- procedure AddMetaToList(List: TStringList; const Id: string; const Value: Variant; ImageIndex: Integer);
- procedure ClearMetaList(List: TStringList);
- function GetMetaById(const Id: string): Variant;
- function GetMetaByIdMulti(const Id: string; ImageIndex: Integer): Variant;
- function GetMetaCount: Integer;
- function GetMetaByIdx(Index: Integer): TMetadataItem;
- function GetSaveMetaById(const Id: string): Variant;
- function GetSaveMetaByIdMulti(const Id: string; ImageIndex: Integer): Variant;
- procedure TranslateUnits(ResolutionUnit: TResolutionUnit; var XRes, YRes: Single);
- public
- constructor Create;
- destructor Destroy; override;
- procedure SetMetaItem(const Id: string; const Value: Variant; ImageIndex: Integer = 0);
- procedure SetMetaItemForSaving(const Id: string; const Value: Variant; ImageIndex: Integer = 0);
- function HasMetaItem(const Id: string; ImageIndex: Integer = 0): Boolean;
- function HasMetaItemForSaving(const Id: string; ImageIndex: Integer = 0): Boolean;
- procedure ClearMetaItems;
- procedure ClearMetaItemsForSaving;
- function GetMetaItemName(const Id: string; ImageIndex: Integer): string;
- { Copies loaded meta items to items-for-save stack. Use this when you want to
- save metadata that have been just loaded (e.g. resaving image in
- different file format but keeping the metadata).}
- procedure CopyLoadedMetaItemsForSaving;
- function GetPhysicalPixelSize(ResUnit: TResolutionUnit; out XSize,
- YSize: Single; MetaForSave: Boolean = False; ImageIndex: Integer = 0): Boolean;
- procedure SetPhysicalPixelSize(ResUnit: TResolutionUnit; XSize, YSize: Single;
- MetaForSave: Boolean = False; ImageIndex: Integer = 0);
- property MetaItems[const Id: string]: Variant read GetMetaById;
- property MetaItemsMulti[const Id: string; ImageIndex: Integer]: Variant read GetMetaByIdMulti;
- { Number of loaded metadata items.}
- property MetaItemCount: Integer read GetMetaCount;
- property MetaItemsByIdx[Index: Integer]: TMetadataItem read GetMetaByIdx;
- property MetaItemsForSaving[const Id: string]: Variant read GetSaveMetaById;
- property MetaItemsForSavingMulti[const Id: string; ImageIndex: Integer]: Variant read GetSaveMetaByIdMulti;
- end;
- const
- { Metadata item id constants }
- { Physical size of one pixel in micrometers. Type of value is Float.}
- SMetaPhysicalPixelSizeX = 'PhysicalPixelSizeX';
- SMetaPhysicalPixelSizeY = 'PhysicalPixelSizeY';
- { Delay for frame of animation (how long it should stay visible) in milliseconds.
- Type of value is Integer.}
- SMetaFrameDelay = 'FrameDelay';
- { Number of times animation should be looped (0 = infinite looping). Type is Int. }
- SMetaAnimationLoops = 'AnimationLoops';
- { Gamma correction value. Type is Float.}
- SMetaGamma = 'Gamma';
- { Exposure value for HDR etc. Type is Float.}
- SMetaExposure = 'Exposure';
- { EXIF image metadata raw blob.}
- SMetaExifBlob = 'ExifBlob';
- { XMP image metadata raw blob.}
- SMetaXmpBlob = 'XmpBlob';
- { IPTC image metadata raw blob.}
- SMetaIptcBlob = 'IptcBlob';
- var
- GlobalMetadata: TMetadata;
- { Returns symbolic name of given format.}
- function GetFormatName(Format: TImageFormat): string;
- { Returns string with information about given Image.}
- function ImageToStr(const Image: TImageData): string;
- { Returns Imaging version string in format 'Major.Minor'.}
- function GetVersionStr: string;
- { If Condition is True then TruePart is retured, otherwise FalsePart is returned.}
- function IffFormat(Condition: Boolean; const TruePart, FalsePart: TImageFormat): TImageFormat;
- { Registers new option so it can be used by SetOption and GetOption functions.
- Returns True if registration was succesful - that is Id is valid and is
- not already taken by another option.}
- function RegisterOption(OptionId: LongInt; Variable: PLongInt): Boolean;
- { Registers new image loader/saver so it can be used by LoadFrom/SaveTo
- functions.}
- procedure RegisterImageFileFormat(AClass: TImageFileFormatClass);
- { Returns image format loader/saver according to given extension
- or nil if not found.}
- function FindImageFileFormatByExt(const Ext: string): TImageFileFormat;
- { Returns image format loader/saver according to given filename
- or nil if not found.}
- function FindImageFileFormatByName(const FileName: string): TImageFileFormat;
- { Returns image format loader/saver based on its class
- or nil if not found or not registered.}
- function FindImageFileFormatByClass(AClass: TImageFileFormatClass): TImageFileFormat;
- { Returns number of registered image file format loaders/saver.}
- function GetFileFormatCount: LongInt;
- { Returns image file format loader/saver at given index. Index must be
- in range [0..GetFileFormatCount - 1] otherwise nil is returned.}
- function GetFileFormatAtIndex(Index: LongInt): TImageFileFormat;
- { Returns filter string for usage with open and save picture dialogs
- which contains all registered image file formats.
- Set OpenFileFilter to True if you want filter for open dialog
- and to False if you want save dialog filter (formats that cannot save to files
- are not added then).
- For open dialog filter for all known graphic files
- (like All(*.jpg;*.png;....) is added too at the first index.}
- function GetImageFileFormatsFilter(OpenFileFilter: Boolean): string;
- { Returns file extension (without dot) of image format selected
- by given filter index. Used filter string is defined by GetImageFileFormatsFilter
- function. This function can be used with save dialogs (with filters created
- by GetImageFileFormatsFilter) to get the extension of file format selected
- in dialog quickly. Index is in range 1..N (as FilterIndex property
- of TOpenDialog/TSaveDialog)}
- function GetFilterIndexExtension(Index: LongInt; OpenFileFilter: Boolean): string;
- { Returns filter index of image file format of file specified by FileName. Used filter
- string is defined by GetImageFileFormatsFilter function.
- Returned index is in range 1..N (as FilterIndex property of TOpenDialog/TSaveDialog)}
- function GetFileNameFilterIndex(const FileName: string; OpenFileFilter: Boolean): LongInt;
- { Returns current IO functions.}
- function GetIO: TIOFunctions;
- { Raises EImagingError with given message.}
- procedure RaiseImaging(const Msg: string; const Args: array of const); overload;
- procedure RaiseImaging(const Msg: string); overload; {$IFDEF USE_INLINE}inline;{$ENDIF}
- const
- SImagingLibTitle = 'Vampyre Imaging Library';
- implementation
- uses
- {$IFNDEF DONT_LINK_FILE_FORMATS}
- {$IFNDEF DONT_LINK_BITMAP}
- ImagingBitmap,
- {$ENDIF}
- {$IFNDEF DONT_LINK_JPEG}
- ImagingJpeg,
- {$ENDIF}
- {$IF not Defined(DONT_LINK_PNG) or not Defined(DONT_LINK_MNG) or not Defined(DONT_LINK_JNG)}
- ImagingNetworkGraphics,
- {$IFEND}
- {$IFNDEF DONT_LINK_GIF}
- ImagingGif,
- {$ENDIF}
- {$IFNDEF DONT_LINK_DDS}
- ImagingDds,
- {$ENDIF}
- {$IFNDEF DONT_LINK_TARGA}
- ImagingTarga,
- {$ENDIF}
- {$IFNDEF DONT_LINK_PNM}
- ImagingPortableMaps,
- {$ENDIF}
- {$IFNDEF DONT_LINK_RADHDR}
- ImagingRadiance,
- {$ENDIF}
- {$IFNDEF DONT_LINK_EXTRAS}
- ImagingExtFileFormats,
- {$ENDIF}
- {$ENDIF}
- //ImagingDebug,
- ImagingFormats, ImagingUtility, ImagingIO, Variants;
- resourcestring
- SExceptMsg = 'Exception Message';
- SAllFilter = 'All Images';
- SUnknownFormat = 'Unknown and unsupported format';
- SErrorFreeImage = 'Error while freeing image. %s';
- SErrorCloneImage = 'Error while cloning image. %s';
- SErrorFlipImage = 'Error while flipping image. %s';
- SErrorMirrorImage = 'Error while mirroring image. %s';
- SErrorResizeImage = 'Error while resizing image. %s';
- SErrorSwapImage = 'Error while swapping channels of image. %s';
- SFileFormatCanNotLoad = 'Image Format "%s" does not support loading images.';
- SFileFormatCanNotSave = 'Image Format "%s" does not support saving images.';
- SErrorNewImage = 'Error while creating image data with params: Width=%d ' +
- 'Height=%d Format=%s.';
- SErrorConvertImage = 'Error while converting image to format "%s". %s';
- SImageInfo = 'Image @%p info: Width = %dpx, Height = %dpx, ' +
- 'Format = %s, Size = %.0n %s, Bits @%p, Palette @%p.';
- SImageInfoInvalid = 'Access violation encountered when getting info on ' +
- 'image at address %p.';
- SFileNotValid = 'File "%s" is not valid image in "%s" format.';
- SStreamNotValid = 'Stream %p does not contain valid image in "%s" format.';
- SMemoryNotValid = 'Memory %p (%d Bytes) does not contain valid image ' +
- 'in "%s" format.';
- SErrorLoadingFile = 'Error while loading images from file "%s" (file format: %s).';
- SErrorLoadingStream = 'Error while loading images from stream %p (file format: %s).';
- SErrorLoadingMemory = 'Error while loading images from memory %p (%d Bytes) (file format: %s).';
- SErrorSavingFile = 'Error while saving images to file "%s" (file format: %s).';
- SErrorSavingStream = 'Error while saving images to stream %p (file format: %s).';
- SErrorSavingMemory = 'Error while saving images to memory %p (%d Bytes) (file format: %s).';
- SErrorFindColor = 'Error while finding color in palette @%p with %d entries.';
- SErrorGrayscalePalette = 'Error while filling grayscale palette @%p with %d entries.';
- SErrorCustomPalette = 'Error while filling custom palette @%p with %d entries.';
- SErrorSwapPalette = 'Error while swapping channels of palette @%p with %d entries.';
- SErrorReduceColors = 'Error while reducing number of colors of image to %d. %s';
- SErrorGenerateMipMaps = 'Error while generating %d mipmap levels for image %s';
- SImagesNotValid = 'One or more images are not valid.';
- SErrorCopyRect = 'Error while copying rect from image %s to image %s.';
- SErrorMapImage = 'Error while mapping image %s to palette.';
- SErrorFillRect = 'Error while filling rectangle X:%d Y:%d W:%d H:%d in image %s';
- SErrorSplitImage = 'Error while splitting image %s to %dx%d sized chunks.';
- SErrorMakePaletteForImages = 'Error while making %d color palette for %d images.';
- SErrorNewPalette = 'Error while creating new palette with %d entries';
- SErrorFreePalette = 'Error while freeing palette @%p';
- SErrorCopyPalette = 'Error while copying %d entries from palette @%p to @%p';
- SErrorReplaceColor = 'Error while replacing colors in rectangle X:%d Y:%d W:%d H:%d of image %s';
- SErrorRotateImage = 'Error while rotating image %s by %.2n degrees';
- SErrorStretchRect = 'Error while stretching rect from image %s to image %s.';
- SErrorEmptyStream = 'Input stream has no data. Check Position property.';
- SErrorInvalidInputImage = 'Invalid input image.';
- SErrorBadImage = 'Bad image detected.';
- const
- // Initial size of array with options information
- InitialOptions = 256;
- // Max depth of the option stack
- OptionStackDepth = 8;
- // Do not change the default format now, its too late
- DefaultImageFormat: TImageFormat = ifA8R8G8B8;
- // Format used to create metadata IDs for frames loaded form multiimages.
- SMetaIdForSubImage = '%s/%d';
- type
- TOptionArray = array of PLongInt;
- TOptionValueArray = array of LongInt;
- TOptionStack = class(TObject)
- private
- FStack: array[0..OptionStackDepth - 1] of TOptionValueArray;
- FPosition: LongInt;
- public
- constructor Create;
- destructor Destroy; override;
- function Push: Boolean;
- function Pop: Boolean;
- end;
- var
- // Currently set IO functions
- IO: TIOFunctions;
- // List with all registered TImageFileFormat classes
- ImageFileFormats: TList = nil;
- // Aarray with registered options (pointers to their values)
- Options: TOptionArray = nil;
- // Array containing addional infomation about every image format
- ImageFormatInfos: TImageFormatInfoArray;
- // Stack used by PushOptions/PopOtions functions
- OptionStack: TOptionStack = nil;
- var
- // Variable for ImagingColorReduction option
- ColorReductionMask: LongInt = $FF;
- // Variable for ImagingLoadOverrideFormat option
- LoadOverrideFormat: TImageFormat = ifUnknown;
- // Variable for ImagingSaveOverrideFormat option
- SaveOverrideFormat: TImageFormat = ifUnknown;
- // Variable for ImagingSaveOverrideFormat option
- MipMapFilter: TSamplingFilter = sfLinear;
- // Variable for ImagingBinaryTreshold option
- BinaryTreshold: Integer = 128;
- { Exceptions }
- constructor EImagingBadImage.Create;
- begin
- inherited Create(SErrorBadImage);
- end;
- { Internal unit functions }
- { Modifies option value to be in the allowed range. Works only
- for options registered in this unit.}
- function CheckOptionValue(OptionId, Value: LongInt): LongInt; forward;
- { Sets IO functions to file IO.}
- procedure SetFileIO; forward;
- { Sets IO functions to stream IO.}
- procedure SetStreamIO; forward;
- { Sets IO functions to memory IO.}
- procedure SetMemoryIO; forward;
- { Inits image format infos array.}
- procedure InitImageFormats; forward;
- { Freew image format infos array.}
- procedure FreeImageFileFormats; forward;
- { Creates options array and stack.}
- procedure InitOptions; forward;
- { Frees options array and stack.}
- procedure FreeOptions; forward;
- function UpdateExceptMessage(E: Exception; const MsgToPrepend: string; const Args: array of const): Exception;
- begin
- Result := E;
- E.Message := Format(MsgToPrepend, Args) + ' ' + SExceptMsg + ': ' + E.Message
- end;
- { ------------------------------------------------------------------------
- Low Level Interface Functions
- ------------------------------------------------------------------------}
- { General Functions }
- procedure InitImage(out Image: TImageData);
- begin
- FillChar(Image, SizeOf(Image), 0);
- end;
- function NewImage(Width, Height: LongInt; Format: TImageFormat; var Image:
- TImageData): Boolean;
- var
- FInfo: PImageFormatInfo;
- begin
- Assert((Width > 0) and (Height >0));
- Assert(IsImageFormatValid(Format));
- Result := False;
- FreeImage(Image);
- try
- Image.Width := Width;
- Image.Height := Height;
- // Select default data format if selected
- if (Format = ifDefault) then
- Image.Format := DefaultImageFormat
- else
- Image.Format := Format;
- // Get extended format info
- FInfo := ImageFormatInfos[Image.Format];
- if FInfo = nil then
- begin
- InitImage(Image);
- Exit;
- end;
- // Check image dimensions and calculate its size in bytes
- FInfo.CheckDimensions(FInfo.Format, Image.Width, Image.Height);
- Image.Size := FInfo.GetPixelsSize(FInfo.Format, Image.Width, Image.Height);
- if Image.Size = 0 then
- begin
- InitImage(Image);
- Exit;
- end;
- // Image bits are allocated and set to zeroes
- GetMem(Image.Bits, Image.Size);
- FillChar(Image.Bits^, Image.Size, 0);
- // Palette is allocated and set to zeroes
- if FInfo.PaletteEntries > 0 then
- begin
- GetMem(Image.Palette, FInfo.PaletteEntries * SizeOf(TColor32Rec));
- FillChar(Image.Palette^, FInfo.PaletteEntries * SizeOf(TColor32Rec), 0);
- end;
- Result := TestImage(Image);
- except
- on E: Exception do
- begin
- FreeMem(Image.Bits);
- FreeMem(Image.Palette);
- InitImage(Image);
- raise UpdateExceptMessage(E, SErrorNewImage, [Width, Height, GetFormatName(Format)]);
- end;
- end;
- end;
- function TestImage(const Image: TImageData): Boolean;
- begin
- try
- Result := (LongInt(Image.Format) >= LongInt(Low(TImageFormat))) and
- (LongInt(Image.Format) <= LongInt(High(TImageFormat))) and
- (ImageFormatInfos[Image.Format] <> nil) and
- (Assigned(ImageFormatInfos[Image.Format].GetPixelsSize) and
- (ImageFormatInfos[Image.Format].GetPixelsSize(Image.Format,
- Image.Width, Image.Height) = Image.Size));
- except
- // Possible int overflows or other errors
- Result := False;
- end;
- end;
- procedure FreeImage(var Image: TImageData);
- begin
- try
- if TestImage(Image) then
- begin
- FreeMemNil(Image.Bits);
- FreeMemNil(Image.Palette);
- end;
- InitImage(Image);
- except
- raise UpdateExceptMessage(GetExceptObject, SErrorFreeImage, [ImageToStr(Image)]);
- end;
- end;
- procedure FreeImagesInArray(var Images: TDynImageDataArray);
- var
- I: LongInt;
- begin
- if Length(Images) > 0 then
- begin
- for I := 0 to Length(Images) - 1 do
- FreeImage(Images[I]);
- SetLength(Images, 0);
- end;
- end;
- function TestImagesInArray(const Images: TDynImageDataArray): Boolean;
- var
- I: LongInt;
- begin
- if Length(Images) > 0 then
- begin
- Result := True;
- for I := 0 to Length(Images) - 1 do
- begin
- Result := Result and TestImage(Images[I]);
- if not Result then
- Break;
- end;
- end
- else
- Result := False;
- end;
- function DetermineFileFormat(const FileName: string): string;
- var
- I: LongInt;
- Fmt: TImageFileFormat;
- Handle: TImagingHandle;
- begin
- Assert(FileName <> '');
- Result := '';
- SetFileIO;
- Handle := IO.Open(PChar(FileName), omReadOnly);
- try
- // First file format according to FileName and test if the data in
- // file is really in that format
- for I := 0 to ImageFileFormats.Count - 1 do
- begin
- Fmt := TImageFileFormat(ImageFileFormats[I]);
- if Fmt.TestFileName(FileName) and Fmt.TestFormat(Handle) then
- begin
- Result := Fmt.Extensions[0];
- Exit;
- end;
- end;
- // No file format was found with filename search so try data-based search
- for I := 0 to ImageFileFormats.Count - 1 do
- begin
- Fmt := TImageFileFormat(ImageFileFormats[I]);
- if Fmt.TestFormat(Handle) then
- begin
- Result := Fmt.Extensions[0];
- Exit;
- end;
- end;
- finally
- IO.Close(Handle);
- end;
- end;
- function DetermineStreamFormat(Stream: TStream): string;
- var
- I: LongInt;
- Fmt: TImageFileFormat;
- Handle: TImagingHandle;
- begin
- Assert(Stream <> nil);
- Result := '';
- SetStreamIO;
- Handle := IO.Open(Pointer(Stream), omReadOnly);
- try
- for I := 0 to ImageFileFormats.Count - 1 do
- begin
- Fmt := TImageFileFormat(ImageFileFormats[I]);
- if Fmt.TestFormat(Handle) then
- begin
- Result := Fmt.Extensions[0];
- Exit;
- end;
- end;
- finally
- IO.Close(Handle);
- end;
- end;
- function DetermineMemoryFormat(Data: Pointer; Size: LongInt): string;
- var
- I: LongInt;
- Fmt: TImageFileFormat;
- Handle: TImagingHandle;
- IORec: TMemoryIORec;
- begin
- Assert((Data <> nil) and (Size > 0));
- Result := '';
- SetMemoryIO;
- IORec.Data := Data;
- IORec.Position := 0;
- IORec.Size := Size;
- Handle := IO.Open(@IORec, omReadOnly);
- try
- for I := 0 to ImageFileFormats.Count - 1 do
- begin
- Fmt := TImageFileFormat(ImageFileFormats[I]);
- if Fmt.TestFormat(Handle) then
- begin
- Result := Fmt.Extensions[0];
- Exit;
- end;
- end;
- finally
- IO.Close(Handle);
- end;
- end;
- function IsFileFormatSupported(const FileName: string): Boolean;
- begin
- Result := FindImageFileFormatByName(FileName) <> nil;
- end;
- function EnumFileFormats(var Index: LongInt; var Name, DefaultExt, Masks: string;
- var CanSaveImages, IsMultiImageFormat: Boolean): Boolean;
- var
- FileFmt: TImageFileFormat;
- begin
- FileFmt := GetFileFormatAtIndex(Index);
- Result := FileFmt <> nil;
- if Result then
- begin
- Name := FileFmt.Name;
- DefaultExt := FileFmt.Extensions[0];
- Masks := FileFmt.Masks.DelimitedText;
- CanSaveImages := FileFmt.CanSave;
- IsMultiImageFormat := FileFmt.IsMultiImageFormat;
- Inc(Index);
- end
- else
- begin
- Name := '';
- DefaultExt := '';
- Masks := '';
- CanSaveImages := False;
- IsMultiImageFormat := False;
- end;
- end;
- { Loading Functions }
- function LoadImageFromFile(const FileName: string; var Image: TImageData):
- Boolean;
- var
- Format: TImageFileFormat;
- IArray: TDynImageDataArray;
- I: LongInt;
- begin
- Assert(FileName <> '');
- Result := False;
- Format := FindImageFileFormatByExt(DetermineFileFormat(FileName));
- if Format <> nil then
- begin
- FreeImage(Image);
- Result := Format.LoadFromFile(FileName, IArray, True);
- if Result and (Length(IArray) > 0) then
- begin
- Image := IArray[0];
- for I := 1 to Length(IArray) - 1 do
- FreeImage(IArray[I]);
- end
- else
- Result := False;
- end;
- end;
- function LoadImageFromStream(Stream: TStream; var Image: TImageData): Boolean;
- var
- Format: TImageFileFormat;
- IArray: TDynImageDataArray;
- I: LongInt;
- begin
- Assert(Stream <> nil);
- if Stream.Size - Stream.Position = 0 then
- RaiseImaging(SErrorEmptyStream, []);
- Result := False;
- Format := FindImageFileFormatByExt(DetermineStreamFormat(Stream));
- if Format <> nil then
- begin
- FreeImage(Image);
- Result := Format.LoadFromStream(Stream, IArray, True);
- if Result and (Length(IArray) > 0) then
- begin
- Image := IArray[0];
- for I := 1 to Length(IArray) - 1 do
- FreeImage(IArray[I]);
- end
- else
- Result := False;
- end;
- end;
- function LoadImageFromMemory(Data: Pointer; Size: LongInt; var Image: TImageData): Boolean;
- var
- Format: TImageFileFormat;
- IArray: TDynImageDataArray;
- I: LongInt;
- begin
- Assert((Data <> nil) and (Size > 0));
- Result := False;
- Format := FindImageFileFormatByExt(DetermineMemoryFormat(Data, Size));
- if Format <> nil then
- begin
- FreeImage(Image);
- Result := Format.LoadFromMemory(Data, Size, IArray, True);
- if Result and (Length(IArray) > 0) then
- begin
- Image := IArray[0];
- for I := 1 to Length(IArray) - 1 do
- FreeImage(IArray[I]);
- end
- else
- Result := False;
- end;
- end;
- function LoadMultiImageFromFile(const FileName: string; var Images:
- TDynImageDataArray): Boolean;
- var
- Format: TImageFileFormat;
- begin
- Assert(FileName <> '');
- Result := False;
- Format := FindImageFileFormatByExt(DetermineFileFormat(FileName));
- if Format <> nil then
- begin
- FreeImagesInArray(Images);
- Result := Format.LoadFromFile(FileName, Images);
- end;
- end;
- function LoadMultiImageFromStream(Stream: TStream; var Images: TDynImageDataArray): Boolean;
- var
- Format: TImageFileFormat;
- begin
- Assert(Stream <> nil);
- if Stream.Size - Stream.Position = 0 then
- RaiseImaging(SErrorEmptyStream, []);
- Result := False;
- Format := FindImageFileFormatByExt(DetermineStreamFormat(Stream));
- if Format <> nil then
- begin
- FreeImagesInArray(Images);
- Result := Format.LoadFromStream(Stream, Images);
- end;
- end;
- function LoadMultiImageFromMemory(Data: Pointer; Size: LongInt;
- var Images: TDynImageDataArray): Boolean;
- var
- Format: TImageFileFormat;
- begin
- Assert((Data <> nil) and (Size > 0));
- Result := False;
- Format := FindImageFileFormatByExt(DetermineMemoryFormat(Data, Size));
- if Format <> nil then
- begin
- FreeImagesInArray(Images);
- Result := Format.LoadFromMemory(Data, Size, Images);
- end;
- end;
- { Saving Functions }
- function SaveImageToFile(const FileName: string; const Image: TImageData): Boolean;
- var
- Format: TImageFileFormat;
- IArray: TDynImageDataArray;
- begin
- Assert(FileName <> '');
- Result := False;
- Format := FindImageFileFormatByName(FileName);
- if Format <> nil then
- begin
- SetLength(IArray, 1);
- IArray[0] := Image;
- Result := Format.SaveToFile(FileName, IArray, True);
- end;
- end;
- function SaveImageToStream(const Ext: string; Stream: TStream;
- const Image: TImageData): Boolean;
- var
- Format: TImageFileFormat;
- IArray: TDynImageDataArray;
- begin
- Assert((Ext <> '') and (Stream <> nil));
- Result := False;
- Format := FindImageFileFormatByExt(Ext);
- if Format <> nil then
- begin
- SetLength(IArray, 1);
- IArray[0] := Image;
- Result := Format.SaveToStream(Stream, IArray, True);
- end;
- end;
- function SaveImageToMemory(const Ext: string; Data: Pointer; var Size: LongInt;
- const Image: TImageData): Boolean;
- var
- Format: TImageFileFormat;
- IArray: TDynImageDataArray;
- begin
- Assert((Ext <> '') and (Data <> nil) and (Size > 0));
- Result := False;
- Format := FindImageFileFormatByExt(Ext);
- if Format <> nil then
- begin
- SetLength(IArray, 1);
- IArray[0] := Image;
- Result := Format.SaveToMemory(Data, Size, IArray, True);
- end;
- end;
- function SaveMultiImageToFile(const FileName: string;
- const Images: TDynImageDataArray): Boolean;
- var
- Format: TImageFileFormat;
- begin
- Assert(FileName <> '');
- Result := False;
- Format := FindImageFileFormatByName(FileName);
- if Format <> nil then
- Result := Format.SaveToFile(FileName, Images);
- end;
- function SaveMultiImageToStream(const Ext: string; Stream: TStream;
- const Images: TDynImageDataArray): Boolean;
- var
- Format: TImageFileFormat;
- begin
- Assert((Ext <> '') and (Stream <> nil));
- Result := False;
- Format := FindImageFileFormatByExt(Ext);
- if Format <> nil then
- Result := Format.SaveToStream(Stream, Images);
- end;
- function SaveMultiImageToMemory(const Ext: string; Data: Pointer;
- var Size: LongInt; const Images: TDynImageDataArray): Boolean;
- var
- Format: TImageFileFormat;
- begin
- Assert((Ext <> '') and (Data <> nil) and (Size > 0));
- Result := False;
- Format := FindImageFileFormatByExt(Ext);
- if Format <> nil then
- Result := Format.SaveToMemory(Data, Size, Images);
- end;
- { Manipulation Functions }
- function CloneImage(const Image: TImageData; var Clone: TImageData): Boolean;
- var
- Info: PImageFormatInfo;
- begin
- Result := False;
- if TestImage(Image) then
- try
- if TestImage(Clone) and (Image.Bits <> Clone.Bits) then
- FreeImage(Clone)
- else
- InitImage(Clone);
- Info := ImageFormatInfos[Image.Format];
- Clone.Width := Image.Width;
- Clone.Height := Image.Height;
- Clone.Format := Image.Format;
- Clone.Size := Image.Size;
- if Info.PaletteEntries > 0 then
- begin
- GetMem(Clone.Palette, Info.PaletteEntries * SizeOf(TColor32Rec));
- Move(Image.Palette^, Clone.Palette^, Info.PaletteEntries *
- SizeOf(TColor32Rec));
- end;
- GetMem(Clone.Bits, Clone.Size);
- Move(Image.Bits^, Clone.Bits^, Clone.Size);
- Result := True;
- except
- raise UpdateExceptMessage(GetExceptObject, SErrorCloneImage, [ImageToStr(Image)]);
- end;
- end;
- function ConvertImage(var Image: TImageData; DestFormat: TImageFormat): Boolean;
- var
- NewData: Pointer;
- NewPal: PPalette32;
- NewSize, NumPixels: LongInt;
- SrcInfo, DstInfo: PImageFormatInfo;
- begin
- Assert(IsImageFormatValid(DestFormat));
- Result := False;
- if TestImage(Image) then
- with Image do
- try
- // If default format is set we use DefaultImageFormat
- if DestFormat = ifDefault then
- DestFormat := DefaultImageFormat;
- SrcInfo := ImageFormatInfos[Format];
- DstInfo := ImageFormatInfos[DestFormat];
- if SrcInfo = DstInfo then
- begin
- // There is nothing to convert - src is alredy in dest format
- Result := True;
- Exit;
- end;
- // Exit Src or Dest format is invalid
- if (SrcInfo = nil) or (DstInfo = nil) then Exit;
- // If dest format is just src with swapped channels we call
- // SwapChannels instead
- if (SrcInfo.RBSwapFormat = DestFormat) and
- (DstInfo.RBSwapFormat = SrcInfo.Format) then
- begin
- Result := SwapChannels(Image, ChannelRed, ChannelBlue);
- Image.Format := SrcInfo.RBSwapFormat;
- Exit;
- end;
- if (not SrcInfo.IsSpecial) and (not DstInfo.IsSpecial) then
- begin
- NumPixels := Width * Height;
- NewSize := NumPixels * DstInfo.BytesPerPixel;
- GetMem(NewData, NewSize);
- FillChar(NewData^, NewSize, 0);
- GetMem(NewPal, DstInfo.PaletteEntries * SizeOf(TColor32Rec));
- FillChar(NewPal^, DstInfo.PaletteEntries * SizeOf(TColor32Rec), 0);
- if SrcInfo.IsIndexed then
- begin
- // Source: indexed format
- if DstInfo.IsIndexed then
- IndexToIndex(NumPixels, Bits, NewData, SrcInfo, DstInfo, Palette, NewPal)
- else if DstInfo.HasGrayChannel then
- IndexToGray(NumPixels, Bits, NewData, SrcInfo, DstInfo, Palette)
- else if DstInfo.IsFloatingPoint then
- IndexToFloat(NumPixels, Bits, NewData, SrcInfo, DstInfo, Palette)
- else
- IndexToChannel(NumPixels, Bits, NewData, SrcInfo, DstInfo, Palette);
- end
- else if SrcInfo.HasGrayChannel then
- begin
- // Source: grayscale format
- if DstInfo.IsIndexed then
- GrayToIndex(NumPixels, Bits, NewData, SrcInfo, DstInfo, NewPal)
- else if DstInfo.HasGrayChannel then
- GrayToGray(NumPixels, Bits, NewData, SrcInfo, DstInfo)
- else if DstInfo.IsFloatingPoint then
- GrayToFloat(NumPixels, Bits, NewData, SrcInfo, DstInfo)
- else
- GrayToChannel(NumPixels, Bits, NewData, SrcInfo, DstInfo);
- end
- else if SrcInfo.IsFloatingPoint then
- begin
- // Source: floating point format
- if DstInfo.IsIndexed then
- FloatToIndex(NumPixels, Bits, NewData, SrcInfo, DstInfo, NewPal)
- else if DstInfo.HasGrayChannel then
- FloatToGray(NumPixels, Bits, NewData, SrcInfo, DstInfo)
- else if DstInfo.IsFloatingPoint then
- FloatToFloat(NumPixels, Bits, NewData, SrcInfo, DstInfo)
- else
- FloatToChannel(NumPixels, Bits, NewData, SrcInfo, DstInfo);
- end
- else
- begin
- // Source: standard multi channel image
- if DstInfo.IsIndexed then
- ChannelToIndex(NumPixels, Bits, NewData, SrcInfo, DstInfo, NewPal)
- else if DstInfo.HasGrayChannel then
- ChannelToGray(NumPixels, Bits, NewData, SrcInfo, DstInfo)
- else if DstInfo.IsFloatingPoint then
- ChannelToFloat(NumPixels, Bits, NewData, SrcInfo, DstInfo)
- else
- ChannelToChannel(NumPixels, Bits, NewData, SrcInfo, DstInfo);
- end;
- FreeMemNil(Bits);
- FreeMemNil(Palette);
- Format := DestFormat;
- Bits := NewData;
- Size := NewSize;
- Palette := NewPal;
- end
- else
- ConvertSpecial(Image, SrcInfo, DstInfo);
- Assert(SrcInfo.Format <> Image.Format);
- Result := True;
- except
- raise UpdateExceptMessage(GetExceptObject, SErrorConvertImage, [GetFormatName(DestFormat), ImageToStr(Image)]);
- end;
- end;
- function FlipImage(var Image: TImageData): Boolean;
- var
- P1, P2, Buff: Pointer;
- WidthBytes, I: LongInt;
- OldFmt: TImageFormat;
- begin
- Result := False;
- OldFmt := Image.Format;
- if TestImage(Image) then
- with Image do
- try
- if ImageFormatInfos[OldFmt].IsSpecial then
- ConvertImage(Image, ifDefault);
- WidthBytes := Width * ImageFormatInfos[Format].BytesPerPixel;
- GetMem(Buff, WidthBytes);
- try
- // Swap all scanlines of image
- for I := 0 to Height div 2 - 1 do
- begin
- P1 := @PByteArray(Bits)[I * WidthBytes];
- P2 := @PByteArray(Bits)[(Height - I - 1) * WidthBytes];
- Move(P1^, Buff^, WidthBytes);
- Move(P2^, P1^, WidthBytes);
- Move(Buff^, P2^, WidthBytes);
- end;
- finally
- FreeMemNil(Buff);
- end;
- if OldFmt <> Format then
- ConvertImage(Image, OldFmt);
- Result := True;
- except
- RaiseImaging(SErrorFlipImage, [ImageToStr(Image)]);
- end;
- end;
- function MirrorImage(var Image: TImageData): Boolean;
- var
- Scanline: PByte;
- Buff: TColorFPRec;
- Bpp, Y, X, WidthDiv2, WidthBytes, XLeft, XRight: LongInt;
- OldFmt: TImageFormat;
- begin
- Result := False;
- OldFmt := Image.Format;
- if TestImage(Image) then
- with Image do
- try
- if ImageFormatInfos[OldFmt].IsSpecial then
- ConvertImage(Image, ifDefault);
- Bpp := ImageFormatInfos[Format].BytesPerPixel;
- WidthDiv2 := Width div 2;
- WidthBytes := Width * Bpp;
- // Mirror all pixels on each scanline of image
- for Y := 0 to Height - 1 do
- begin
- Scanline := @PByteArray(Bits)[Y * WidthBytes];
- XLeft := 0;
- XRight := (Width - 1) * Bpp;
- for X := 0 to WidthDiv2 - 1 do
- begin
- CopyPixel(@PByteArray(Scanline)[XLeft], @Buff, Bpp);
- CopyPixel(@PByteArray(Scanline)[XRight],
- @PByteArray(Scanline)[XLeft], Bpp);
- CopyPixel(@Buff, @PByteArray(Scanline)[XRight], Bpp);
- Inc(XLeft, Bpp);
- Dec(XRight, Bpp);
- end;
- end;
- if OldFmt <> Format then
- ConvertImage(Image, OldFmt);
- Result := True;
- except
- RaiseImaging(SErrorMirrorImage, [ImageToStr(Image)]);
- end;
- end;
- function ResizeImage(var Image: TImageData; NewWidth, NewHeight: LongInt;
- Filter: TResizeFilter): Boolean;
- var
- WorkImage: TImageData;
- begin
- Assert((NewWidth > 0) and (NewHeight > 0), 'New width or height is zero.');
- Result := False;
- if TestImage(Image) and ((Image.Width <> NewWidth) or (Image.Height <> NewHeight)) then
- try
- InitImage(WorkImage);
- // Create new image with desired dimensions
- NewImage(NewWidth, NewHeight, Image.Format, WorkImage);
- // Stretch pixels from old image to new one
- StretchRect(Image, 0, 0, Image.Width, Image.Height,
- WorkImage, 0, 0, WorkImage.Width, WorkImage.Height, Filter);
- // Free old image and assign new image to it
- FreeMemNil(Image.Bits);
- if Image.Palette <> nil then
- begin
- FreeMem(WorkImage.Palette);
- WorkImage.Palette := Image.Palette;
- end;
- Image := WorkImage;
- Result := True;
- except
- raise UpdateExceptMessage(GetExceptObject, SErrorResizeImage, [ImageToStr(Image)]);
- end;
- end;
- function SwapChannels(var Image: TImageData; SrcChannel, DstChannel: LongInt): Boolean;
- var
- I, NumPixels: LongInt;
- Info: PImageFormatInfo;
- Swap, Alpha: Word;
- Data: PByte;
- Pix64: TColor64Rec;
- PixF: TColorFPRec;
- SwapF: Single;
- begin
- Assert((SrcChannel in [0..3]) and (DstChannel in [0..3]));
- Result := False;
- if TestImage(Image) and (SrcChannel <> DstChannel) then
- with Image do
- try
- NumPixels := Width * Height;
- Info := ImageFormatInfos[Format];
- Data := Bits;
- if (Info.Format = ifR8G8B8) or ((Info.Format = ifA8R8G8B8) and
- (SrcChannel <> ChannelAlpha) and (DstChannel <> ChannelAlpha)) then
- begin
- // Swap channels of most common formats R8G8B8 and A8R8G8B8 (no alpha)
- for I := 0 to NumPixels - 1 do
- with PColor24Rec(Data)^ do
- begin
- Swap := Channels[SrcChannel];
- Channels[SrcChannel] := Channels[DstChannel];
- Channels[DstChannel] := Swap;
- Inc(Data, Info.BytesPerPixel);
- end;
- end
- else if Info.IsIndexed then
- begin
- // Swap palette channels of indexed images
- SwapChannelsOfPalette(Palette, Info.PaletteEntries, SrcChannel, DstChannel)
- end
- else if Info.IsFloatingPoint then
- begin
- // Swap channels of floating point images
- for I := 0 to NumPixels - 1 do
- begin
- FloatGetSrcPixel(Data, Info, PixF);
- with PixF do
- begin
- SwapF := Channels[SrcChannel];
- Channels[SrcChannel] := Channels[DstChannel];
- Channels[DstChannel] := SwapF;
- end;
- FloatSetDstPixel(Data, Info, PixF);
- Inc(Data, Info.BytesPerPixel);
- end;
- end
- else if Info.IsSpecial then
- begin
- // Swap channels of special format images
- ConvertImage(Image, ifDefault);
- SwapChannels(Image, SrcChannel, DstChannel);
- ConvertImage(Image, Info.Format);
- end
- else if Info.HasGrayChannel and Info.HasAlphaChannel and
- ((SrcChannel = ChannelAlpha) or (DstChannel = ChannelAlpha)) then
- begin
- for I := 0 to NumPixels - 1 do
- begin
- // If we have grayscale image with alpha and alpha is channel
- // to be swapped, we swap it. No other alternative for gray images,
- // just alpha and something
- GrayGetSrcPixel(Data, Info, Pix64, Alpha);
- Swap := Alpha;
- Alpha := Pix64.A;
- Pix64.A := Swap;
- GraySetDstPixel(Data, Info, Pix64, Alpha);
- Inc(Data, Info.BytesPerPixel);
- end;
- end
- else
- begin
- // Then do general swap on other channel image formats
- for I := 0 to NumPixels - 1 do
- begin
- ChannelGetSrcPixel(Data, Info, Pix64);
- with Pix64 do
- begin
- Swap := Channels[SrcChannel];
- Channels[SrcChannel] := Channels[DstChannel];
- Channels[DstChannel] := Swap;
- end;
- ChannelSetDstPixel(Data, Info, Pix64);
- Inc(Data, Info.BytesPerPixel);
- end;
- end;
- Result := True;
- except
- RaiseImaging(SErrorSwapImage, [ImageToStr(Image)]);
- end;
- end;
- function ReduceColors(var Image: TImageData; MaxColors: LongInt): Boolean;
- var
- TmpInfo: TImageFormatInfo;
- Data, Index: PWord;
- I, NumPixels: LongInt;
- Pal: PPalette32;
- Col:PColor32Rec;
- OldFmt: TImageFormat;
- begin
- Result := False;
- if TestImage(Image) then
- with Image do
- try
- // First create temp image info and allocate output bits and palette
- MaxColors := ClampInt(MaxColors, 2, High(Word));
- OldFmt := Format;
- FillChar(TmpInfo, SizeOf(TmpInfo), 0);
- TmpInfo.PaletteEntries := MaxColors;
- TmpInfo.BytesPerPixel := 2;
- NumPixels := Width * Height;
- GetMem(Data, NumPixels * TmpInfo.BytesPerPixel);
- GetMem(Pal, MaxColors * SizeOf(TColor32Rec));
- ConvertImage(Image, ifA8R8G8B8);
- // We use median cut algorithm to create reduced palette and to
- // fill Data with indices to this palette
- ReduceColorsMedianCut(NumPixels, Bits, PByte(Data),
- ImageFormatInfos[Format], @TmpInfo, MaxColors, ColorReductionMask, Pal);
- Col := Bits;
- Index := Data;
- // Then we write reduced colors to the input image
- for I := 0 to NumPixels - 1 do
- begin
- Col.Color := Pal[Index^].Color;
- Inc(Col);
- Inc(Index);
- end;
- FreeMemNil(Data);
- FreeMemNil(Pal);
- // And convert it to its original format
- ConvertImage(Image, OldFmt);
- Result := True;
- except
- RaiseImaging(SErrorReduceColors, [MaxColors, ImageToStr(Image)]);
- end;
- end;
- function GenerateMipMaps(const Image: TImageData; Levels: LongInt;
- var MipMaps: TDynImageDataArray): Boolean;
- var
- Width, Height, I, Count: LongInt;
- Info: TImageFormatInfo;
- CompatibleCopy: TImageData;
- begin
- Result := False;
- if TestImage(Image) then
- try
- Width := Image.Width;
- Height := Image.Height;
- // We compute number of possible mipmap levels and if
- // the given levels are invalid or zero we use this value
- Count := GetNumMipMapLevels(Width, Height);
- if (Levels <= 0) or (Levels > Count) then
- Levels := Count;
- // If we have special format image we create copy to allow pixel access.
- // This is also done in FillMipMapLevel which is called for each level
- // but then the main big image would be converted to compatible
- // for every level.
- GetImageFormatInfo(Image.Format, Info);
- if Info.IsSpecial then
- begin
- InitImage(CompatibleCopy);
- CloneImage(Image, CompatibleCopy);
- ConvertImage(CompatibleCopy, ifDefault);
- end
- else
- CompatibleCopy := Image;
- FreeImagesInArray(MipMaps);
- SetLength(MipMaps, Levels);
- CloneImage(Image, MipMaps[0]);
- for I := 1 to Levels - 1 do
- begin
- Width := Width shr 1;
- Height := Height shr 1;
- if Width < 1 then Width := 1;
- if Height < 1 then Height := 1;
- FillMipMapLevel(CompatibleCopy, Width, Height, MipMaps[I]);
- end;
- if CompatibleCopy.Format <> MipMaps[0].Format then
- begin
- // Must convert smaller levels to proper format
- for I := 1 to High(MipMaps) do
- ConvertImage(MipMaps[I], MipMaps[0].Format);
- FreeImage(CompatibleCopy);
- end;
- Result := True;
- except
- RaiseImaging(SErrorGenerateMipMaps, [Levels, ImageToStr(Image)]);
- end;
- end;
- function MapImageToPalette(var Image: TImageData; Pal: PPalette32;
- Entries: LongInt): Boolean;
- function FindNearestColor(Pal: PPalette32; Entries: LongInt; Col: TColor32Rec): LongInt;
- var
- I, MinDif, Dif: LongInt;
- begin
- Result := 0;
- MinDif := 1020;
- for I := 0 to Entries - 1 do
- with Pal[I] do
- begin
- Dif := Abs(R - Col.R);
- if Dif > MinDif then Continue;
- Dif := Dif + Abs(G - Col.G);
- if Dif > MinDif then Continue;
- Dif := Dif + Abs(B - Col.B);
- if Dif > MinDif then Continue;
- Dif := Dif + Abs(A - Col.A);
- if Dif < MinDif then
- begin
- MinDif := Dif;
- Result := I;
- end;
- end;
- end;
- var
- I, MaxEntries: LongInt;
- PIndex: PByte;
- PColor: PColor32Rec;
- CloneARGB: TImageData;
- Info: PImageFormatInfo;
- begin
- Assert((Entries >= 2) and (Entries <= 256));
- Result := False;
- if TestImage(Image) then
- try
- // We create clone of source image in A8R8G8B8 and
- // then recreate source image in ifIndex8 format
- // with palette taken from Pal parameter
- InitImage(CloneARGB);
- CloneImage(Image, CloneARGB);
- ConvertImage(CloneARGB, ifA8R8G8B8);
- FreeImage(Image);
- NewImage(CloneARGB.Width, CloneARGB.Height, ifIndex8, Image);
- Info := ImageFormatInfos[Image.Format];
- MaxEntries := Min(Info.PaletteEntries, Entries);
- Move(Pal^, Image.Palette^, MaxEntries * SizeOf(TColor32Rec));
- PIndex := Image.Bits;
- PColor := CloneARGB.Bits;
- // For every pixel of ARGB clone we find closest color in
- // given palette and assign its index to resulting image's pixel
- // procedure used here is very slow but simple and memory usage friendly
- // (contrary to other methods)
- for I := 0 to Image.Width * Image.Height - 1 do
- begin
- PIndex^ := Byte(FindNearestColor(Image.Palette, MaxEntries, PColor^));
- Inc(PIndex);
- Inc(PColor);
- end;
- FreeImage(CloneARGB);
- Result := True;
- except
- raise UpdateExceptMessage(GetExceptObject, SErrorMapImage, [ImageToStr(Image)]);
- end;
- end;
- function SplitImage(var Image: TImageData; var Chunks: TDynImageDataArray;
- ChunkWidth, ChunkHeight: LongInt; var XChunks, YChunks: LongInt;
- PreserveSize: Boolean; Fill: Pointer): Boolean;
- var
- X, Y, XTrunc, YTrunc: LongInt;
- NotOnEdge: Boolean;
- Info: PImageFormatInfo;
- OldFmt: TImageFormat;
- begin
- Assert((ChunkWidth > 0) and (ChunkHeight > 0));
- Result := False;
- OldFmt := Image.Format;
- FreeImagesInArray(Chunks);
- if TestImage(Image) then
- try
- Info := ImageFormatInfos[Image.Format];
- if Info.IsSpecial then
- ConvertImage(Image, ifDefault);
- // We compute make sure that chunks are not larger than source image or negative
- ChunkWidth := ClampInt(ChunkWidth, 0, Image.Width);
- ChunkHeight := ClampInt(ChunkHeight, 0, Image.Height);
- // Number of chunks along X and Y axes is computed
- XChunks := Ceil(Image.Width / ChunkWidth);
- YChunks := Ceil(Image.Height / ChunkHeight);
- SetLength(Chunks, XChunks * YChunks);
- // For every chunk we create new image and copy a portion of
- // the source image to it. If chunk is on the edge of the source image
- // we fill enpty space with Fill pixel data if PreserveSize is set or
- // make the chunk smaller if it is not set
- for Y := 0 to YChunks - 1 do
- for X := 0 to XChunks - 1 do
- begin
- // Determine if current chunk is on the edge of original image
- NotOnEdge := ((X < XChunks - 1) and (Y < YChunks - 1)) or
- ((Image.Width mod ChunkWidth = 0) and (Image.Height mod ChunkHeight = 0));
- if PreserveSize or NotOnEdge then
- begin
- // We should preserve chunk sizes or we are somewhere inside original image
- NewImage(ChunkWidth, ChunkHeight, Image.Format, Chunks[Y * XChunks + X]);
- if (not NotOnEdge) and (Fill <> nil) then
- FillRect(Chunks[Y * XChunks + X], 0, 0, ChunkWidth, ChunkHeight, Fill);
- CopyRect(Image, X * ChunkWidth, Y * ChunkHeight, ChunkWidth, ChunkHeight,
- Chunks[Y * XChunks + X], 0, 0);
- end
- else
- begin
- // Create smaller edge chunk
- XTrunc := Image.Width - X * ChunkWidth;
- YTrunc := Image.Height - Y * ChunkHeight;
- NewImage(XTrunc, YTrunc, Image.Format, Chunks[Y * XChunks + X]);
- CopyRect(Image, X * ChunkWidth, Y * ChunkHeight, XTrunc, YTrunc,
- Chunks[Y * XChunks + X], 0, 0);
- end;
-
- // If source image is in indexed format we copy its palette to chunk
- if Info.IsIndexed then
- begin
- Move(Image.Palette^, Chunks[Y * XChunks + X].Palette^,
- Info.PaletteEntries * SizeOf(TColor32Rec));
- end;
- end;
- if OldFmt <> Image.Format then
- begin
- ConvertImage(Image, OldFmt);
- for X := 0 to Length(Chunks) - 1 do
- ConvertImage(Chunks[X], OldFmt);
- end;
- Result := True;
- except
- raise UpdateExceptMessage(GetExceptObject, SErrorSplitImage,
- [ImageToStr(Image), ChunkWidth, ChunkHeight]);
- end;
- end;
- function MakePaletteForImages(var Images: TDynImageDataArray; Pal: PPalette32;
- MaxColors: LongInt; ConvertImages: Boolean): Boolean;
- var
- I: Integer;
- SrcInfo, DstInfo: PImageFormatInfo;
- Target, TempImage: TImageData;
- DstFormat: TImageFormat;
- begin
- Assert((Pal <> nil) and (MaxColors > 0));
- Result := False;
- InitImage(TempImage);
- if TestImagesInArray(Images) then
- try
- // Null the color histogram
- ReduceColorsMedianCut(0, nil, nil, nil, nil, 0, 0, nil, [raCreateHistogram]);
- for I := 0 to Length(Images) - 1 do
- begin
- SrcInfo := ImageFormatInfos[Images[I].Format];
- if SrcInfo.IsIndexed or SrcInfo.IsSpecial then
- begin
- // create temp image in supported format for updating histogram
- CloneImage(Images[I], TempImage);
- ConvertImage(TempImage, ifA8R8G8B8);
- SrcInfo := ImageFormatInfos[TempImage.Format];
- end
- else
- TempImage := Images[I];
- // Update histogram with colors of each input image
- ReduceColorsMedianCut(TempImage.Width * TempImage.Height, TempImage.Bits,
- nil, SrcInfo, nil, MaxColors, ColorReductionMask, nil, [raUpdateHistogram]);
- if Images[I].Bits <> TempImage.Bits then
- FreeImage(TempImage);
- end;
- // Construct reduced color map from the histogram
- ReduceColorsMedianCut(0, nil, nil, nil, nil, MaxColors, ColorReductionMask,
- Pal, [raMakeColorMap]);
- if ConvertImages then
- begin
- DstFormat := ifIndex8;
- DstInfo := ImageFormatInfos[DstFormat];
- MaxColors := Min(DstInfo.PaletteEntries, MaxColors);
- for I := 0 to Length(Images) - 1 do
- begin
- SrcInfo := ImageFormatInfos[Images[I].Format];
- if SrcInfo.IsIndexed or SrcInfo.IsSpecial then
- begin
- // If source image is in format not supported by ReduceColorsMedianCut
- // we convert it
- ConvertImage(Images[I], ifA8R8G8B8);
- SrcInfo := ImageFormatInfos[Images[I].Format];
- end;
- InitImage(Target);
- NewImage(Images[I].Width, Images[I].Height, DstFormat, Target);
- // We map each input image to reduced palette and replace
- // image in array with mapped image
- ReduceColorsMedianCut(Images[I].Width * Images[I].Height, Images[I].Bits,
- Target.Bits, SrcInfo, DstInfo, MaxColors, 0, nil, [raMapImage]);
- Move(Pal^, Target.Palette^, MaxColors * SizeOf(TColor32Rec));
- FreeImage(Images[I]);
- Images[I] := Target;
- end;
- end;
- Result := True;
- except
- RaiseImaging(SErrorMakePaletteForImages, [MaxColors, Length(Images)]);
- end;
- end;
- procedure RotateImage(var Image: TImageData; Angle: Single);
- var
- OldFmt: TImageFormat;
- procedure XShear(var Src, Dst: TImageData; Row, Offset, Weight, Bpp: Integer);
- var
- I, J, XPos: Integer;
- PixSrc, PixLeft, PixOldLeft: TColor32Rec;
- LineDst: PByteArray;
- SrcPtr: PColor32;
- begin
- SrcPtr := @PByteArray(Src.Bits)[Row * Src.Width * Bpp];
- LineDst := @PByteArray(Dst.Bits)[Row * Dst.Width * Bpp];
- PixOldLeft.Color := 0;
- for I := 0 to Src.Width - 1 do
- begin
- CopyPixel(SrcPtr, @PixSrc, Bpp);
- for J := 0 to Bpp - 1 do
- PixLeft.Channels[J] := MulDiv(PixSrc.Channels[J], Weight, 256);
- XPos := I + Offset;
- if (XPos >= 0) and (XPos < Dst.Width) then
- begin
- for J := 0 to Bpp - 1 do
- PixSrc.Channels[J] := ClampToByte(PixSrc.Channels[J] - (PixLeft.Channels[J] - PixOldLeft.Channels[J]));
- CopyPixel(@PixSrc, @LineDst[XPos * Bpp], Bpp);
- end;
- PixOldLeft := PixLeft;
- Inc(PByte(SrcPtr), Bpp);
- end;
- XPos := Src.Width + Offset;
- if XPos < Dst.Width then
- CopyPixel(@PixOldLeft, @LineDst[XPos * Bpp], Bpp);
- end;
- procedure YShear(var Src, Dst: TImageData; Col, Offset, Weight, Bpp: Integer);
- var
- I, J, YPos: Integer;
- PixSrc, PixLeft, PixOldLeft: TColor32Rec;
- SrcPtr: PByte;
- begin
- SrcPtr := @PByteArray(Src.Bits)[Col * Bpp];
- PixOldLeft.Color := 0;
- for I := 0 to Src.Height - 1 do
- begin
- CopyPixel(SrcPtr, @PixSrc, Bpp);
- for J := 0 to Bpp - 1 do
- PixLeft.Channels[J] := MulDiv(PixSrc.Channels[J], Weight, 256);
- YPos := I + Offset;
- if (YPos >= 0) and (YPos < Dst.Height) then
- begin
- for J := 0 to Bpp - 1 do
- PixSrc.Channels[J] := ClampToByte(PixSrc.Channels[J] - (PixLeft.Channels[J] - PixOldLeft.Channels[J]));
- CopyPixel(@PixSrc, @PByteArray(Dst.Bits)[(YPos * Dst.Width + Col) * Bpp], Bpp);
- end;
- PixOldLeft := PixLeft;
- Inc(SrcPtr, Src.Width * Bpp);
- end;
- YPos := Src.Height + Offset;
- if YPos < Dst.Height then
- CopyPixel(@PixOldLeft, @PByteArray(Dst.Bits)[(YPos * Dst.Width + Col) * Bpp], Bpp);
- end;
- procedure Rotate45(var Image: TImageData; Angle: Single);
- var
- TempImage1, TempImage2: TImageData;
- AngleRad, AngleTan, AngleSin, AngleCos, Shear: Single;
- I, DstWidth, DstHeight, SrcWidth, SrcHeight, Bpp: Integer;
- SrcFmt, TempFormat: TImageFormat;
- Info: TImageFormatInfo;
- begin
- AngleRad := Angle * Pi / 180;
- AngleSin := Sin(AngleRad);
- AngleCos := Cos(AngleRad);
- AngleTan := Sin(AngleRad / 2) / Cos(AngleRad / 2);
- SrcWidth := Image.Width;
- SrcHeight := Image.Height;
- SrcFmt := Image.Format;
- if not (SrcFmt in [ifR8G8B8..ifX8R8G8B8, ifGray8..ifGray32, ifA16Gray16]) then
- ConvertImage(Image, ifA8R8G8B8);
- TempFormat := Image.Format;
- GetImageFormatInfo(TempFormat, Info);
- Bpp := Info.BytesPerPixel;
- // 1st shear (horizontal)
- DstWidth := Trunc(SrcWidth + SrcHeight * Abs(AngleTan) + 0.5);
- DstHeight := SrcHeight;
- InitImage(TempImage1);
- NewImage(DstWidth, DstHeight, TempFormat, TempImage1);
- for I := 0 to DstHeight - 1 do
- begin
- if AngleTan >= 0 then
- Shear := (I + 0.5) * AngleTan
- else
- Shear := (I - DstHeight + 0.5) * AngleTan;
- XShear(Image, TempImage1, I, Floor(Shear), Trunc(255 * (Shear - Floor(Shear)) + 1), Bpp);
- end;
- // 2nd shear (vertical)
- FreeImage(Image);
- DstHeight := Trunc(SrcWidth * Abs(AngleSin) + SrcHeight * AngleCos + 0.5) + 1;
- InitImage(TempImage2);
- NewImage(DstWidth, DstHeight, TempFormat, TempImage2);
- if AngleSin >= 0 then
- Shear := (SrcWidth - 1) * AngleSin
- else
- Shear := (SrcWidth - DstWidth) * -AngleSin;
- for I := 0 to DstWidth - 1 do
- begin
- YShear(TempImage1, TempImage2, I, Floor(Shear), Trunc(255 * (Shear - Floor(Shear)) + 1), Bpp);
- Shear := Shear - AngleSin;
- end;
- // 3rd shear (horizontal)
- FreeImage(TempImage1);
- DstWidth := Trunc(SrcHeight * Abs(AngleSin) + SrcWidth * AngleCos + 0.5) + 1;
- NewImage(DstWidth, DstHeight, TempFormat, Image);
- if AngleSin >= 0 then
- Shear := (SrcWidth - 1) * AngleSin * -AngleTan
- else
- Shear := ((SrcWidth - 1) * -AngleSin + (1 - DstHeight)) * AngleTan;
- for I := 0 to DstHeight - 1 do
- begin
- XShear(TempImage2, Image, I, Floor(Shear), Trunc(255 * (Shear - Floor(Shear)) + 1), Bpp);
- Shear := Shear + AngleTan;
- end;
- FreeImage(TempImage2);
- if Image.Format <> SrcFmt then
- ConvertImage(Image, SrcFmt);
- end;
- procedure RotateMul90(var Image: TImageData; Angle: Integer);
- var
- RotImage: TImageData;
- X, Y, BytesPerPixel: Integer;
- RotPix, Pix: PByte;
- begin
- InitImage(RotImage);
- BytesPerPixel := ImageFormatInfos[Image.Format].BytesPerPixel;
- if ((Angle = 90) or (Angle = 270)) and (Image.Width <> Image.Height) then
- NewImage(Image.Height, Image.Width, Image.Format, RotImage)
- else
- NewImage(Image.Width, Image.Height, Image.Format, RotImage);
- RotPix := RotImage.Bits;
- case Angle of
- 90:
- begin
- for Y := 0 to RotImage.Height - 1 do
- begin
- Pix := @PByteArray(Image.Bits)[(Image.Width - Y - 1) * BytesPerPixel];
- for X := 0 to RotImage.Width - 1 do
- begin
- CopyPixel(Pix, RotPix, BytesPerPixel);
- Inc(RotPix, BytesPerPixel);
- Inc(Pix, Image.Width * BytesPerPixel);
- end;
- end;
- end;
- 180:
- begin
- Pix := @PByteArray(Image.Bits)[((Image.Height - 1) * Image.Width +
- (Image.Width - 1)) * BytesPerPixel];
- for Y := 0 to RotImage.Height - 1 do
- for X := 0 to RotImage.Width - 1 do
- begin
- CopyPixel(Pix, RotPix, BytesPerPixel);
- Inc(RotPix, BytesPerPixel);
- Dec(Pix, BytesPerPixel);
- end;
- end;
- 270:
- begin
- for Y := 0 to RotImage.Height - 1 do
- begin
- Pix := @PByteArray(Image.Bits)[((Image.Height - 1) * Image.Width + Y) * BytesPerPixel];
- for X := 0 to RotImage.Width - 1 do
- begin
- CopyPixel(Pix, RotPix, BytesPerPixel);
- Inc(RotPix, BytesPerPixel);
- Dec(Pix, Image.Width * BytesPerPixel);
- end;
- end;
- end;
- end;
- FreeMemNil(Image.Bits);
- RotImage.Palette := Image.Palette;
- Image := RotImage;
- end;
- begin
- if TestImage(Image) then
- try
- while Angle >= 360 do
- Angle := Angle - 360;
- while Angle < 0 do
- Angle := Angle + 360;
- if (Angle = 0) or (Abs(Angle) = 360) then
- Exit;
- OldFmt := Image.Format;
- if ImageFormatInfos[Image.Format].IsSpecial then
- ConvertImage(Image, ifDefault);
- if (Angle > 45) and (Angle <= 135) then
- begin
- RotateMul90(Image, 90);
- Angle := Angle - 90;
- end
- else if (Angle > 135) and (Angle <= 225) then
- begin
- RotateMul90(Image, 180);
- Angle := Angle - 180;
- end
- else if (Angle > 225) and (Angle <= 315) then
- begin
- RotateMul90(Image, 270);
- Angle := Angle - 270;
- end;
- if Angle <> 0 then
- Rotate45(Image, Angle);
- if OldFmt <> Image.Format then
- ConvertImage(Image, OldFmt);
- except
- raise UpdateExceptMessage(GetExceptObject, SErrorRotateImage, [ImageToStr(Image), Angle]);
- end;
- end;
- { Drawing/Pixel functions }
- function CopyRect(const SrcImage: TImageData; SrcX, SrcY, Width, Height: LongInt;
- var DstImage: TImageData; DstX, DstY: LongInt): Boolean;
- var
- Info: PImageFormatInfo;
- I, SrcWidthBytes, DstWidthBytes, MoveBytes: LongInt;
- SrcPointer, DstPointer: PByte;
- WorkImage: TImageData;
- OldFormat: TImageFormat;
- begin
- Result := False;
- OldFormat := ifUnknown;
- if TestImage(SrcImage) and TestImage(DstImage) then
- try
- // Make sure we are still copying image to image, not invalid pointer to protected memory
- ClipCopyBounds(SrcX, SrcY, Width, Height, DstX, DstY, SrcImage.Width, SrcImage.Height,
- Rect(0, 0, DstImage.Width, DstImage.Height));
- if (Width > 0) and (Height > 0) then
- begin
- Info := ImageFormatInfos[DstImage.Format];
- if Info.IsSpecial then
- begin
- // If dest image is in special format we convert it to default
- OldFormat := Info.Format;
- ConvertImage(DstImage, ifDefault);
- Info := ImageFormatInfos[DstImage.Format];
- end;
- if SrcImage.Format <> DstImage.Format then
- begin
- // If images are in different format source is converted to dest's format
- InitImage(WorkImage);
- CloneImage(SrcImage, WorkImage);
- ConvertImage(WorkImage, DstImage.Format);
- end
- else
- WorkImage := SrcImage;
- MoveBytes := Width * Info.BytesPerPixel;
- DstWidthBytes := DstImage.Width * Info.BytesPerPixel;
- DstPointer := @PByteArray(DstImage.Bits)[DstY * DstWidthBytes +
- DstX * Info.BytesPerPixel];
- SrcWidthBytes := WorkImage.Width * Info.BytesPerPixel;
- SrcPointer := @PByteArray(WorkImage.Bits)[SrcY * SrcWidthBytes +
- SrcX * Info.BytesPerPixel];
- for I := 0 to Height - 1 do
- begin
- Move(SrcPointer^, DstPointer^, MoveBytes);
- Inc(SrcPointer, SrcWidthBytes);
- Inc(DstPointer, DstWidthBytes);
- end;
- // If dest image was in special format we convert it back
- if OldFormat <> ifUnknown then
- ConvertImage(DstImage, OldFormat);
- // Working image must be freed if it is not the same as source image
- if WorkImage.Bits <> SrcImage.Bits then
- FreeImage(WorkImage);
- Result := True;
- end;
- except
- RaiseImaging(SErrorCopyRect, [ImageToStr(SrcImage), ImageToStr(DstImage)]);
- end;
- end;
- function FillRect(var Image: TImageData; X, Y, Width, Height: LongInt;
- FillColor: Pointer): Boolean;
- var
- Info: PImageFormatInfo;
- I, J, ImageWidthBytes, RectWidthBytes, Bpp: Longint;
- LinePointer, PixPointer: PByte;
- OldFmt: TImageFormat;
- begin
- Result := False;
- if TestImage(Image) then
- try
- ClipRectBounds(X, Y, Width, Height, Rect(0, 0, Image.Width, Image.Height));
- if (Width > 0) and (Height > 0) then
- begin
- OldFmt := Image.Format;
- if ImageFormatInfos[OldFmt].IsSpecial then
- ConvertImage(Image, ifDefault);
- Info := ImageFormatInfos[Image.Format];
- Bpp := Info.BytesPerPixel;
- ImageWidthBytes := Image.Width * Bpp;
- RectWidthBytes := Width * Bpp;
- LinePointer := @PByteArray(Image.Bits)[Y * ImageWidthBytes + X * Bpp];
- for I := 0 to Height - 1 do
- begin
- case Bpp of
- 1: FillMemoryByte(LinePointer, RectWidthBytes, PByte(FillColor)^);
- 2: FillMemoryWord(LinePointer, RectWidthBytes, PWord(FillColor)^);
- 4: FillMemoryUInt32(LinePointer, RectWidthBytes, PUInt32(FillColor)^);
- else
- PixPointer := LinePointer;
- for J := 0 to Width - 1 do
- begin
- CopyPixel(FillColor, PixPointer, Bpp);
- Inc(PixPointer, Bpp);
- end;
- end;
- Inc(LinePointer, ImageWidthBytes);
- end;
- if OldFmt <> Image.Format then
- ConvertImage(Image, OldFmt);
- end;
- Result := True;
- except
- RaiseImaging(SErrorFillRect, [X, Y, Width, Height, ImageToStr(Image)]);
- end;
- end;
- function ReplaceColor(var Image: TImageData; X, Y, Width, Height: LongInt;
- OldColor, NewColor: Pointer): Boolean;
- var
- Info: PImageFormatInfo;
- I, J, WidthBytes, Bpp: Longint;
- LinePointer, PixPointer: PByte;
- OldFmt: TImageFormat;
- begin
- Assert((OldColor <> nil) and (NewColor <> nil));
- Result := False;
- if TestImage(Image) then
- try
- ClipRectBounds(X, Y, Width, Height, Rect(0, 0, Image.Width, Image.Height));
- if (Width > 0) and (Height > 0) then
- begin
- OldFmt := Image.Format;
- if ImageFormatInfos[OldFmt].IsSpecial then
- ConvertImage(Image, ifDefault);
- Info := ImageFormatInfos[Image.Format];
- Bpp := Info.BytesPerPixel;
- WidthBytes := Image.Width * Bpp;
- LinePointer := @PByteArray(Image.Bits)[Y * WidthBytes + X * Bpp];
- for I := 0 to Height - 1 do
- begin
- PixPointer := LinePointer;
- for J := 0 to Width - 1 do
- begin
- if ComparePixels(PixPointer, OldColor, Bpp) then
- CopyPixel(NewColor, PixPointer, Bpp);
- Inc(PixPointer, Bpp);
- end;
- Inc(LinePointer, WidthBytes);
- end;
- if OldFmt <> Image.Format then
- ConvertImage(Image, OldFmt);
- end;
- Result := True;
- except
- RaiseImaging(SErrorReplaceColor, [X, Y, Width, Height, ImageToStr(Image)]);
- end;
- end;
- function StretchRect(const SrcImage: TImageData; SrcX, SrcY, SrcWidth,
- SrcHeight: LongInt; var DstImage: TImageData; DstX, DstY, DstWidth,
- DstHeight: LongInt; Filter: TResizeFilter): Boolean;
- var
- Info: PImageFormatInfo;
- WorkImage: TImageData;
- OldFormat: TImageFormat;
- Resampling: TSamplingFilter;
- begin
- Result := False;
- OldFormat := ifUnknown;
- if TestImage(SrcImage) and TestImage(DstImage) then
- try
- // Make sure we are still copying image to image, not invalid pointer to protected memory
- ClipStretchBounds(SrcX, SrcY, SrcWidth, SrcHeight, DstX, DstY, DstWidth, DstHeight,
- SrcImage.Width, SrcImage.Height, Rect(0, 0, DstImage.Width, DstImage.Height));
- if (SrcWidth = DstWidth) and (SrcHeight = DstHeight) then
- begin
- // If source and dest rectangles have the same size call CopyRect
- Result := CopyRect(SrcImage, SrcX, SrcY, SrcWidth, SrcHeight, DstImage, DstX, DstY);
- end
- else if (SrcWidth > 0) and (SrcHeight > 0) and (DstWidth > 0) and (DstHeight > 0) then
- begin
- // If source and dest rectangles don't have the same size we do stretch
- Info := ImageFormatInfos[DstImage.Format];
- if Info.IsSpecial then
- begin
- // If dest image is in special format we convert it to default
- OldFormat := Info.Format;
- ConvertImage(DstImage, ifDefault);
- Info := ImageFormatInfos[DstImage.Format];
- end;
- if SrcImage.Format <> DstImage.Format then
- begin
- // If images are in different format source is converted to dest's format
- InitImage(WorkImage);
- CloneImage(SrcImage, WorkImage);
- ConvertImage(WorkImage, DstImage.Format);
- end
- else
- WorkImage := SrcImage;
- // Only pixel resize is supported for indexed images
- if Info.IsIndexed then
- Filter := rfNearest;
- if Filter = rfNearest then
- begin
- StretchNearest(WorkImage, SrcX, SrcY, SrcWidth, SrcHeight,
- DstImage, DstX, DstY, DstWidth, DstHeight);
- end
- else
- begin
- Resampling := sfNearest;
- case Filter of
- rfBilinear: Resampling := sfLinear;
- rfBicubic: Resampling := DefaultCubicFilter;
- rfLanczos: Resampling := sfLanczos;
- end;
- StretchResample(WorkImage, SrcX, SrcY, SrcWidth, SrcHeight,
- DstImage, DstX, DstY, DstWidth, DstHeight, Resampling);
- end;
- // If dest image was in special format we convert it back
- if OldFormat <> ifUnknown then
- ConvertImage(DstImage, OldFormat);
- // Working image must be freed if it is not the same as source image
- if WorkImage.Bits <> SrcImage.Bits then
- FreeImage(WorkImage);
- Result := True;
- end;
- except
- RaiseImaging(SErrorStretchRect, [ImageToStr(SrcImage), ImageToStr(DstImage)]);
- end;
- end;
- procedure GetPixelDirect(const Image: TImageData; X, Y: LongInt; Pixel: Pointer);
- var
- BytesPerPixel: LongInt;
- begin
- Assert(Pixel <> nil);
- BytesPerPixel := ImageFormatInfos[Image.Format].BytesPerPixel;
- CopyPixel(@PByteArray(Image.Bits)[(Y * Image.Width + X) * BytesPerPixel],
- Pixel, BytesPerPixel);
- end;
- procedure SetPixelDirect(const Image: TImageData; X, Y: LongInt; Pixel: Pointer);
- var
- BytesPerPixel: LongInt;
- begin
- Assert(Pixel <> nil);
- BytesPerPixel := ImageFormatInfos[Image.Format].BytesPerPixel;
- CopyPixel(Pixel, @PByteArray(Image.Bits)[(Y * Image.Width + X) * BytesPerPixel],
- BytesPerPixel);
- end;
- function GetPixel32(const Image: TImageData; X, Y: LongInt): TColor32Rec;
- var
- Info: PImageFormatInfo;
- Data: PByte;
- begin
- Info := ImageFormatInfos[Image.Format];
- Data := @PByteArray(Image.Bits)[(Y * Image.Width + X) * Info.BytesPerPixel];
- Result := GetPixel32Generic(Data, Info, Image.Palette);
- end;
- procedure SetPixel32(const Image: TImageData; X, Y: LongInt; const Color: TColor32Rec);
- var
- Info: PImageFormatInfo;
- Data: PByte;
- begin
- Info := ImageFormatInfos[Image.Format];
- Data := @PByteArray(Image.Bits)[(Y * Image.Width + X) * Info.BytesPerPixel];
- SetPixel32Generic(Data, Info, Image.Palette, Color);
- end;
- function GetPixelFP(const Image: TImageData; X, Y: LongInt): TColorFPRec;
- var
- Info: PImageFormatInfo;
- Data: PByte;
- begin
- Info := ImageFormatInfos[Image.Format];
- Data := @PByteArray(Image.Bits)[(Y * Image.Width + X) * Info.BytesPerPixel];
- Result := GetPixelFPGeneric(Data, Info, Image.Palette);
- end;
- procedure SetPixelFP(const Image: TImageData; X, Y: LongInt; const Color: TColorFPRec);
- var
- Info: PImageFormatInfo;
- Data: PByte;
- begin
- Info := ImageFormatInfos[Image.Format];
- Data := @PByteArray(Image.Bits)[(Y * Image.Width + X) * Info.BytesPerPixel];
- SetPixelFPGeneric(Data, Info, Image.Palette, Color);
- end;
- { Palette Functions }
- procedure NewPalette(Entries: LongInt; var Pal: PPalette32);
- begin
- Assert((Entries > 2) and (Entries <= 65535));
- try
- GetMem(Pal, Entries * SizeOf(TColor32Rec));
- FillChar(Pal^, Entries * SizeOf(TColor32Rec), $FF);
- except
- RaiseImaging(SErrorNewPalette, [Entries]);
- end;
- end;
- procedure FreePalette(var Pal: PPalette32);
- begin
- try
- FreeMemNil(Pal);
- except
- RaiseImaging(SErrorFreePalette, [Pal]);
- end;
- end;
- procedure CopyPalette(SrcPal, DstPal: PPalette32; SrcIdx, DstIdx, Count: LongInt);
- begin
- Assert((SrcPal <> nil) and (DstPal <> nil));
- Assert((SrcIdx >= 0) and (DstIdx >= 0) and (Count >= 0));
- try
- Move(SrcPal[SrcIdx], DstPal[DstIdx], Count * SizeOf(TColor32Rec));
- except
- RaiseImaging(SErrorCopyPalette, [Count, SrcPal, DstPal]);
- end;
- end;
- function FindColor(Pal: PPalette32; Entries: LongInt; Color: TColor32):
- LongInt;
- var
- Col: TColor32Rec;
- I, MinDif, Dif: LongInt;
- begin
- Assert(Pal <> nil);
- Result := -1;
- Col.Color := Color;
- try
- // First try to find exact match
- for I := 0 to Entries - 1 do
- with Pal[I] do
- begin
- if (A = Col.A) and (R = Col.R) and
- (G = Col.G) and (B = Col.B) then
- begin
- Result := I;
- Exit;
- end;
- end;
- // If exact match was not found, find nearest color
- MinDif := 1020;
- for I := 0 to Entries - 1 do
- with Pal[I] do
- begin
- Dif := Abs(R - Col.R);
- if Dif > MinDif then Continue;
- Dif := Dif + Abs(G - Col.G);
- if Dif > MinDif then Continue;
- Dif := Dif + Abs(B - Col.B);
- if Dif > MinDif then Continue;
- Dif := Dif + Abs(A - Col.A);
- if Dif < MinDif then
- begin
- MinDif := Dif;
- Result := I;
- end;
- end;
- except
- RaiseImaging(SErrorFindColor, [Pal, Entries]);
- end;
- end;
- procedure FillGrayscalePalette(Pal: PPalette32; Entries: LongInt);
- var
- I: LongInt;
- begin
- Assert(Pal <> nil);
- try
- for I := 0 to Entries - 1 do
- with Pal[I] do
- begin
- A := $FF;
- R := Byte(I);
- G := Byte(I);
- B := Byte(I);
- end;
- except
- RaiseImaging(SErrorGrayscalePalette, [Pal, Entries]);
- end;
- end;
- procedure FillCustomPalette(Pal: PPalette32; Entries: LongInt; RBits, GBits,
- BBits: Byte; Alpha: Byte = $FF);
- var
- I, TotalBits, MaxEntries: LongInt;
- begin
- Assert(Pal <> nil);
- TotalBits := RBits + GBits + BBits;
- MaxEntries := Min(Pow2Int(TotalBits), Entries);
- FillChar(Pal^, Entries * SizeOf(TColor32Rec), 0);
- try
- for I := 0 to MaxEntries - 1 do
- with Pal[I] do
- begin
- A := Alpha;
- if RBits > 0 then
- R := ((I shr Max(0, GBits + BBits - 1)) and (1 shl RBits - 1)) * 255 div (1 shl RBits - 1);
- if GBits > 0 then
- G := ((I shr Max(0, BBits - 1)) and (1 shl GBits - 1)) * 255 div (1 shl GBits - 1);
- if BBits > 0 then
- B := ((I shr 0) and (1 shl BBits - 1)) * 255 div (1 shl BBits - 1);
- end;
- except
- RaiseImaging(SErrorCustomPalette, [Pal, Entries]);
- end;
- end;
- procedure SwapChannelsOfPalette(Pal: PPalette32; Entries, SrcChannel,
- DstChannel: LongInt);
- var
- I: LongInt;
- Swap: Byte;
- begin
- Assert(Pal <> nil);
- Assert((SrcChannel in [0..3]) and (DstChannel in [0..3]));
- try
- for I := 0 to Entries - 1 do
- with Pal[I] do
- begin
- Swap := Channels[SrcChannel];
- Channels[SrcChannel] := Channels[DstChannel];
- Channels[DstChannel] := Swap;
- end;
- except
- RaiseImaging(SErrorSwapPalette, [Pal, Entries]);
- end;
- end;
- { Options Functions }
- function SetOption(OptionId, Value: LongInt): Boolean;
- begin
- Result := False;
- if (OptionId >= 0) and (OptionId < Length(Options)) and
- (Options[OptionID] <> nil) then
- begin
- Options[OptionID]^ := CheckOptionValue(OptionId, Value);
- Result := True;
- end;
- end;
- function GetOption(OptionId: LongInt): LongInt;
- begin
- Result := InvalidOption;
- if (OptionId >= 0) and (OptionId < Length(Options)) and
- (Options[OptionID] <> nil) then
- begin
- Result := Options[OptionID]^;
- end;
- end;
- function PushOptions: Boolean;
- begin
- Result := OptionStack.Push;
- end;
- function PopOptions: Boolean;
- begin
- Result := OptionStack.Pop;
- end;
- { Image Format Functions }
- function GetImageFormatInfo(Format: TImageFormat; out Info: TImageFormatInfo): Boolean;
- begin
- FillChar(Info, SizeOf(Info), 0);
- if ImageFormatInfos[Format] <> nil then
- begin
- Info := ImageFormatInfos[Format]^;
- Result := True;
- end
- else
- Result := False;
- end;
- function GetPixelsSize(Format: TImageFormat; Width, Height: LongInt): LongInt;
- begin
- if ImageFormatInfos[Format] <> nil then
- Result := ImageFormatInfos[Format].GetPixelsSize(Format, Width, Height)
- else
- Result := 0;
- end;
- { IO Functions }
- procedure SetUserFileIO(OpenProc: TOpenProc;
- CloseProc: TCloseProc; EofProc: TEofProc; SeekProc: TSeekProc; TellProc:
- TTellProc; ReadProc: TReadProc; WriteProc: TWriteProc);
- begin
- FileIO.Open := OpenProc;
- FileIO.Close := CloseProc;
- FileIO.Eof := EofProc;
- FileIO.Seek := SeekProc;
- FileIO.Tell := TellProc;
- FileIO.Read := ReadProc;
- FileIO.Write := WriteProc;
- end;
- procedure ResetFileIO;
- begin
- FileIO := OriginalFileIO;
- end;
- { Raw Image IO Functions }
- procedure ReadRawImage(Handle: TImagingHandle; Width, Height: Integer;
- Format: TImageFormat; var Image: TImageData; Offset, RowLength: Integer);
- var
- WidthBytes, I: Integer;
- Info: PImageFormatInfo;
- begin
- Info := ImageFormatInfos[Format];
- // Calc scanline size
- WidthBytes := Info.GetPixelsSize(Format, Width, 1);
- if RowLength = 0 then
- RowLength := WidthBytes;
- // Create new image if needed - don't need to allocate new one if there is already
- // one with desired size and format
- if (Image.Width <> Width) or (Image.Height <> Height) or (Image.Format <> Format) then
- NewImage(Width, Height, Format, Image);
- // Move past the header
- IO.Seek(Handle, Offset, smFromCurrent);
- // Read scanlines from input
- for I := 0 to Height - 1 do
- begin
- IO.Read(Handle, @PByteArray(Image.Bits)[I * WidthBytes], WidthBytes);
- IO.Seek(Handle, RowLength - WidthBytes, smFromCurrent);
- end;
- end;
- procedure ReadRawImageFromFile(const FileName: string; Width, Height: Integer;
- Format: TImageFormat; var Image: TImageData; Offset, RowLength: Integer);
- var
- Handle: TImagingHandle;
- begin
- Assert(FileName <> '');
- // Set IO ops to file ops and open given file
- SetFileIO;
- Handle := IO.Open(PChar(FileName), omReadOnly);
- try
- ReadRawImage(Handle, Width, Height, Format, Image, Offset, RowLength);
- finally
- IO.Close(Handle);
- end;
- end;
- procedure ReadRawImageFromStream(Stream: TStream; Width, Height: Integer;
- Format: TImageFormat; var Image: TImageData; Offset, RowLength: Integer);
- var
- Handle: TImagingHandle;
- begin
- Assert(Stream <> nil);
- if Stream.Size - Stream.Position = 0 then
- RaiseImaging(SErrorEmptyStream, []);
- // Set IO ops to stream ops and open given stream
- SetStreamIO;
- Handle := IO.Open(Pointer(Stream), omReadOnly);
- try
- ReadRawImage(Handle, Width, Height, Format, Image, Offset, RowLength);
- finally
- IO.Close(Handle);
- end;
- end;
- procedure ReadRawImageFromMemory(Data: Pointer; DataSize: Integer; Width, Height: Integer;
- Format: TImageFormat; var Image: TImageData; Offset, RowLength: Integer);
- var
- Handle: TImagingHandle;
- MemRec: TMemoryIORec;
- begin
- Assert((Data <> nil) and (DataSize > 0));
- // Set IO ops to memory ops and open given stream
- SetMemoryIO;
- MemRec := PrepareMemIO(Data, DataSize);
- Handle := IO.Open(@MemRec, omReadOnly);
- try
- ReadRawImage(Handle, Width, Height, Format, Image, Offset, RowLength);
- finally
- IO.Close(Handle);
- end;
- end;
- procedure ReadRawImageRect(Data: Pointer; Left, Top, Width, Height: Integer;
- var Image: TImageData; Offset, RowLength: Integer);
- var
- DestScanBytes, RectBytes, I: Integer;
- Info: PImageFormatInfo;
- Src, Dest: PByte;
- begin
- Assert(Data <> nil);
- Assert((Left + Width <= Image.Width) and (Top + Height <= Image.Height));
- Info := ImageFormatInfos[Image.Format];
- // Calc scanline size
- DestScanBytes := Info.GetPixelsSize(Info.Format, Image.Width, 1);
- RectBytes := Info.GetPixelsSize(Info.Format, Width, 1);
- if RowLength = 0 then
- RowLength := RectBytes;
- Src := Data;
- Dest := @PByteArray(Image.Bits)[Top * DestScanBytes + Info.GetPixelsSize(Info.Format, Left, 1)];
- // Move past the header
- Inc(Src, Offset);
- // Read lines into rect in the existing image
- for I := 0 to Height - 1 do
- begin
- Move(Src^, Dest^, RectBytes);
- Inc(Src, RowLength);
- Inc(Dest, DestScanBytes);
- end;
- end;
- procedure WriteRawImage(Handle: TImagingHandle; const Image: TImageData;
- Offset, RowLength: Integer);
- var
- WidthBytes, I: Integer;
- Info: PImageFormatInfo;
- begin
- Info := ImageFormatInfos[Image.Format];
- // Calc scanline size
- WidthBytes := Info.GetPixelsSize(Image.Format, Image.Width, 1);
- if RowLength = 0 then
- RowLength := WidthBytes;
- // Move past the header
- IO.Seek(Handle, Offset, smFromCurrent);
- // Write scanlines to output
- for I := 0 to Image.Height - 1 do
- begin
- IO.Write(Handle, @PByteArray(Image.Bits)[I * WidthBytes], WidthBytes);
- IO.Seek(Handle, RowLength - WidthBytes, smFromCurrent);
- end;
- end;
- procedure WriteRawImageToFile(const FileName: string; const Image: TImageData;
- Offset, RowLength: Integer);
- var
- Handle: TImagingHandle;
- begin
- Assert(FileName <> '');
- // Set IO ops to file ops and open given file
- SetFileIO;
- Handle := IO.Open(PChar(FileName), omCreate);
- try
- WriteRawImage(Handle, Image, Offset, RowLength);
- finally
- IO.Close(Handle);
- end;
- end;
- procedure WriteRawImageToStream(Stream: TStream; const Image: TImageData;
- Offset, RowLength: Integer);
- var
- Handle: TImagingHandle;
- begin
- Assert(Stream <> nil);
- // Set IO ops to stream ops and open given stream
- SetStreamIO;
- Handle := IO.Open(Pointer(Stream), omCreate);
- try
- WriteRawImage(Handle, Image, Offset, RowLength);
- finally
- IO.Close(Handle);
- end;
- end;
- procedure WriteRawImageToMemory(Data: Pointer; DataSize: Integer; const Image: TImageData;
- Offset, RowLength: Integer);
- var
- Handle: TImagingHandle;
- MemRec: TMemoryIORec;
- begin
- Assert((Data <> nil) and (DataSize > 0));
- // Set IO ops to memory ops and open given stream
- SetMemoryIO;
- MemRec := PrepareMemIO(Data, DataSize);
- Handle := IO.Open(@MemRec, omCreate);
- try
- WriteRawImage(Handle, Image, Offset, RowLength);
- finally
- IO.Close(Handle);
- end;
- end;
- procedure WriteRawImageRect(Data: Pointer; Left, Top, Width, Height: Integer;
- const Image: TImageData; Offset, RowLength: Integer);
- var
- SrcScanBytes, RectBytes, I: Integer;
- Info: PImageFormatInfo;
- Src, Dest: PByte;
- begin
- Assert(Data <> nil);
- Assert((Left + Width <= Image.Width) and (Top + Height <= Image.Height));
- Info := ImageFormatInfos[Image.Format];
- // Calc scanline size
- SrcScanBytes := Info.GetPixelsSize(Info.Format, Image.Width, 1);
- RectBytes := Info.GetPixelsSize(Info.Format, Width, 1);
- if RowLength = 0 then
- RowLength := RectBytes;
- Src := @PByteArray(Image.Bits)[Top * SrcScanBytes + Info.GetPixelsSize(Info.Format, Left, 1)];
- Dest := Data;
- // Move past the header
- Inc(Dest, Offset);
- // Write lines from rect of the existing image
- for I := 0 to Height - 1 do
- begin
- Move(Src^, Dest^, RectBytes);
- Inc(Dest, RowLength);
- Inc(Src, SrcScanBytes);
- end;
- end;
- { Convenience/helper Functions }
- procedure ResizeImageToFit(const SrcImage: TImageData; FitWidth, FitHeight: Integer;
- Filter: TResizeFilter; var DestImage: TImageData);
- var
- CurSize, FitSize, DestSize: TSize;
- begin
- if not TestImage(SrcImage) then
- raise EImagingError.Create(SErrorInvalidInputImage);
- FitSize.CX := FitWidth;
- FitSize.CY := FitHeight;
- CurSize.CX := SrcImage.Width;
- CurSize.CY := SrcImage.Height;
- DestSize := ImagingUtility.ScaleSizeToFit(CurSize, FitSize);
- NewImage(Max(DestSize.CX, 1), Max(DestSize.CY, 1), SrcImage.Format, DestImage);
- if SrcImage.Palette <> nil then
- CopyPalette(SrcImage.Palette, DestImage.Palette, 0, 0, ImageFormatInfos[SrcImage.Format].PaletteEntries);
- StretchRect(SrcImage, 0, 0, CurSize.CX, CurSize.CY, DestImage, 0, 0,
- DestSize.CX, DestSize.CY, Filter);
- end;
- { Color constructor functions }
- function Color24(R, G, B: Byte): TColor24Rec;
- begin
- Result.R := R;
- Result.G := G;
- Result.B := B;
- end;
- function Color32(A, R, G, B: Byte): TColor32Rec;
- begin
- Result.A := A;
- Result.R := R;
- Result.G := G;
- Result.B := B;
- end;
- function Color48(R, G, B: Word): TColor48Rec;
- begin
- Result.R := R;
- Result.G := G;
- Result.B := B;
- end;
- function Color64(A, R, G, B: Word): TColor64Rec;
- begin
- Result.A := A;
- Result.R := R;
- Result.G := G;
- Result.B := B;
- end;
- function ColorFP(A, R, G, B: Single): TColorFPRec;
- begin
- Result.A := A;
- Result.R := R;
- Result.G := G;
- Result.B := B;
- end;
- function ColorHF(A, R, G, B: THalfFloat): TColorHFRec;
- begin
- Result.A := A;
- Result.R := R;
- Result.G := G;
- Result.B := B;
- end;
- function GetAlphaValue(Color32: TColor32): Byte;
- begin
- Result := Color32 shr 24;
- end;
- function GetRedValue(Color32: TColor32): Byte;
- begin
- Result := (Color32 shr 16) and $FF;
- end;
- function GetGreenValue(Color32: TColor32): Byte;
- begin
- Result := (Color32 shr 8) and $FF;
- end;
- function GetBlueValue(Color32: TColor32): Byte;
- begin
- Result := Color32 and $FF;
- end;
- { ------------------------------------------------------------------------
- Other Imaging Stuff
- ------------------------------------------------------------------------}
- function GetFormatName(Format: TImageFormat): string;
- begin
- if ImageFormatInfos[Format] <> nil then
- Result := ImageFormatInfos[Format].Name
- else
- Result := SUnknownFormat;
- end;
- function ImageToStr(const Image: TImageData): string;
- var
- ImgSize: Integer;
- begin
- if TestImage(Image) then
- with Image do
- begin
- ImgSize := Size;
- if ImgSize > 8192 then
- ImgSize := ImgSize div 1024;
- Result := SysUtils.Format(SImageInfo, [@Image, Width, Height,
- GetFormatName(Format), ImgSize + 0.0, Iff(ImgSize = Size, 'B', 'KiB'), Bits,
- Palette]);
- end
- else
- Result := SysUtils.Format(SImageInfoInvalid, [@Image]);
- end;
- function GetVersionStr: string;
- begin
- Result := Format('%.1d.%.2d', [ImagingVersionMajor, ImagingVersionMinor]);
- end;
- function IffFormat(Condition: Boolean; const TruePart, FalsePart: TImageFormat): TImageFormat;
- begin
- if Condition then
- Result := TruePart
- else
- Result := FalsePart;
- end;
- procedure RegisterImageFileFormat(AClass: TImageFileFormatClass);
- begin
- Assert(AClass <> nil);
- if ImageFileFormats = nil then
- ImageFileFormats := TList.Create;
- if GlobalMetadata = nil then
- GlobalMetadata := TMetadata.Create;
- if ImageFileFormats <> nil then
- ImageFileFormats.Add(AClass.Create);
- end;
- function RegisterOption(OptionId: LongInt; Variable: PLongInt): Boolean;
- begin
- Result := False;
- if Options = nil then
- InitOptions;
- Assert(Variable <> nil);
- if OptionId >= Length(Options) then
- SetLength(Options, OptionId + InitialOptions);
- if (OptionId >= 0) and (OptionId < Length(Options)) {and (Options[OptionId] = nil) - must be able to override existing } then
- begin
- Options[OptionId] := Variable;
- Result := True;
- end;
- end;
- function FindImageFileFormatByExt(const Ext: string): TImageFileFormat;
- var
- I: LongInt;
- begin
- Result := nil;
- for I := ImageFileFormats.Count - 1 downto 0 do
- if TImageFileFormat(ImageFileFormats[I]).Extensions.IndexOf(Ext) >= 0 then
- begin
- Result := TImageFileFormat(ImageFileFormats[I]);
- Exit;
- end;
- end;
- function FindImageFileFormatByName(const FileName: string): TImageFileFormat;
- var
- I: LongInt;
- begin
- Result := nil;
- if FileName = '' then
- Exit;
- for I := ImageFileFormats.Count - 1 downto 0 do
- if TImageFileFormat(ImageFileFormats[I]).TestFileName(FileName) then
- begin
- Result := TImageFileFormat(ImageFileFormats[I]);
- Exit;
- end;
- end;
- function FindImageFileFormatByClass(AClass: TImageFileFormatClass): TImageFileFormat;
- var
- I: LongInt;
- begin
- Result := nil;
- for I := 0 to ImageFileFormats.Count - 1 do
- if TImageFileFormat(ImageFileFormats[I]) is AClass then
- begin
- Result := TObject(ImageFileFormats[I]) as TImageFileFormat;
- Break;
- end;
- end;
- function GetFileFormatCount: LongInt;
- begin
- Result := ImageFileFormats.Count;
- end;
- function GetFileFormatAtIndex(Index: LongInt): TImageFileFormat;
- begin
- if (Index >= 0) and (Index < ImageFileFormats.Count) then
- Result := TImageFileFormat(ImageFileFormats[Index])
- else
- Result := nil;
- end;
- function GetImageFileFormatsFilter(OpenFileFilter: Boolean): string;
- var
- I, J, Count: LongInt;
- Descriptions: string;
- Filters, CurFilter: string;
- FileFormat: TImageFileFormat;
- begin
- Descriptions := '';
- Filters := '';
- Count := 0;
- for I := 0 to ImageFileFormats.Count - 1 do
- begin
- FileFormat := TObject(ImageFileFormats[I]) as TImageFileFormat;
- // If we are creating filter for save dialog and this format cannot save
- // files the we skip it
- if not OpenFileFilter and not FileFormat.CanSave then
- Continue;
- CurFilter := '';
- for J := 0 to FileFormat.Masks.Count - 1 do
- begin
- CurFilter := CurFilter + FileFormat.Masks[J];
- if J < FileFormat.Masks.Count - 1 then
- CurFilter := CurFilter + ';';
- end;
- FmtStr(Descriptions, '%s%s (%s)|%2:s', [Descriptions, FileFormat.Name, CurFilter]);
- if Filters <> '' then
- FmtStr(Filters, '%s;%s', [Filters, CurFilter])
- else
- Filters := CurFilter;
- if I < ImageFileFormats.Count - 1 then
- Descriptions := Descriptions + '|';
- Inc(Count);
- end;
- if (Count > 1) and OpenFileFilter then
- FmtStr(Descriptions, '%s (%s)|%1:s|%s', [SAllFilter, Filters, Descriptions]);
- Result := Descriptions;
- end;
- function GetFilterIndexExtension(Index: LongInt; OpenFileFilter: Boolean): string;
- var
- I, Count: LongInt;
- FileFormat: TImageFileFormat;
- begin
- // -1 because filter indices are in 1..n range
- Index := Index - 1;
- Result := '';
- if OpenFileFilter then
- begin
- if Index > 0 then
- Index := Index - 1;
- end;
- if (Index >= 0) and (Index < ImageFileFormats.Count) then
- begin
- Count := 0;
- for I := 0 to ImageFileFormats.Count - 1 do
- begin
- FileFormat := TObject(ImageFileFormats[I]) as TImageFileFormat;
- if not OpenFileFilter and not FileFormat.CanSave then
- Continue;
- if Index = Count then
- begin
- if FileFormat.Extensions.Count > 0 then
- Result := FileFormat.Extensions[0];
- Exit;
- end;
- Inc(Count);
- end;
- end;
- end;
- function GetFileNameFilterIndex(const FileName: string; OpenFileFilter: Boolean): LongInt;
- var
- I: LongInt;
- FileFormat: TImageFileFormat;
- begin
- Result := 0;
- for I := 0 to ImageFileFormats.Count - 1 do
- begin
- FileFormat := TObject(ImageFileFormats[I]) as TImageFileFormat;
- if not OpenFileFilter and not FileFormat.CanSave then
- Continue;
- if FileFormat.TestFileName(FileName) then
- begin
- // +1 because filter indices are in 1..n range
- Inc(Result);
- if OpenFileFilter then
- Inc(Result);
- Exit;
- end;
- Inc(Result);
- end;
- Result := -1;
- end;
- function GetIO: TIOFunctions;
- begin
- Result := IO;
- end;
- procedure RaiseImaging(const Msg: string; const Args: array of const);
- var
- WholeMsg: string;
- begin
- WholeMsg := Msg;
- if GetExceptObject <> nil then
- begin
- WholeMsg := WholeMsg + ' ' + SExceptMsg + ': ' +
- GetExceptObject.Message;
- end;
- raise EImagingError.CreateFmt(WholeMsg, Args);
- end;
- procedure RaiseImaging(const Msg: string);
- begin
- RaiseImaging(Msg, []);
- end;
- { Internal unit functions }
- function CheckOptionValue(OptionId, Value: LongInt): LongInt;
- begin
- case OptionId of
- ImagingColorReductionMask:
- Result := ClampInt(Value, 0, $FF);
- ImagingLoadOverrideFormat, ImagingSaveOverrideFormat:
- Result := Iff(ImagingFormats.IsImageFormatValid(TImageFormat(Value)),
- Value, LongInt(ifUnknown));
- ImagingMipMapFilter: Result := ClampInt(Value, Ord(Low(TSamplingFilter)),
- Ord(High(TSamplingFilter)));
- else
- Result := Value;
- end;
- end;
- procedure SetFileIO;
- begin
- IO := FileIO;
- end;
- procedure SetStreamIO;
- begin
- IO := StreamIO;
- end;
- procedure SetMemoryIO;
- begin
- IO := MemoryIO;
- end;
- procedure InitImageFormats;
- begin
- ImagingFormats.InitImageFormats(ImageFormatInfos);
- end;
- procedure FreeImageFileFormats;
- var
- I: LongInt;
- begin
- if ImageFileFormats <> nil then
- for I := 0 to ImageFileFormats.Count - 1 do
- TImageFileFormat(ImageFileFormats[I]).Free;
- FreeAndNil(ImageFileFormats);
- end;
- procedure InitOptions;
- begin
- SetLength(Options, InitialOptions);
- OptionStack := TOptionStack.Create;
- end;
- procedure FreeOptions;
- begin
- SetLength(Options, 0);
- FreeAndNil(OptionStack);
- end;
- {
- TImageFileFormat class implementation
- }
- constructor TImageFileFormat.Create(AMetadata: TMetadata);
- begin
- inherited Create;
- FName := SUnknownFormat;
- FExtensions := TStringList.Create;
- FMasks := TStringList.Create;
- if AMetadata = nil then
- FMetadata := GlobalMetadata
- else
- FMetadata := AMetadata;
- Define;
- end;
- destructor TImageFileFormat.Destroy;
- begin
- FExtensions.Free;
- FMasks.Free;
- inherited Destroy;
- end;
- procedure TImageFileFormat.Define;
- begin
- end;
- function TImageFileFormat.PrepareLoad(Handle: TImagingHandle;
- var Images: TDynImageDataArray; OnlyFirstFrame: Boolean): Boolean;
- begin
- FMetadata.ClearMetaItems; // Clear old metadata
- FreeImagesInArray(Images);
- SetLength(Images, 0);
- Result := Handle <> nil;
- end;
- function TImageFileFormat.PostLoadCheck(var Images: TDynImageDataArray;
- LoadResult: Boolean): Boolean;
- var
- I: LongInt;
- begin
- if not LoadResult then
- begin
- FreeImagesInArray(Images);
- SetLength(Images, 0);
- Result := False;
- end
- else
- begin
- Result := (Length(Images) > 0) and TestImagesInArray(Images);
- if Result then
- begin
- // Convert to overriden format if it is set
- if LoadOverrideFormat <> ifUnknown then
- for I := Low(Images) to High(Images) do
- ConvertImage(Images[I], LoadOverrideFormat);
- end;
- end;
- end;
- function TImageFileFormat.PrepareSave(Handle: TImagingHandle;
- const Images: TDynImageDataArray; var Index: LongInt): Boolean;
- var
- Len, I: LongInt;
- begin
- CheckOptionsValidity;
- Result := False;
- if CanSave then
- begin
- Len := Length(Images);
- Assert(Len > 0);
- // If there are no images to be saved exit
- if Len = 0 then Exit;
- // Check index of image to be saved (-1 as index means save all images)
- if IsMultiImageFormat then
- begin
- if (Index >= Len) then
- Index := 0;
- if Index < 0 then
- begin
- Index := 0;
- FFirstIdx := 0;
- FLastIdx := Len - 1;
- end
- else
- begin
- FFirstIdx := Index;
- FLastIdx := Index;
- end;
- for I := FFirstIdx to FLastIdx - 1 do
- begin
- if not TestImage(Images[I]) then
- Exit;
- end;
- end
- else
- begin
- if (Index >= Len) or (Index < 0) then
- Index := 0;
- if not TestImage(Images[Index]) then
- Exit;
- end;
- Result := True;
- end;
- end;
- procedure TImageFileFormat.AddMasks(const AMasks: string);
- var
- I: LongInt;
- Ext: string;
- begin
- FExtensions.Clear;
- FMasks.CommaText := AMasks;
- FMasks.Delimiter := ';';
- for I := 0 to FMasks.Count - 1 do
- begin
- FMasks[I] := Trim(FMasks[I]);
- Ext := GetFileExt(FMasks[I]);
- if (Ext <> '') and (Ext <> '*') then
- FExtensions.Add(Ext);
- end;
- end;
- function TImageFileFormat.GetFormatInfo(Format: TImageFormat): TImageFormatInfo;
- begin
- Result := ImageFormatInfos[Format]^;
- end;
- function TImageFileFormat.GetSupportedFormats: TImageFormats;
- begin
- Result := FSupportedFormats;
- end;
- function TImageFileFormat.LoadData(Handle: TImagingHandle;
- var Images: TDynImageDataArray; OnlyFirstFrame: Boolean): Boolean;
- begin
- Result := False;
- RaiseImaging(SFileFormatCanNotLoad, [FName]);
- end;
- function TImageFileFormat.SaveData(Handle: TImagingHandle;
- const Images: TDynImageDataArray; Index: LongInt): Boolean;
- begin
- Result := False;
- RaiseImaging(SFileFormatCanNotSave, [FName]);
- end;
- procedure TImageFileFormat.ConvertToSupported(var Image: TImageData;
- const Info: TImageFormatInfo);
- begin
- end;
- function TImageFileFormat.IsSupported(const Image: TImageData): Boolean;
- begin
- Result := Image.Format in GetSupportedFormats;
- end;
- function TImageFileFormat.LoadFromFile(const FileName: string;
- var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
- var
- Handle: TImagingHandle;
- begin
- Result := False;
- if CanLoad then
- try
- // Set IO ops to file ops and open given file
- SetFileIO;
- Handle := IO.Open(PChar(FileName), omReadOnly);
- try
- // Test if file contains valid image and if so then load it
- if TestFormat(Handle) then
- begin
- Result := PrepareLoad(Handle, Images, OnlyFirstLevel) and
- LoadData(Handle, Images, OnlyFirstlevel);
- Result := PostLoadCheck(Images, Result);
- end
- else
- RaiseImaging(SFileNotValid, [FileName, Name]);
- finally
- IO.Close(Handle);
- end;
- except
- RaiseImaging(SErrorLoadingFile, [FileName, FExtensions[0]]);
- end;
- end;
- function TImageFileFormat.LoadFromStream(Stream: TStream;
- var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
- var
- Handle: TImagingHandle;
- OldPosition: Int64;
- begin
- Result := False;
- OldPosition := Stream.Position;
- if CanLoad then
- try
- // Set IO ops to stream ops and "open" given memory
- SetStreamIO;
- Handle := IO.Open(Pointer(Stream), omReadOnly);
- try
- // Test if stream contains valid image and if so then load it
- if TestFormat(Handle) then
- begin
- Result := PrepareLoad(Handle, Images, OnlyFirstLevel) and
- LoadData(Handle, Images, OnlyFirstlevel);
- Result := PostLoadCheck(Images, Result);
- end
- else
- RaiseImaging(SStreamNotValid, [@Stream, Name]);
- finally
- IO.Close(Handle);
- end;
- except
- Stream.Position := OldPosition;
- FreeImagesInArray(Images);
- RaiseImaging(SErrorLoadingStream, [@Stream, FExtensions[0]]);
- end;
- end;
- function TImageFileFormat.LoadFromMemory(Data: Pointer; Size: LongInt; var
- Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
- var
- Handle: TImagingHandle;
- IORec: TMemoryIORec;
- begin
- Result := False;
- if CanLoad then
- try
- // Set IO ops to memory ops and "open" given memory
- SetMemoryIO;
- IORec := PrepareMemIO(Data, Size);
- Handle := IO.Open(@IORec,omReadOnly);
- try
- // Test if memory contains valid image and if so then load it
- if TestFormat(Handle) then
- begin
- Result := PrepareLoad(Handle, Images, OnlyFirstLevel) and
- LoadData(Handle, Images, OnlyFirstlevel);
- Result := PostLoadCheck(Images, Result);
- end
- else
- RaiseImaging(SMemoryNotValid, [Data, Size, Name]);
- finally
- IO.Close(Handle);
- end;
- except
- RaiseImaging(SErrorLoadingMemory, [Data, Size, FExtensions[0]]);
- end;
- end;
- function TImageFileFormat.SaveToFile(const FileName: string;
- const Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
- var
- Handle: TImagingHandle;
- Len, Index, I: LongInt;
- Ext, FName: string;
- begin
- Result := False;
- if CanSave and TestImagesInArray(Images) then
- try
- SetFileIO;
- Len := Length(Images);
- if IsMultiImageFormat or
- (not IsMultiImageFormat and (OnlyFirstLevel or (Len = 1))) then
- begin
- Handle := IO.Open(PChar(FileName), GetSaveOpenMode);
- try
- if OnlyFirstLevel then
- Index := 0
- else
- Index := -1;
- // Write multi image to one file
- Result := PrepareSave(Handle, Images, Index) and SaveData(Handle, Images, Index);
- finally
- IO.Close(Handle);
- end;
- end
- else
- begin
- // Write multi image to file sequence
- Ext := ExtractFileExt(FileName);
- FName := ChangeFileExt(FileName, '');
- Result := True;
- for I := 0 to Len - 1 do
- begin
- Handle := IO.Open(PChar(Format(FName + '%.3d' + Ext, [I])), GetSaveOpenMode);
- try
- Index := I;
- Result := Result and PrepareSave(Handle, Images, Index) and
- SaveData(Handle, Images, Index);
- if not Result then
- Break;
- finally
- IO.Close(Handle);
- end;
- end;
- end;
- except
- raise UpdateExceptMessage(GetExceptObject, SErrorSavingFile, [FileName, FExtensions[0]]);
- end;
- end;
- function TImageFileFormat.SaveToStream(Stream: TStream;
- const Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
- var
- Handle: TImagingHandle;
- Len, Index, I: LongInt;
- OldPosition: Int64;
- begin
- Result := False;
- OldPosition := Stream.Position;
- if CanSave and TestImagesInArray(Images) then
- try
- SetStreamIO;
- Handle := IO.Open(PChar(Stream), GetSaveOpenMode);
- try
- if IsMultiImageFormat or OnlyFirstLevel then
- begin
- if OnlyFirstLevel then
- Index := 0
- else
- Index := -1;
- // Write multi image in one run
- Result := PrepareSave(Handle, Images, Index) and SaveData(Handle, Images, Index);
- end
- else
- begin
- // Write multi image to sequence
- Result := True;
- Len := Length(Images);
- for I := 0 to Len - 1 do
- begin
- Index := I;
- Result := Result and PrepareSave(Handle, Images, Index) and
- SaveData(Handle, Images, Index);
- if not Result then
- Break;
- end;
- end;
- finally
- IO.Close(Handle);
- end;
- except
- Stream.Position := OldPosition;
- raise UpdateExceptMessage(GetExceptObject, SErrorSavingStream, [@Stream, FExtensions[0]]);
- end;
- end;
- function TImageFileFormat.SaveToMemory(Data: Pointer; var Size: LongInt;
- const Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
- var
- Handle: TImagingHandle;
- Len, Index, I: LongInt;
- IORec: TMemoryIORec;
- begin
- Result := False;
- if CanSave and TestImagesInArray(Images) then
- try
- SetMemoryIO;
- IORec := PrepareMemIO(Data, Size);
- Handle := IO.Open(PChar(@IORec), GetSaveOpenMode);
- try
- if IsMultiImageFormat or OnlyFirstLevel then
- begin
- if OnlyFirstLevel then
- Index := 0
- else
- Index := -1;
- // Write multi image in one run
- Result := PrepareSave(Handle, Images, Index) and SaveData(Handle, Images, Index);
- end
- else
- begin
- // Write multi image to sequence
- Result := True;
- Len := Length(Images);
- for I := 0 to Len - 1 do
- begin
- Index := I;
- Result := Result and PrepareSave(Handle, Images, Index) and
- SaveData(Handle, Images, Index);
- if not Result then
- Break;
- end;
- end;
- Size := IORec.Position;
- finally
- IO.Close(Handle);
- end;
- except
- raise UpdateExceptMessage(GetExceptObject, SErrorSavingMemory, [Data, Size, FExtensions[0]]);
- end;
- end;
- function TImageFileFormat.MakeCompatible(const Image: TImageData;
- var Compatible: TImageData; out MustBeFreed: Boolean): Boolean;
- begin
- InitImage(Compatible);
- if SaveOverrideFormat <> ifUnknown then
- begin
- // Save format override is active. Clone input and convert it to override format.
- CloneImage(Image, Compatible);
- ConvertImage(Compatible, SaveOverrideFormat);
- // Now check if override format is supported by file format. If it is not
- // then file format specific conversion (virtual method) is called.
- Result := IsSupported(Compatible);
- if not Result then
- begin
- ConvertToSupported(Compatible, GetFormatInfo(Compatible.Format));
- Result := IsSupported(Compatible);
- end;
- end // Add IsCompatible function! not only checking by Format
- else if IsSupported(Image) then
- begin
- // No save format override and input is in format supported by this
- // file format. Just copy Image's fields to Compatible
- Compatible := Image;
- Result := True;
- end
- else
- begin
- // No override and input's format is not compatible with file format.
- // Clone it and the call file format specific conversion (virtual method).
- CloneImage(Image, Compatible);
- ConvertToSupported(Compatible, GetFormatInfo(Compatible.Format));
- Result := IsSupported(Compatible);
- end;
- // Tell the user that he must free Compatible after he's done with it
- // (if necessary).
- MustBeFreed := Image.Bits <> Compatible.Bits;
- end;
- function TImageFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
- begin
- Result := False;
- end;
- function TImageFileFormat.TestFileName(const FileName: string): Boolean;
- var
- I: LongInt;
- OnlyName: string;
- begin
- OnlyName := ExtractFileName(FileName);
- // For each mask test if filename matches it
- for I := 0 to FMasks.Count - 1 do
- if StrMaskMatch(OnlyName, FMasks[I], False) then
- begin
- Result := True;
- Exit;
- end;
- Result := False;
- end;
- procedure TImageFileFormat.CheckOptionsValidity;
- begin
- end;
- function TImageFileFormat.GetCanLoad: Boolean;
- begin
- Result := ffLoad in FFeatures;
- end;
- function TImageFileFormat.GetCanSave: Boolean;
- begin
- Result := ffSave in FFeatures;
- end;
- function TImageFileFormat.GetIsMultiImageFormat: Boolean;
- begin
- Result := ffMultiImage in FFeatures;
- end;
- function TImageFileFormat.GetSaveOpenMode: TOpenMode;
- begin
- // TODO: fix
- //if ffReadOnSave in FFeatures then
- // Result := omReadWrite
- //else
- Result := omCreate;
- end;
- { TOptionStack class implementation }
- constructor TOptionStack.Create;
- begin
- inherited Create;
- FPosition := -1;
- end;
- destructor TOptionStack.Destroy;
- var
- I: LongInt;
- begin
- for I := 0 to OptionStackDepth - 1 do
- SetLength(FStack[I], 0);
- inherited Destroy;
- end;
- function TOptionStack.Pop: Boolean;
- var
- I: LongInt;
- begin
- Result := False;
- if FPosition >= 0 then
- begin
- SetLength(Options, Length(FStack[FPosition]));
- for I := 0 to Length(FStack[FPosition]) - 1 do
- if Options[I] <> nil then
- Options[I]^ := FStack[FPosition, I];
- Dec(FPosition);
- Result := True;
- end;
- end;
- function TOptionStack.Push: Boolean;
- var
- I: LongInt;
- begin
- Result := False;
- if FPosition < OptionStackDepth - 1 then
- begin
- Inc(FPosition);
- SetLength(FStack[FPosition], Length(Options));
- for I := 0 to Length(Options) - 1 do
- if Options[I] <> nil then
- FStack[FPosition, I] := Options[I]^;
- Result := True;
- end;
- end;
- { TMetadata }
- procedure TMetadata.SetMetaItem(const Id: string; const Value: Variant;
- ImageIndex: Integer);
- begin
- AddMetaToList(FLoadMetaItems, Id, Value, ImageIndex);
- end;
- procedure TMetadata.SetMetaItemForSaving(const Id: string; const Value: Variant;
- ImageIndex: Integer);
- begin
- AddMetaToList(FSaveMetaItems, Id, Value, ImageIndex);
- end;
- procedure TMetadata.AddMetaToList(List: TStringList; const Id: string;
- const Value: Variant; ImageIndex: Integer);
- var
- Item: TMetadataItem;
- Idx: Integer;
- FullId: string;
- begin
- FullId := GetMetaItemName(Id, ImageIndex);
- if List.Find(FullId, Idx) then
- (List.Objects[Idx] as TMetadataItem).Value := Value
- else
- begin
- Item := TMetadataItem.Create;
- Item.Id := Id;
- Item.ImageIndex := ImageIndex;
- Item.Value := Value;
- List.AddObject(FullId, Item);
- end;
- end;
- procedure TMetadata.ClearMetaItems;
- begin
- ClearMetaList(FLoadMetaItems);
- end;
- procedure TMetadata.ClearMetaItemsForSaving;
- begin
- ClearMetaList(FSaveMetaItems);
- end;
- procedure TMetadata.ClearMetaList(List: TStringList);
- var
- I: Integer;
- begin
- for I := 0 to List.Count - 1 do
- List.Objects[I].Free;
- List.Clear;
- end;
- procedure TMetadata.CopyLoadedMetaItemsForSaving;
- var
- I: Integer;
- Copy, Orig: TMetadataItem;
- begin
- ClearMetaItemsForSaving;
- for I := 0 to FLoadMetaItems.Count - 1 do
- begin
- Orig := TMetadataItem(FLoadMetaItems.Objects[I]);
- Copy := TMetadataItem.Create;
- Copy.Id := Orig.Id;
- Copy.ImageIndex := Orig.ImageIndex;
- Copy.Value := Orig.Value;
- FSaveMetaItems.AddObject(GetMetaItemName(Copy.Id, Copy.ImageIndex), Copy);
- end;
- end;
- constructor TMetadata.Create;
- begin
- inherited;
- FLoadMetaItems := TStringList.Create;
- FLoadMetaItems.Sorted := True;
- FSaveMetaItems := TStringList.Create;
- FSaveMetaItems.Sorted := True;
- end;
- destructor TMetadata.Destroy;
- begin
- ClearMetaItems;
- ClearMetaItemsForSaving;
- FLoadMetaItems.Free;
- FSaveMetaItems.Free;
- inherited;
- end;
- function TMetadata.GetMetaById(const Id: string): Variant;
- var
- Idx: Integer;
- begin
- if FLoadMetaItems.Find(Id, Idx) then
- Result := (FLoadMetaItems.Objects[Idx] as TMetadataItem).Value
- else
- Result := Variants.Null;
- end;
- function TMetadata.GetMetaByIdMulti(const Id: string; ImageIndex: Integer): Variant;
- begin
- Result := GetMetaById(GetMetaItemName(Id, ImageIndex));
- end;
- function TMetadata.GetSaveMetaById(const Id: string): Variant;
- var
- Idx: Integer;
- begin
- if FSaveMetaItems.Find(Id, Idx) then
- Result := (FSaveMetaItems.Objects[Idx] as TMetadataItem).Value
- else
- Result := Variants.Null;
- end;
- function TMetadata.GetSaveMetaByIdMulti(const Id: string;
- ImageIndex: Integer): Variant;
- begin
- Result := GetSaveMetaById(GetMetaItemName(Id, ImageIndex));
- end;
- function TMetadata.GetMetaByIdx(Index: Integer): TMetadataItem;
- begin
- Result := FLoadMetaItems.Objects[Index] as TMetadataItem;
- end;
- function TMetadata.GetMetaCount: Integer;
- begin
- Result := FLoadMetaItems.Count;
- end;
- function TMetadata.GetMetaItemName(const Id: string;
- ImageIndex: Integer): string;
- begin
- Result := Iff(ImageIndex = 0, Id, Format(SMetaIdForSubImage, [Id, ImageIndex]));
- end;
- function TMetadata.GetPhysicalPixelSize(ResUnit: TResolutionUnit; out XSize,
- YSize: Single; MetaForSave: Boolean; ImageIndex: Integer): Boolean;
- type
- TGetter = function(const Id: string; ImageIndex: Integer): Variant of object;
- var
- Getter: TGetter;
- XMeta, YMeta: Variant;
- begin
- if MetaForSave then
- Getter := GetSaveMetaByIdMulti
- else
- Getter := GetMetaByIdMulti;
- XMeta := Getter(SMetaPhysicalPixelSizeX, ImageIndex);
- YMeta := Getter(SMetaPhysicalPixelSizeY, ImageIndex);
- XSize := -1;
- YSize := -1;
- Result := not VarIsNull(XMeta) or not VarIsNull(YMeta);
- if not Result then
- Exit;
- if not VarIsNull(XMeta) then
- XSize := XMeta;
- if not VarIsNull(YMeta) then
- YSize := YMeta;
- if XSize < 0 then
- XSize := YSize;
- if YSize < 0 then
- YSize := XSize;
- TranslateUnits(ResUnit, XSize, YSize);
- end;
- procedure TMetadata.SetPhysicalPixelSize(ResUnit: TResolutionUnit; XSize,
- YSize: Single; MetaForSave: Boolean; ImageIndex: Integer);
- type
- TAdder = procedure(const Id: string; const Value: Variant; ImageIndex: Integer) of object;
- var
- Adder: TAdder;
- begin
- TranslateUnits(ResUnit, XSize, YSize);
- if MetaForSave then
- Adder := SetMetaItemForSaving
- else
- Adder := SetMetaItem;
- Adder(SMetaPhysicalPixelSizeX, XSize, ImageIndex);
- Adder(SMetaPhysicalPixelSizeY, YSize, ImageIndex);
- end;
- procedure TMetadata.TranslateUnits(ResolutionUnit: TResolutionUnit; var XRes,
- YRes: Single);
- var
- UnitSize: Single;
- begin
- case ResolutionUnit of
- ruDpi: UnitSize := 25400;
- ruDpm: UnitSize := 1e06;
- ruDpcm: UnitSize := 1e04;
- else
- UnitSize := 1;
- end;
- if ResolutionUnit <> ruSizeInMicroMeters then
- begin
- XRes := UnitSize / XRes;
- YRes := UnitSize / YRes;
- end;
- end;
- function TMetadata.HasMetaItem(const Id: string; ImageIndex: Integer): Boolean;
- begin
- Result := GetMetaByIdMulti(Id, ImageIndex) <> Variants.Null;
- end;
- function TMetadata.HasMetaItemForSaving(const Id: string; ImageIndex: Integer): Boolean;
- begin
- Result := GetSaveMetaByIdMulti(Id, ImageIndex) <> Variants.Null;
- end;
- initialization
- {$IFDEF MEMCHECK}
- {$IF CompilerVersion >= 18}
- System.ReportMemoryLeaksOnShutdown := True;
- {$IFEND}
- {$ENDIF}
- if GlobalMetadata = nil then
- GlobalMetadata := TMetadata.Create;
- if ImageFileFormats = nil then
- ImageFileFormats := TList.Create;
- InitImageFormats;
- RegisterOption(ImagingColorReductionMask, @ColorReductionMask);
- RegisterOption(ImagingLoadOverrideFormat, @LoadOverrideFormat);
- RegisterOption(ImagingSaveOverrideFormat, @SaveOverrideFormat);
- RegisterOption(ImagingMipMapFilter, @MipMapFilter);
- RegisterOption(ImagingBinaryTreshold, @BinaryTreshold);
- finalization
- FreeOptions;
- FreeImageFileFormats;
- GlobalMetadata.Free;
- {
- File Notes (obsolete):
- -- 0.80 ------------------------------------------------------
- - Added new color records constructor functions (Color24(..), Color32(..)).
- - Added convenience channel getters for TColor32 (GetGreenValue, ...).
- -- 0.77.1 ---------------------------------------------------
- - Updated IO Open functions according to changes in ImagingTypes.
- - Fixed bug in SplitImage that could cause wrong size of edge chunks.
- - Metadata support fixes and extensions (frame delays, animation loops).
- -- 0.26.5 Changes/Bug Fixes ---------------------------------
- - Started reworking exception raising to keep the original class type
- (e.g. in NewImage EOutOfMemory could be raised but was hidden
- by EImagingError raised afterwards in NewImage try/except).
- - Fixed possible AV in Rotate45 subproc of RotateImage.
- - Added ReadRawXXX and WriteRawXXX functions for raw image bits IO.
- - Implemented ImagingBinaryTreshold option.
- - Added support for simple image metadata loading/saving.
- - Moved file format definition (name, exts, caps, ...) from
- constructor to new Define method.
- - Fixed some memory leaks caused by failures during image loading.
- -- 0.26.3 Changes/Bug Fixes ---------------------------------
- - Extended RotateImage to allow arbitrary angle rotations.
- - Reversed the order file formats list is searched so
- if you register a new one it will be found sooner than
- built in formats.
- - Fixed memory leak in ResizeImage ocurring when resizing
- indexed images.
- -- 0.26.1 Changes/Bug Fixes ---------------------------------
- - Added position/size checks to LoadFromStream functions.
- - Changed conditional compilation in impl. uses section to reflect changes
- in LINK symbols.
- -- 0.24.3 Changes/Bug Fixes ---------------------------------
- - GenerateMipMaps now generates all smaller levels from
- original big image (better results when using more advanced filters).
- Also conversion to compatible image format is now done here not
- in FillMipMapLevel (that is called for every mipmap level).
- -- 0.23 Changes/Bug Fixes -----------------------------------
- - MakePaletteForImages now works correctly for indexed and special format images
- - Fixed bug in StretchRect: Image was not properly stretched if
- src and dst dimensions differed only in height.
- - ConvertImage now fills new image with zeroes to avoid random data in
- some conversions (RGB->XRGB)
- - Changed RegisterOption procedure to function
- - Changed bunch of palette functions from low level interface to procedure
- (there was no reason for them to be functions).
- - Changed FreeImage and FreeImagesInArray functions to procedures.
- - Added many assertions, come try-finally, other checks, and small code
- and doc changes.
- -- 0.21 Changes/Bug Fixes -----------------------------------
- - GenerateMipMaps threw failed assertion when input was indexed or special,
- fixed.
- - Added CheckOptionsValidity to TImageFileFormat and its decendants.
- - Unit ImagingExtras which registers file formats in Extras package
- is now automatically added to uses clause if LINK_EXTRAS symbol is
- defined in ImagingOptions.inc file.
- - Added EnumFileFormats function to low level interface.
- - Fixed bug in SwapChannels which could cause AV when swapping alpha
- channel of A8R8G8B8 images.
- - Converting loaded images to ImagingOverrideFormat is now done
- in PostLoadCheck method to avoid code duplicity.
- - Added GetFileFormatCount and GetFileFormatAtIndex functions
- - Bug in ConvertImage: if some format was converted to similar format
- only with swapped channels (R16G16B16<>B16G16R16) then channels were
- swapped correctly but new data format (swapped one) was not set.
- - Made TImageFileFormat.MakeCompatible public non-virtual method
- (and modified its function). Created new virtual
- ConvertToSupported which should be overriden by descendants.
- Main reason for doint this is to avoid duplicate code that was in all
- TImageFileFormat's descendants.
- - Changed TImageFileFormat.GetFormatInfo's result type to TImageFormatInfo.
- - Split overloaded FindImageFileFormat functions to
- FindImageFileFormatByClass and FindImageFileFormatByExt and created new
- FindImageFileFormatByName which operates on whole filenames.
- - Function GetExtensionFilterIndex renamed to GetFileNameFilterIndex
- (because it now works with filenames not extensions).
- - DetermineFileFormat now first searches by filename and if not found
- then by data.
- - Added TestFileName method to TImageFileFormat.
- - Updated GetImageFileFormatsFilter to uses Masks instead of Extensions
- property of TImageFileFormat. Also you can now request
- OpenDialog and SaveDialog type filters
- - Added Masks property and AddMasks method to TImageFileFormat.
- AddMasks replaces AddExtensions, it uses filename masks instead
- of sime filename extensions to identify supported files.
- - Changed TImageFileFormat.LoadData procedure to function and
- moved varios duplicate code from its descandats (check index,...)
- here to TImageFileFormat helper methods.
- - Changed TImageFileFormat.SaveData procedure to function and
- moved varios duplicate code from its descandats (check index,...)
- here to TImageFileFormat helper methods.
- - Removed RAISE_EXCEPTIONS define, exceptions are now raised everytime
- - Added MustBeFreed parameter to TImageFileFormat.MakeComptible method
- that indicates that compatible image returned by this method must be
- freed after its usage.
- -- 0.19 Changes/Bug Fixes -----------------------------------
- - fixed bug in NewImage: if given format was ifDefault it wasn't
- replaced with DefaultImageFormat constant which caused problems later
- in other units
- - fixed bug in RotateImage which caused that rotated special format
- images were whole black
- - LoadImageFromXXX and LoadMultiImageFromXXX now use DetermineXXXFormat
- when choosing proper loader, this eliminated need for Ext parameter
- in stream and memory loading functions
- - added GetVersionStr function
- - fixed bug in ResizeImage which caued indexed images to lose their
- palette during process resulting in whole black image
- - Clipping in ...Rect functions now uses clipping procs from ImagingUtility,
- it also works better
- - FillRect optimization for 8, 16, and 32 bit formats
- - added pixel set/get functions to low level interface:
- GetPixelDirect, SetPixelDirect, GetPixel32, SetPixel32,
- GetPixelFP, SetPixelFP
- - removed GetPixelBytes low level intf function - redundant
- (same data can be obtained by GetImageFormatInfo)
- - made small changes in many parts of library to compile
- on AMD64 CPU (Linux with FPC)
- - changed InitImage to procedure (function was pointless)
- - Method TestFormat of TImageFileFormat class made public
- (was protected)
- - added function IsFileFormatSupported to low level interface
- (contributed by Paul Michell)
- - fixed some missing format arguments from error strings
- which caused Format function to raise exception
- - removed forgotten debug code that disabled filtered resizing of images with
- channel bitcounts > 8
- -- 0.17 Changes/Bug Fixes -----------------------------------
- - changed order of parameters of CopyRect function
- - GenerateMipMaps now filters mipmap levels
- - ResizeImage functions was extended to allow bilinear and bicubic filtering
- - added StretchRect function to low level interface
- - added functions GetImageFileFormatsFilter, GetFilterIndexExtension,
- and GetExtensionFilterIndex
- -- 0.15 Changes/Bug Fixes -----------------------------------
- - added function RotateImage to low level interface
- - moved TImageFormatInfo record and types required by it to
- ImagingTypes unit, changed GetImageFormatInfo low level
- interface function to return TImageFormatInfo instead of short info
- - added checking of options values validity before they are used
- - fixed possible memory leak in CloneImage
- - added ReplaceColor function to low level interface
- - new function FindImageFileFormat by class added
- -- 0.13 Changes/Bug Fixes -----------------------------------
- - added DetermineFileFormat, DetermineStreamFormat, DetermineMemoryFormat,
- GetPixelsSize functions to low level interface
- - added NewPalette, CopyPalette, FreePalette functions
- to low level interface
- - added MapImageToPalette, FillRect, SplitImage, MakePaletteForImages
- functions to low level interface
- - fixed buggy FillCustomPalette function (possible div by zero and others)
- - added CopyRect function to low level interface
- - Member functions of TImageFormatInfo record implemented for all formats
- - before saving images TestImagesInArray is called now
- - added TestImagesInArray function to low level interface
- - added GenerateMipMaps function to low level interface
- - stream position in load/save from/to stream is now set to position before
- function was called if error occurs
- - when error occured during load/save from/to file file handle
- was not released
- - CloneImage returned always False
- }
- end.
|