ImagingNetworkGraphics.pas 86 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700
  1. {
  2. Vampyre Imaging Library
  3. by Marek Mauder
  4. https://github.com/galfar/imaginglib
  5. https://imaginglib.sourceforge.io
  6. - - - - -
  7. This Source Code Form is subject to the terms of the Mozilla Public
  8. License, v. 2.0. If a copy of the MPL was not distributed with this
  9. file, You can obtain one at https://mozilla.org/MPL/2.0.
  10. }
  11. { This unit contains image format loaders/savers for Network Graphics image
  12. file formats PNG, MNG, and JNG.}
  13. unit ImagingNetworkGraphics;
  14. interface
  15. {$I ImagingOptions.inc}
  16. { If MNG support is enabled we must make sure PNG and JNG are enabled too.}
  17. {$IFNDEF DONT_LINK_MNG}
  18. {$UNDEF DONT_LINK_PNG}
  19. {$UNDEF DONT_LINK_JNG}
  20. {$ENDIF}
  21. uses
  22. Types, SysUtils, Classes, ImagingTypes, Imaging, ImagingUtility, ImagingFormats, dzlib;
  23. type
  24. { Basic class for Network Graphics file formats loaders/savers.}
  25. TNetworkGraphicsFileFormat = class(TImageFileFormat)
  26. protected
  27. FSignature: TChar8;
  28. FPreFilter: LongInt;
  29. FCompressLevel: LongInt;
  30. FLossyCompression: LongBool;
  31. FLossyAlpha: LongBool;
  32. FQuality: LongInt;
  33. FProgressive: LongBool;
  34. FZLibStrategy: Integer;
  35. function GetSupportedFormats: TImageFormats; override;
  36. procedure ConvertToSupported(var Image: TImageData;
  37. const Info: TImageFormatInfo); override;
  38. procedure Define; override;
  39. public
  40. function TestFormat(Handle: TImagingHandle): Boolean; override;
  41. procedure CheckOptionsValidity; override;
  42. published
  43. { Sets precompression filter used when saving images with lossless compression.
  44. Allowed values are: 0 (none), 1 (sub), 2 (up), 3 (average), 4 (paeth),
  45. 5 (use 0 for indexed/gray images and 4 for RGB/ARGB images),
  46. 6 (adaptive filtering - use best filter for each scanline - very slow).
  47. Note that filters 3 and 4 are much slower than filters 1 and 2.
  48. Default value is 5.}
  49. property PreFilter: LongInt read FPreFilter write FPreFilter;
  50. { Sets ZLib compression level used when saving images with lossless compression.
  51. Allowed values are in range 0 (no compression) to 9 (best compression).
  52. Default value is 5.}
  53. property CompressLevel: LongInt read FCompressLevel write FCompressLevel;
  54. { Specifies whether MNG animation frames are saved with lossy or lossless
  55. compression. Lossless frames are saved as PNG images and lossy frames are
  56. saved as JNG images. Allowed values are 0 (False) and 1 (True).
  57. Default value is 0.}
  58. property LossyCompression: LongBool read FLossyCompression write FLossyCompression;
  59. { Defines whether alpha channel of lossy MNG frames or JNG images
  60. is lossy compressed too. Allowed values are 0 (False) and 1 (True).
  61. Default value is 0.}
  62. property LossyAlpha: LongBool read FLossyAlpha write FLossyAlpha;
  63. { Specifies compression quality used when saving lossy MNG frames or JNG images.
  64. For details look at ImagingJpegQuality option.}
  65. property Quality: LongInt read FQuality write FQuality;
  66. { Specifies whether images are saved in progressive format when saving lossy
  67. MNG frames or JNG images. For details look at ImagingJpegProgressive.}
  68. property Progressive: LongBool read FProgressive write FProgressive;
  69. end;
  70. { Class for loading Portable Network Graphics Images.
  71. Loads all types of this image format (all images in png test suite)
  72. and saves all types with bitcount >= 8 (non-interlaced only).
  73. Compression level and filtering can be set by options interface.
  74. Supported ancillary chunks (loading):
  75. tRNS, bKGD
  76. (for indexed images transparency contains alpha values for palette,
  77. RGB/Gray images with transparency are converted to formats with alpha
  78. and pixels with transparent color are replaced with background color
  79. with alpha = 0).}
  80. TPNGFileFormat = class(TNetworkGraphicsFileFormat)
  81. private
  82. FLoadAnimated: LongBool;
  83. protected
  84. procedure Define; override;
  85. function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
  86. OnlyFirstLevel: Boolean): Boolean; override;
  87. function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
  88. Index: LongInt): Boolean; override;
  89. published
  90. property LoadAnimated: LongBool read FLoadAnimated write FLoadAnimated;
  91. end;
  92. {$IFNDEF DONT_LINK_MNG}
  93. { Class for loading Multiple Network Graphics files.
  94. This format has complex animation capabilities but Imaging only
  95. extracts frames. Individual frames are stored as standard PNG or JNG
  96. images. Loads all types of these frames stored in IHDR-IEND and
  97. JHDR-IEND streams (Note that there are MNG chunks
  98. like BASI which define images but does not contain image data itself,
  99. those are ignored).
  100. Imaging saves MNG files as MNG-VLC (very low complexity) so it is basically
  101. an array of image frames without MNG animation chunks. Frames can be saved
  102. as lossless PNG or lossy JNG images (look at TPNGFileFormat and
  103. TJNGFileFormat for info). Every frame can be in different data format.
  104. Many frame compression settings can be modified by options interface.}
  105. TMNGFileFormat = class(TNetworkGraphicsFileFormat)
  106. protected
  107. procedure Define; override;
  108. function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
  109. OnlyFirstLevel: Boolean): Boolean; override;
  110. function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
  111. Index: LongInt): Boolean; override;
  112. end;
  113. {$ENDIF}
  114. {$IFNDEF DONT_LINK_JNG}
  115. { Class for loading JPEG Network Graphics Images.
  116. Loads all types of this image format (all images in jng test suite)
  117. and saves all types except 12 bit JPEGs.
  118. Alpha channel in JNG images is stored separately from color/gray data and
  119. can be lossy (as JPEG image) or lossless (as PNG image) compressed.
  120. Type of alpha compression, compression level and quality,
  121. and filtering can be set by options interface.
  122. Supported ancillary chunks (loading):
  123. tRNS, bKGD
  124. (Images with transparency are converted to formats with alpha
  125. and pixels with transparent color are replaced with background color
  126. with alpha = 0).}
  127. TJNGFileFormat = class(TNetworkGraphicsFileFormat)
  128. protected
  129. procedure Define; override;
  130. function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
  131. OnlyFirstLevel: Boolean): Boolean; override;
  132. function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
  133. Index: LongInt): Boolean; override;
  134. end;
  135. {$ENDIF}
  136. implementation
  137. uses
  138. {$IFNDEF DONT_LINK_JNG}
  139. ImagingJpeg, ImagingIO,
  140. {$ENDIF}
  141. ImagingColors,
  142. ImagingCanvases;
  143. const
  144. NGDefaultPreFilter = 5;
  145. NGDefaultCompressLevel = 5;
  146. NGDefaultLossyAlpha = False;
  147. NGDefaultLossyCompression = False;
  148. NGDefaultProgressive = False;
  149. NGDefaultQuality = 90;
  150. NGLosslessFormats: TImageFormats = [ifIndex8, ifGray8, ifA8Gray8, ifGray16,
  151. ifA16Gray16, ifR8G8B8, ifA8R8G8B8, ifR16G16B16, ifA16R16G16B16, ifB16G16R16,
  152. ifA16B16G16R16, ifBinary];
  153. NGLossyFormats: TImageFormats = [ifGray8, ifA8Gray8, ifR8G8B8, ifA8R8G8B8];
  154. PNGDefaultLoadAnimated = True;
  155. NGDefaultZLibStrategy = 1; // Z_FILTERED
  156. SPNGFormatName = 'Portable Network Graphics';
  157. SPNGMasks = '*.png';
  158. SMNGFormatName = 'Multiple Network Graphics';
  159. SMNGMasks = '*.mng';
  160. SJNGFormatName = 'JPEG Network Graphics';
  161. SJNGMasks = '*.jng';
  162. resourcestring
  163. SErrorLoadingChunk = 'Error when reading %s chunk data. File may be corrupted.';
  164. type
  165. { Chunk header.}
  166. TChunkHeader = packed record
  167. DataSize: UInt32;
  168. ChunkID: TChar4;
  169. end;
  170. { IHDR chunk format - PNG header.}
  171. TIHDR = packed record
  172. Width: UInt32; // Image width
  173. Height: UInt32; // Image height
  174. BitDepth: Byte; // Bits per pixel or bits per sample (for truecolor)
  175. ColorType: Byte; // 0 = grayscale, 2 = truecolor, 3 = palette,
  176. // 4 = gray + alpha, 6 = truecolor + alpha
  177. Compression: Byte; // Compression type: 0 = ZLib
  178. Filter: Byte; // Used precompress filter
  179. Interlacing: Byte; // Used interlacing: 0 = no int, 1 = Adam7
  180. end;
  181. PIHDR = ^TIHDR;
  182. { MHDR chunk format - MNG header.}
  183. TMHDR = packed record
  184. FrameWidth: UInt32; // Frame width
  185. FrameHeight: UInt32; // Frame height
  186. TicksPerSecond: UInt32; // FPS of animation
  187. NominalLayerCount: UInt32; // Number of layers in file
  188. NominalFrameCount: UInt32; // Number of frames in file
  189. NominalPlayTime: UInt32; // Play time of animation in ticks
  190. SimplicityProfile: UInt32; // Defines which MNG features are used in this file
  191. end;
  192. PMHDR = ^TMHDR;
  193. { JHDR chunk format - JNG header.}
  194. TJHDR = packed record
  195. Width: UInt32; // Image width
  196. Height: UInt32; // Image height
  197. ColorType: Byte; // 8 = grayscale (Y), 10 = color (YCbCr),
  198. // 12 = gray + alpha (Y-alpha), 14 = color + alpha (YCbCr-alpha)
  199. SampleDepth: Byte; // 8, 12 or 20 (8 and 12 samples together) bit
  200. Compression: Byte; // Compression type: 8 = Huffman coding
  201. Interlacing: Byte; // 0 = single scan, 8 = progressive
  202. AlphaSampleDepth: Byte; // 0, 1, 2, 4, 8, 16 if alpha compression is 0 (PNG)
  203. // 8 if alpha compression is 8 (JNG)
  204. AlphaCompression: Byte; // 0 = PNG grayscale IDAT, 8 = grayscale 8-bit JPEG
  205. AlphaFilter: Byte; // 0 = PNG filter or no filter (JPEG)
  206. AlphaInterlacing: Byte; // 0 = non interlaced
  207. end;
  208. PJHDR = ^TJHDR;
  209. { acTL chunk format - APNG animation control.}
  210. TacTL = packed record
  211. NumFrames: UInt32; // Number of frames
  212. NumPlay: UInt32; // Number of times to loop the animation (0 = inf)
  213. end;
  214. PacTL =^TacTL;
  215. { fcTL chunk format - APNG frame control.}
  216. TfcTL = packed record
  217. SeqNumber: UInt32; // Sequence number of the animation chunk, starting from 0
  218. Width: UInt32; // Width of the following frame
  219. Height: UInt32; // Height of the following frame
  220. XOffset: UInt32; // X position at which to render the following frame
  221. YOffset: UInt32; // Y position at which to render the following frame
  222. DelayNumer: Word; // Frame delay fraction numerator
  223. DelayDenom: Word; // Frame delay fraction denominator
  224. DisposeOp: Byte; // Type of frame area disposal to be done after rendering this frame
  225. BlendOp: Byte; // Type of frame area rendering for this frame
  226. end;
  227. PfcTL = ^TfcTL;
  228. { pHYs chunk format - encodes the absolute or relative dimensions of pixels.}
  229. TpHYs = packed record
  230. PixelsPerUnitX: UInt32;
  231. PixelsPerUnitY: UInt32;
  232. UnitSpecifier: Byte;
  233. end;
  234. PpHYs = ^TpHYs;
  235. const
  236. { PNG file identifier.}
  237. PNGSignature: TChar8 = #$89'PNG'#$0D#$0A#$1A#$0A;
  238. { MNG file identifier.}
  239. MNGSignature: TChar8 = #$8A'MNG'#$0D#$0A#$1A#$0A;
  240. { JNG file identifier.}
  241. JNGSignature: TChar8 = #$8B'JNG'#$0D#$0A#$1A#$0A;
  242. { Constants for chunk identifiers and signature identifiers.
  243. They are in big-endian format.}
  244. IHDRChunk: TChar4 = 'IHDR';
  245. IENDChunk: TChar4 = 'IEND';
  246. MHDRChunk: TChar4 = 'MHDR';
  247. MENDChunk: TChar4 = 'MEND';
  248. JHDRChunk: TChar4 = 'JHDR';
  249. IDATChunk: TChar4 = 'IDAT';
  250. JDATChunk: TChar4 = 'JDAT';
  251. JDAAChunk: TChar4 = 'JDAA';
  252. JSEPChunk: TChar4 = 'JSEP';
  253. PLTEChunk: TChar4 = 'PLTE';
  254. BACKChunk: TChar4 = 'BACK';
  255. DEFIChunk: TChar4 = 'DEFI';
  256. TERMChunk: TChar4 = 'TERM';
  257. tRNSChunk: TChar4 = 'tRNS';
  258. bKGDChunk: TChar4 = 'bKGD';
  259. gAMAChunk: TChar4 = 'gAMA';
  260. acTLChunk: TChar4 = 'acTL';
  261. fcTLChunk: TChar4 = 'fcTL';
  262. fdATChunk: TChar4 = 'fdAT';
  263. pHYsChunk: TChar4 = 'pHYs';
  264. { APNG frame dispose operations.}
  265. DisposeOpNone = 0;
  266. DisposeOpBackground = 1;
  267. DisposeOpPrevious = 2;
  268. { APNG frame blending modes}
  269. BlendOpSource = 0;
  270. BlendOpOver = 1;
  271. { Interlace start and offsets.}
  272. RowStart: array[0..6] of LongInt = (0, 0, 4, 0, 2, 0, 1);
  273. ColumnStart: array[0..6] of LongInt = (0, 4, 0, 2, 0, 1, 0);
  274. RowIncrement: array[0..6] of LongInt = (8, 8, 8, 4, 4, 2, 2);
  275. ColumnIncrement: array[0..6] of LongInt = (8, 8, 4, 4, 2, 2, 1);
  276. type
  277. { Helper class that holds information about MNG frame in PNG or JNG format.}
  278. TFrameInfo = class
  279. public
  280. Index: Integer;
  281. FrameWidth, FrameHeight: LongInt;
  282. IsJpegFrame: Boolean;
  283. IHDR: TIHDR;
  284. JHDR: TJHDR;
  285. fcTL: TfcTL;
  286. pHYs: TpHYs;
  287. Palette: PPalette24;
  288. PaletteEntries: LongInt;
  289. Transparency: Pointer;
  290. TransparencySize: LongInt;
  291. Background: Pointer;
  292. BackgroundSize: LongInt;
  293. IDATMemory: TMemoryStream;
  294. JDATMemory: TMemoryStream;
  295. JDAAMemory: TMemoryStream;
  296. constructor Create(AIndex: Integer);
  297. destructor Destroy; override;
  298. procedure AssignSharedProps(Source: TFrameInfo);
  299. end;
  300. { Defines type of Network Graphics file.}
  301. TNGFileType = (ngPNG, ngAPNG, ngMNG, ngJNG);
  302. TNGFileHandler = class
  303. public
  304. FileFormat: TNetworkGraphicsFileFormat;
  305. FileType: TNGFileType;
  306. Frames: array of TFrameInfo;
  307. MHDR: TMHDR; // Main header for MNG files
  308. acTL: TacTL; // Global anim control for APNG files
  309. GlobalPalette: PPalette24;
  310. GlobalPaletteEntries: LongInt;
  311. GlobalTransparency: Pointer;
  312. GlobalTransparencySize: LongInt;
  313. constructor Create(AFileFormat: TNetworkGraphicsFileFormat);
  314. destructor Destroy; override;
  315. procedure Clear;
  316. function GetLastFrame: TFrameInfo;
  317. function AddFrameInfo: TFrameInfo;
  318. procedure LoadMetaData;
  319. end;
  320. { Network Graphics file parser and frame converter.}
  321. TNGFileLoader = class(TNGFileHandler)
  322. public
  323. function LoadFile(Handle: TImagingHandle): Boolean;
  324. procedure LoadImageFromPNGFrame(FrameWidth, FrameHeight: LongInt; const IHDR: TIHDR; IDATStream: TMemoryStream; var Image: TImageData);
  325. {$IFNDEF DONT_LINK_JNG}
  326. procedure LoadImageFromJNGFrame(FrameWidth, FrameHeight: LongInt; const JHDR: TJHDR; IDATStream, JDATStream, JDAAStream: TMemoryStream; var Image: TImageData);
  327. {$ENDIF}
  328. procedure ApplyFrameSettings(Frame: TFrameInfo; var Image: TImageData);
  329. end;
  330. TNGFileSaver = class(TNGFileHandler)
  331. public
  332. PreFilter: LongInt;
  333. CompressLevel: LongInt;
  334. LossyAlpha: Boolean;
  335. Quality: LongInt;
  336. Progressive: Boolean;
  337. ZLibStrategy: Integer;
  338. function SaveFile(Handle: TImagingHandle): Boolean;
  339. procedure AddFrame(const Image: TImageData; IsJpegFrame: Boolean);
  340. procedure StoreImageToPNGFrame(const IHDR: TIHDR; Bits: Pointer; FmtInfo: TImageFormatInfo; IDATStream: TMemoryStream);
  341. {$IFNDEF DONT_LINK_JNG}
  342. procedure StoreImageToJNGFrame(const JHDR: TJHDR; const Image: TImageData; IDATStream, JDATStream, JDAAStream: TMemoryStream);
  343. {$ENDIF}
  344. procedure SetFileOptions;
  345. end;
  346. {$IFNDEF DONT_LINK_JNG}
  347. TCustomIOJpegFileFormat = class(TJpegFileFormat)
  348. protected
  349. FCustomIO: TIOFunctions;
  350. procedure SetJpegIO(const JpegIO: TIOFunctions); override;
  351. procedure SetCustomIO(const CustomIO: TIOFunctions);
  352. end;
  353. {$ENDIF}
  354. TAPNGAnimator = class
  355. public
  356. class procedure Animate(var Images: TDynImageDataArray; const acTL: TacTL; const SrcFrames: array of TFrameInfo);
  357. end;
  358. { Helper routines }
  359. function PaethPredictor(A, B, C: LongInt): LongInt; {$IFDEF USE_INLINE}inline;{$ENDIF}
  360. var
  361. P, PA, PB, PC: LongInt;
  362. begin
  363. P := A + B - C;
  364. PA := Abs(P - A);
  365. PB := Abs(P - B);
  366. PC := Abs(P - C);
  367. if (PA <= PB) and (PA <= PC) then
  368. Result := A
  369. else
  370. if PB <= PC then
  371. Result := B
  372. else
  373. Result := C;
  374. end;
  375. procedure SwapRGB(Line: PByte; Width, SampleDepth, BytesPerPixel: LongInt);
  376. var
  377. I: LongInt;
  378. Tmp: Word;
  379. begin
  380. case SampleDepth of
  381. 8:
  382. for I := 0 to Width - 1 do
  383. with PColor24Rec(Line)^ do
  384. begin
  385. Tmp := R;
  386. R := B;
  387. B := Tmp;
  388. Inc(Line, BytesPerPixel);
  389. end;
  390. 16:
  391. for I := 0 to Width - 1 do
  392. with PColor48Rec(Line)^ do
  393. begin
  394. Tmp := R;
  395. R := B;
  396. B := Tmp;
  397. Inc(Line, BytesPerPixel);
  398. end;
  399. end;
  400. end;
  401. {$IFNDEF DONT_LINK_JNG}
  402. { TCustomIOJpegFileFormat class implementation }
  403. procedure TCustomIOJpegFileFormat.SetCustomIO(const CustomIO: TIOFunctions);
  404. begin
  405. FCustomIO := CustomIO;
  406. end;
  407. procedure TCustomIOJpegFileFormat.SetJpegIO(const JpegIO: TIOFunctions);
  408. begin
  409. inherited SetJpegIO(FCustomIO);
  410. end;
  411. {$ENDIF}
  412. { TFrameInfo class implementation }
  413. constructor TFrameInfo.Create(AIndex: Integer);
  414. begin
  415. Index := AIndex;
  416. IDATMemory := TMemoryStream.Create;
  417. JDATMemory := TMemoryStream.Create;
  418. JDAAMemory := TMemoryStream.Create;
  419. end;
  420. destructor TFrameInfo.Destroy;
  421. begin
  422. FreeMem(Palette);
  423. FreeMem(Transparency);
  424. FreeMem(Background);
  425. IDATMemory.Free;
  426. JDATMemory.Free;
  427. JDAAMemory.Free;
  428. inherited Destroy;
  429. end;
  430. procedure TFrameInfo.AssignSharedProps(Source: TFrameInfo);
  431. begin
  432. IHDR := Source.IHDR;
  433. JHDR := Source.JHDR;
  434. PaletteEntries := Source.PaletteEntries;
  435. GetMem(Palette, PaletteEntries * SizeOf(TColor24Rec));
  436. Move(Source.Palette^, Palette^, PaletteEntries * SizeOf(TColor24Rec));
  437. TransparencySize := Source.TransparencySize;
  438. GetMem(Transparency, TransparencySize);
  439. Move(Source.Transparency^, Transparency^, TransparencySize);
  440. end;
  441. { TNGFileHandler class implementation}
  442. destructor TNGFileHandler.Destroy;
  443. begin
  444. Clear;
  445. inherited Destroy;
  446. end;
  447. procedure TNGFileHandler.Clear;
  448. var
  449. I: LongInt;
  450. begin
  451. for I := 0 to Length(Frames) - 1 do
  452. Frames[I].Free;
  453. SetLength(Frames, 0);
  454. FreeMemNil(GlobalPalette);
  455. GlobalPaletteEntries := 0;
  456. FreeMemNil(GlobalTransparency);
  457. GlobalTransparencySize := 0;
  458. end;
  459. constructor TNGFileHandler.Create(AFileFormat: TNetworkGraphicsFileFormat);
  460. begin
  461. FileFormat := AFileFormat;
  462. end;
  463. function TNGFileHandler.GetLastFrame: TFrameInfo;
  464. var
  465. Len: LongInt;
  466. begin
  467. Len := Length(Frames);
  468. if Len > 0 then
  469. Result := Frames[Len - 1]
  470. else
  471. Result := nil;
  472. end;
  473. procedure TNGFileHandler.LoadMetaData;
  474. var
  475. I: Integer;
  476. Delay, Denom: Integer;
  477. begin
  478. if FileType = ngAPNG then
  479. begin
  480. // Num plays of APNG animation
  481. FileFormat.FMetadata.SetMetaItem(SMetaAnimationLoops, acTL.NumPlay);
  482. end;
  483. for I := 0 to High(Frames) do
  484. begin
  485. if Frames[I].pHYs.UnitSpecifier = 1 then
  486. begin
  487. // Store physical pixel dimensions, in PNG stored as pixels per meter DPM
  488. FileFormat.FMetadata.SetPhysicalPixelSize(ruDpm, Frames[I].pHYs.PixelsPerUnitX,
  489. Frames[I].pHYs.PixelsPerUnitY);
  490. end;
  491. if FileType = ngAPNG then
  492. begin
  493. // Store frame delay of APNG file frame
  494. Denom := Frames[I].fcTL.DelayDenom;
  495. if Denom = 0 then
  496. Denom := 100;
  497. Delay := Round(1000 * (Frames[I].fcTL.DelayNumer / Denom));
  498. FileFormat.FMetadata.SetMetaItem(SMetaFrameDelay, Delay, I);
  499. end;
  500. end;
  501. end;
  502. function TNGFileHandler.AddFrameInfo: TFrameInfo;
  503. var
  504. Len: LongInt;
  505. begin
  506. Len := Length(Frames);
  507. SetLength(Frames, Len + 1);
  508. Result := TFrameInfo.Create(Len);
  509. Frames[Len] := Result;
  510. end;
  511. { TNGFileLoader class implementation}
  512. function TNGFileLoader.LoadFile(Handle: TImagingHandle): Boolean;
  513. var
  514. Sig: TChar8;
  515. Chunk: TChunkHeader;
  516. ChunkData: Pointer;
  517. ChunkCrc: UInt32;
  518. procedure ReadChunk;
  519. begin
  520. GetIO.Read(Handle, @Chunk, SizeOf(Chunk));
  521. Chunk.DataSize := SwapEndianUInt32(Chunk.DataSize);
  522. end;
  523. procedure ReadChunkData;
  524. var
  525. ReadBytes: UInt32;
  526. begin
  527. FreeMemNil(ChunkData);
  528. GetMem(ChunkData, Chunk.DataSize);
  529. ReadBytes := GetIO.Read(Handle, ChunkData, Chunk.DataSize);
  530. GetIO.Read(Handle, @ChunkCrc, SizeOf(ChunkCrc));
  531. if ReadBytes <> Chunk.DataSize then
  532. RaiseImaging(SErrorLoadingChunk, [string(Chunk.ChunkID)]);
  533. end;
  534. procedure SkipChunkData;
  535. begin
  536. GetIO.Seek(Handle, Chunk.DataSize + SizeOf(ChunkCrc), smFromCurrent);
  537. end;
  538. procedure StartNewPNGImage;
  539. var
  540. Frame: TFrameInfo;
  541. begin
  542. ReadChunkData;
  543. if Chunk.ChunkID = fcTLChunk then
  544. begin
  545. if (Length(Frames) = 1) and (Frames[0].IDATMemory.Size = 0) then
  546. begin
  547. // First fcTL chunk maybe for first IDAT frame which is alredy created
  548. Frame := Frames[0];
  549. end
  550. else
  551. begin
  552. // Subsequent APNG frames with data in fdAT
  553. Frame := AddFrameInfo;
  554. // Copy some shared props from first frame (IHDR is the same for all APNG frames, palette etc)
  555. Frame.AssignSharedProps(Frames[0]);
  556. end;
  557. Frame.fcTL := PfcTL(ChunkData)^;
  558. SwapEndianUInt32(@Frame.fcTL, 5);
  559. Frame.fcTL.DelayNumer := SwapEndianWord(Frame.fcTL.DelayNumer);
  560. Frame.fcTL.DelayDenom := SwapEndianWord(Frame.fcTL.DelayDenom);
  561. Frame.FrameWidth := Frame.fcTL.Width;
  562. Frame.FrameHeight := Frame.fcTL.Height;
  563. end
  564. else
  565. begin
  566. // This is frame defined by IHDR chunk
  567. Frame := AddFrameInfo;
  568. Frame.IHDR := PIHDR(ChunkData)^;
  569. SwapEndianUInt32(@Frame.IHDR, 2);
  570. Frame.FrameWidth := Frame.IHDR.Width;
  571. Frame.FrameHeight := Frame.IHDR.Height;
  572. end;
  573. Frame.IsJpegFrame := False;
  574. end;
  575. procedure StartNewJNGImage;
  576. var
  577. Frame: TFrameInfo;
  578. begin
  579. ReadChunkData;
  580. Frame := AddFrameInfo;
  581. Frame.IsJpegFrame := True;
  582. Frame.JHDR := PJHDR(ChunkData)^;
  583. SwapEndianUInt32(@Frame.JHDR, 2);
  584. Frame.FrameWidth := Frame.JHDR.Width;
  585. Frame.FrameHeight := Frame.JHDR.Height;
  586. end;
  587. procedure AppendIDAT;
  588. begin
  589. ReadChunkData;
  590. // Append current IDAT/fdAT chunk to storage stream
  591. if Chunk.ChunkID = IDATChunk then
  592. GetLastFrame.IDATMemory.Write(ChunkData^, Chunk.DataSize)
  593. else if Chunk.ChunkID = fdATChunk then
  594. GetLastFrame.IDATMemory.Write(PByteArray(ChunkData)[4], Chunk.DataSize - SizeOf(UInt32));
  595. end;
  596. procedure AppendJDAT;
  597. begin
  598. ReadChunkData;
  599. // Append current JDAT chunk to storage stream
  600. GetLastFrame.JDATMemory.Write(ChunkData^, Chunk.DataSize);
  601. end;
  602. procedure AppendJDAA;
  603. begin
  604. ReadChunkData;
  605. // Append current JDAA chunk to storage stream
  606. GetLastFrame.JDAAMemory.Write(ChunkData^, Chunk.DataSize);
  607. end;
  608. procedure LoadPLTE;
  609. begin
  610. ReadChunkData;
  611. if GetLastFrame = nil then
  612. begin
  613. // Load global palette
  614. GetMem(GlobalPalette, Chunk.DataSize);
  615. Move(ChunkData^, GlobalPalette^, Chunk.DataSize);
  616. GlobalPaletteEntries := Chunk.DataSize div 3;
  617. end
  618. else if GetLastFrame.Palette = nil then
  619. begin
  620. if (Chunk.DataSize = 0) and (GlobalPalette <> nil) then
  621. begin
  622. // Use global palette
  623. GetMem(GetLastFrame.Palette, GlobalPaletteEntries * SizeOf(TColor24Rec));
  624. Move(GlobalPalette^, GetLastFrame.Palette^, GlobalPaletteEntries * SizeOf(TColor24Rec));
  625. GetLastFrame.PaletteEntries := GlobalPaletteEntries;
  626. end
  627. else
  628. begin
  629. // Load pal from PLTE chunk
  630. GetMem(GetLastFrame.Palette, Chunk.DataSize);
  631. Move(ChunkData^, GetLastFrame.Palette^, Chunk.DataSize);
  632. GetLastFrame.PaletteEntries := Chunk.DataSize div 3;
  633. end;
  634. end;
  635. end;
  636. procedure LoadtRNS;
  637. begin
  638. ReadChunkData;
  639. if GetLastFrame = nil then
  640. begin
  641. // Load global transparency
  642. GetMem(GlobalTransparency, Chunk.DataSize);
  643. Move(ChunkData^, GlobalTransparency^, Chunk.DataSize);
  644. GlobalTransparencySize := Chunk.DataSize;
  645. end
  646. else if GetLastFrame.Transparency = nil then
  647. begin
  648. if (Chunk.DataSize = 0) and (GlobalTransparency <> nil) then
  649. begin
  650. // Use global transparency
  651. GetMem(GetLastFrame.Transparency, GlobalTransparencySize);
  652. Move(GlobalTransparency^, GetLastFrame.Transparency^, Chunk.DataSize);
  653. GetLastFrame.TransparencySize := GlobalTransparencySize;
  654. end
  655. else
  656. begin
  657. // Load pal from tRNS chunk
  658. GetMem(GetLastFrame.Transparency, Chunk.DataSize);
  659. Move(ChunkData^, GetLastFrame.Transparency^, Chunk.DataSize);
  660. GetLastFrame.TransparencySize := Chunk.DataSize;
  661. end;
  662. end;
  663. end;
  664. procedure LoadbKGD;
  665. begin
  666. ReadChunkData;
  667. if GetLastFrame.Background = nil then
  668. begin
  669. GetMem(GetLastFrame.Background, Chunk.DataSize);
  670. Move(ChunkData^, GetLastFrame.Background^, Chunk.DataSize);
  671. GetLastFrame.BackgroundSize := Chunk.DataSize;
  672. end;
  673. end;
  674. procedure HandleacTL;
  675. begin
  676. FileType := ngAPNG;
  677. ReadChunkData;
  678. acTL := PacTL(ChunkData)^;
  679. SwapEndianUInt32(@acTL, SizeOf(acTL) div SizeOf(UInt32));
  680. end;
  681. procedure LoadpHYs;
  682. begin
  683. ReadChunkData;
  684. with GetLastFrame do
  685. begin
  686. pHYs := PpHYs(ChunkData)^;
  687. SwapEndianUInt32(@pHYs, SizeOf(pHYs) div SizeOf(UInt32));
  688. end;
  689. end;
  690. begin
  691. Result := False;
  692. Clear;
  693. ChunkData := nil;
  694. with GetIO do
  695. try
  696. Read(Handle, @Sig, SizeOf(Sig));
  697. // Set file type according to the signature
  698. if Sig = PNGSignature then FileType := ngPNG
  699. else if Sig = MNGSignature then FileType := ngMNG
  700. else if Sig = JNGSignature then FileType := ngJNG
  701. else Exit;
  702. if FileType = ngMNG then
  703. begin
  704. // Store MNG header if present
  705. ReadChunk;
  706. ReadChunkData;
  707. MHDR := PMHDR(ChunkData)^;
  708. SwapEndianUInt32(@MHDR, SizeOf(MHDR) div SizeOf(UInt32));
  709. end;
  710. // Read chunks until ending chunk or EOF is reached
  711. repeat
  712. ReadChunk;
  713. if (Chunk.ChunkID = IHDRChunk) or (Chunk.ChunkID = fcTLChunk) then StartNewPNGImage
  714. else if Chunk.ChunkID = JHDRChunk then StartNewJNGImage
  715. else if (Chunk.ChunkID = IDATChunk) or (Chunk.ChunkID = fdATChunk) then AppendIDAT
  716. else if Chunk.ChunkID = JDATChunk then AppendJDAT
  717. else if Chunk.ChunkID = JDAAChunk then AppendJDAA
  718. else if Chunk.ChunkID = PLTEChunk then LoadPLTE
  719. else if Chunk.ChunkID = tRNSChunk then LoadtRNS
  720. else if Chunk.ChunkID = bKGDChunk then LoadbKGD
  721. else if Chunk.ChunkID = acTLChunk then HandleacTL
  722. else if Chunk.ChunkID = pHYsChunk then LoadpHYs
  723. else SkipChunkData;
  724. until Eof(Handle) or (Chunk.ChunkID = MENDChunk) or
  725. ((FileType <> ngMNG) and (Chunk.ChunkID = IENDChunk));
  726. Result := True;
  727. finally
  728. FreeMemNil(ChunkData);
  729. end;
  730. end;
  731. procedure TNGFileLoader.LoadImageFromPNGFrame(FrameWidth, FrameHeight: LongInt; const IHDR: TIHDR;
  732. IDATStream: TMemoryStream; var Image: TImageData);
  733. type
  734. TGetPixelFunc = function(Line: PByteArray; X: LongInt): Byte;
  735. var
  736. LineBuffer: array[Boolean] of PByteArray;
  737. ActLine: Boolean;
  738. Data, TotalBuffer, ZeroLine, PrevLine: Pointer;
  739. BitCount, TotalPos, BytesPerPixel, I, Pass,
  740. SrcDataSize, BytesPerLine, InterlaceLineBytes, InterlaceWidth: LongInt;
  741. TotalSize: Integer;
  742. Info: TImageFormatInfo;
  743. procedure DecodeAdam7;
  744. const
  745. BitTable: array[1..8] of LongInt = ($1, $3, 0, $F, 0, 0, 0, $FF);
  746. StartBit: array[1..8] of LongInt = (7, 6, 0, 4, 0, 0, 0, 0);
  747. var
  748. Src, Dst, Dst2: PByte;
  749. CurBit, Col: LongInt;
  750. begin
  751. Src := @LineBuffer[ActLine][1];
  752. Col := ColumnStart[Pass];
  753. with Image do
  754. case BitCount of
  755. 1, 2, 4:
  756. begin
  757. Dst := @PByteArray(Data)[I * BytesPerLine];
  758. repeat
  759. CurBit := StartBit[BitCount];
  760. repeat
  761. Dst2 := @PByteArray(Dst)[(BitCount * Col) shr 3];
  762. Dst2^ := Dst2^ or ((Src^ shr CurBit) and BitTable[BitCount])
  763. shl (StartBit[BitCount] - (Col * BitCount mod 8));
  764. Inc(Col, ColumnIncrement[Pass]);
  765. Dec(CurBit, BitCount);
  766. until CurBit < 0;
  767. Inc(Src);
  768. until Col >= Width;
  769. end;
  770. else
  771. begin
  772. Dst := @PByteArray(Data)[I * BytesPerLine + Col * BytesPerPixel];
  773. repeat
  774. CopyPixel(Src, Dst, BytesPerPixel);
  775. Inc(Dst, BytesPerPixel);
  776. Inc(Src, BytesPerPixel);
  777. Inc(Dst, ColumnIncrement[Pass] * BytesPerPixel - BytesPerPixel);
  778. Inc(Col, ColumnIncrement[Pass]);
  779. until Col >= Width;
  780. end;
  781. end;
  782. end;
  783. procedure FilterScanline(Filter: Byte; BytesPerPixel: LongInt; Line, PrevLine, Target: PByteArray;
  784. BytesPerLine: LongInt);
  785. var
  786. I: LongInt;
  787. begin
  788. case Filter of
  789. 0:
  790. begin
  791. // No filter
  792. Move(Line^, Target^, BytesPerLine);
  793. end;
  794. 1:
  795. begin
  796. // Sub filter
  797. Move(Line^, Target^, BytesPerPixel);
  798. for I := BytesPerPixel to BytesPerLine - 1 do
  799. Target[I] := (Line[I] + Target[I - BytesPerPixel]) and $FF;
  800. end;
  801. 2:
  802. begin
  803. // Up filter
  804. for I := 0 to BytesPerLine - 1 do
  805. Target[I] := (Line[I] + PrevLine[I]) and $FF;
  806. end;
  807. 3:
  808. begin
  809. // Average filter
  810. for I := 0 to BytesPerPixel - 1 do
  811. Target[I] := (Line[I] + PrevLine[I] shr 1) and $FF;
  812. for I := BytesPerPixel to BytesPerLine - 1 do
  813. Target[I] := (Line[I] + (Target[I - BytesPerPixel] + PrevLine[I]) shr 1) and $FF;
  814. end;
  815. 4:
  816. begin
  817. // Paeth filter
  818. for I := 0 to BytesPerPixel - 1 do
  819. Target[I] := (Line[I] + PaethPredictor(0, PrevLine[I], 0)) and $FF;
  820. for I := BytesPerPixel to BytesPerLine - 1 do
  821. Target[I] := (Line[I] + PaethPredictor(Target[I - BytesPerPixel], PrevLine[I], PrevLine[I - BytesPerPixel])) and $FF;
  822. end;
  823. end;
  824. end;
  825. procedure TransformLOCOToRGB(Data: PByte; NumPixels, BytesPerPixel: LongInt);
  826. var
  827. I: LongInt;
  828. begin
  829. for I := 0 to NumPixels - 1 do
  830. begin
  831. if IHDR.BitDepth = 8 then
  832. begin
  833. PColor32Rec(Data).R := Byte(PColor32Rec(Data).R + PColor32Rec(Data).G);
  834. PColor32Rec(Data).B := Byte(PColor32Rec(Data).B + PColor32Rec(Data).G);
  835. end
  836. else
  837. begin
  838. PColor64Rec(Data).R := Word(PColor64Rec(Data).R + PColor64Rec(Data).G);
  839. PColor64Rec(Data).B := Word(PColor64Rec(Data).B + PColor64Rec(Data).G);
  840. end;
  841. Inc(Data, BytesPerPixel);
  842. end;
  843. end;
  844. function CheckBinaryPalette: Boolean;
  845. begin
  846. with GetLastFrame do
  847. Result := (PaletteEntries = 2) and
  848. (Palette[0].R = 0) and (Palette[0].G = 0) and (Palette[0].B = 0) and
  849. (Palette[1].R = 255) and (Palette[1].G = 255) and (Palette[1].B = 255);
  850. end;
  851. begin
  852. Image.Width := FrameWidth;
  853. Image.Height := FrameHeight;
  854. Image.Format := ifUnknown;
  855. case IHDR.ColorType of
  856. 0:
  857. begin
  858. // Gray scale image
  859. case IHDR.BitDepth of
  860. 1: Image.Format := ifBinary;
  861. 2, 4, 8: Image.Format := ifGray8;
  862. 16: Image.Format := ifGray16;
  863. end;
  864. BitCount := IHDR.BitDepth;
  865. end;
  866. 2:
  867. begin
  868. // RGB image
  869. case IHDR.BitDepth of
  870. 8: Image.Format := ifR8G8B8;
  871. 16: Image.Format := ifR16G16B16;
  872. end;
  873. BitCount := IHDR.BitDepth * 3;
  874. end;
  875. 3:
  876. begin
  877. // Indexed image
  878. if (IHDR.BitDepth = 1) and CheckBinaryPalette then
  879. Image.Format := ifBinary
  880. else
  881. Image.Format := ifIndex8;
  882. BitCount := IHDR.BitDepth;
  883. end;
  884. 4:
  885. begin
  886. // Grayscale + alpha image
  887. case IHDR.BitDepth of
  888. 8: Image.Format := ifA8Gray8;
  889. 16: Image.Format := ifA16Gray16;
  890. end;
  891. BitCount := IHDR.BitDepth * 2;
  892. end;
  893. 6:
  894. begin
  895. // ARGB image
  896. case IHDR.BitDepth of
  897. 8: Image.Format := ifA8R8G8B8;
  898. 16: Image.Format := ifA16R16G16B16;
  899. end;
  900. BitCount := IHDR.BitDepth * 4;
  901. end;
  902. end;
  903. GetImageFormatInfo(Image.Format, Info);
  904. BytesPerPixel := (BitCount + 7) div 8;
  905. LineBuffer[True] := nil;
  906. LineBuffer[False] := nil;
  907. TotalBuffer := nil;
  908. ZeroLine := nil;
  909. ActLine := True;
  910. // Start decoding
  911. with Image do
  912. try
  913. BytesPerLine := (Width * BitCount + 7) div 8;
  914. SrcDataSize := Height * BytesPerLine;
  915. GetMem(Data, SrcDataSize);
  916. FillChar(Data^, SrcDataSize, 0);
  917. GetMem(ZeroLine, BytesPerLine);
  918. FillChar(ZeroLine^, BytesPerLine, 0);
  919. if IHDR.Interlacing = 1 then
  920. begin
  921. // Decode interlaced images
  922. TotalPos := 0;
  923. DecompressBuf(IDATStream.Memory, IDATStream.Size, 0,
  924. Pointer(TotalBuffer), TotalSize);
  925. GetMem(LineBuffer[True], BytesPerLine + 1);
  926. GetMem(LineBuffer[False], BytesPerLine + 1);
  927. for Pass := 0 to 6 do
  928. begin
  929. // Prepare next interlace run
  930. if Width <= ColumnStart[Pass] then
  931. Continue;
  932. InterlaceWidth := (Width + ColumnIncrement[Pass] - 1 -
  933. ColumnStart[Pass]) div ColumnIncrement[Pass];
  934. InterlaceLineBytes := (InterlaceWidth * BitCount + 7) shr 3;
  935. I := RowStart[Pass];
  936. FillChar(LineBuffer[True][0], BytesPerLine + 1, 0);
  937. FillChar(LineBuffer[False][0], BytesPerLine + 1, 0);
  938. while I < Height do
  939. begin
  940. // Copy line from decompressed data to working buffer
  941. Move(PByteArray(TotalBuffer)[TotalPos],
  942. LineBuffer[ActLine][0], InterlaceLineBytes + 1);
  943. Inc(TotalPos, InterlaceLineBytes + 1);
  944. // Swap red and blue channels if necessary
  945. if (IHDR.ColorType in [2, 6]) then
  946. SwapRGB(@LineBuffer[ActLine][1], InterlaceWidth, IHDR.BitDepth, BytesPerPixel);
  947. // Reverse-filter current scanline
  948. FilterScanline(LineBuffer[ActLine][0], BytesPerPixel,
  949. @LineBuffer[ActLine][1], @LineBuffer[not ActLine][1],
  950. @LineBuffer[ActLine][1], InterlaceLineBytes);
  951. // Decode Adam7 interlacing
  952. DecodeAdam7;
  953. ActLine := not ActLine;
  954. // Continue with next row in interlaced order
  955. Inc(I, RowIncrement[Pass]);
  956. end;
  957. end;
  958. end
  959. else
  960. begin
  961. // Decode non-interlaced images
  962. PrevLine := ZeroLine;
  963. DecompressBuf(IDATStream.Memory, IDATStream.Size, SrcDataSize + Height,
  964. Pointer(TotalBuffer), TotalSize);
  965. for I := 0 to Height - 1 do
  966. begin
  967. // Swap red and blue channels if necessary
  968. if IHDR.ColorType in [2, 6] then
  969. SwapRGB(@PByteArray(TotalBuffer)[I * (BytesPerLine + 1) + 1], Width,
  970. IHDR.BitDepth, BytesPerPixel);
  971. // reverse-filter current scanline
  972. FilterScanline(PByteArray(TotalBuffer)[I * (BytesPerLine + 1)],
  973. BytesPerPixel, @PByteArray(TotalBuffer)[I * (BytesPerLine + 1) + 1],
  974. PrevLine, @PByteArray(Data)[I * BytesPerLine], BytesPerLine);
  975. PrevLine := @PByteArray(Data)[I * BytesPerLine];
  976. end;
  977. end;
  978. Size := Info.GetPixelsSize(Info.Format, Width, Height);
  979. if Size <> SrcDataSize then
  980. begin
  981. // If source data size is different from size of image in assigned
  982. // format we must convert it (it is in 1/2/4 bit count)
  983. GetMem(Bits, Size);
  984. case IHDR.BitDepth of
  985. 1:
  986. begin
  987. // Convert only indexed, keep black and white in ifBinary
  988. if IHDR.ColorType <> 0 then
  989. Convert1To8(Data, Bits, Width, Height, BytesPerLine, False);
  990. end;
  991. 2: Convert2To8(Data, Bits, Width, Height, BytesPerLine, IHDR.ColorType = 0);
  992. 4: Convert4To8(Data, Bits, Width, Height, BytesPerLine, IHDR.ColorType = 0);
  993. end;
  994. FreeMem(Data);
  995. end
  996. else
  997. begin
  998. // If source data size is the same as size of
  999. // image Bits in assigned format we simply copy pointer reference
  1000. Bits := Data;
  1001. end;
  1002. // LOCO transformation was used too (only for color types 2 and 6)
  1003. if (IHDR.Filter = 64) and (IHDR.ColorType in [2, 6]) then
  1004. TransformLOCOToRGB(Bits, Width * Height, BytesPerPixel);
  1005. // Images with 16 bit channels must be swapped because of PNG's big endianity
  1006. if IHDR.BitDepth = 16 then
  1007. SwapEndianWord(Bits, Width * Height * BytesPerPixel div SizeOf(Word));
  1008. finally
  1009. FreeMem(LineBuffer[True]);
  1010. FreeMem(LineBuffer[False]);
  1011. FreeMem(TotalBuffer);
  1012. FreeMem(ZeroLine);
  1013. end;
  1014. end;
  1015. {$IFNDEF DONT_LINK_JNG}
  1016. procedure TNGFileLoader.LoadImageFromJNGFrame(FrameWidth, FrameHeight: LongInt; const JHDR: TJHDR; IDATStream,
  1017. JDATStream, JDAAStream: TMemoryStream; var Image: TImageData);
  1018. var
  1019. AlphaImage: TImageData;
  1020. FakeIHDR: TIHDR;
  1021. FmtInfo: TImageFormatInfo;
  1022. I: LongInt;
  1023. AlphaPtr: PByte;
  1024. GrayPtr: PWordRec;
  1025. ColorPtr: PColor32Rec;
  1026. procedure LoadJpegFromStream(Stream: TStream; var DestImage: TImageData);
  1027. var
  1028. JpegFormat: TCustomIOJpegFileFormat;
  1029. Handle: TImagingHandle;
  1030. DynImages: TDynImageDataArray;
  1031. begin
  1032. if JHDR.SampleDepth <> 12 then
  1033. begin
  1034. JpegFormat := TCustomIOJpegFileFormat.Create;
  1035. JpegFormat.SetCustomIO(StreamIO);
  1036. Stream.Position := 0;
  1037. Handle := StreamIO.Open(Pointer(Stream), omReadOnly);
  1038. try
  1039. JpegFormat.LoadData(Handle, DynImages, True);
  1040. DestImage := DynImages[0];
  1041. finally
  1042. StreamIO.Close(Handle);
  1043. JpegFormat.Free;
  1044. SetLength(DynImages, 0);
  1045. end;
  1046. end
  1047. else
  1048. NewImage(FrameWidth, FrameHeight, ifR8G8B8, DestImage);
  1049. end;
  1050. begin
  1051. LoadJpegFromStream(JDATStream, Image);
  1052. // If present separate alpha channel is processed
  1053. if (JHDR.ColorType in [12, 14]) and (Image.Format in [ifGray8, ifR8G8B8]) then
  1054. begin
  1055. InitImage(AlphaImage);
  1056. if JHDR.AlphaCompression = 0 then
  1057. begin
  1058. // Alpha channel is PNG compressed
  1059. FakeIHDR.Width := JHDR.Width;
  1060. FakeIHDR.Height := JHDR.Height;
  1061. FakeIHDR.ColorType := 0;
  1062. FakeIHDR.BitDepth := JHDR.AlphaSampleDepth;
  1063. FakeIHDR.Filter := JHDR.AlphaFilter;
  1064. FakeIHDR.Interlacing := JHDR.AlphaInterlacing;
  1065. LoadImageFromPNGFrame(FrameWidth, FrameHeight, FakeIHDR, IDATStream, AlphaImage);
  1066. end
  1067. else
  1068. begin
  1069. // Alpha channel is JPEG compressed
  1070. LoadJpegFromStream(JDAAStream, AlphaImage);
  1071. end;
  1072. // Check if alpha channel is the same size as image
  1073. if (Image.Width <> AlphaImage.Width) and (Image.Height <> AlphaImage.Height) then
  1074. ResizeImage(AlphaImage, Image.Width, Image.Height, rfNearest);
  1075. // Check alpha channels data format
  1076. GetImageFormatInfo(AlphaImage.Format, FmtInfo);
  1077. if (FmtInfo.BytesPerPixel > 1) or (not FmtInfo.HasGrayChannel) then
  1078. ConvertImage(AlphaImage, ifGray8);
  1079. // Convert image to fromat with alpha channel
  1080. if Image.Format = ifGray8 then
  1081. ConvertImage(Image, ifA8Gray8)
  1082. else
  1083. ConvertImage(Image, ifA8R8G8B8);
  1084. // Combine alpha channel with image
  1085. AlphaPtr := AlphaImage.Bits;
  1086. if Image.Format = ifA8Gray8 then
  1087. begin
  1088. GrayPtr := Image.Bits;
  1089. for I := 0 to Image.Width * Image.Height - 1 do
  1090. begin
  1091. GrayPtr.High := AlphaPtr^;
  1092. Inc(GrayPtr);
  1093. Inc(AlphaPtr);
  1094. end;
  1095. end
  1096. else
  1097. begin
  1098. ColorPtr := Image.Bits;
  1099. for I := 0 to Image.Width * Image.Height - 1 do
  1100. begin
  1101. ColorPtr.A := AlphaPtr^;
  1102. Inc(ColorPtr);
  1103. Inc(AlphaPtr);
  1104. end;
  1105. end;
  1106. FreeImage(AlphaImage);
  1107. end;
  1108. end;
  1109. {$ENDIF}
  1110. procedure TNGFileLoader.ApplyFrameSettings(Frame: TFrameInfo; var Image: TImageData);
  1111. var
  1112. FmtInfo: TImageFormatInfo;
  1113. BackGroundColor: TColor64Rec;
  1114. ColorKey: TColor64Rec;
  1115. Alphas: PByteArray;
  1116. AlphasSize: LongInt;
  1117. IsColorKeyPresent: Boolean;
  1118. IsBackGroundPresent: Boolean;
  1119. IsColorFormat: Boolean;
  1120. procedure ConverttRNS;
  1121. begin
  1122. if FmtInfo.IsIndexed then
  1123. begin
  1124. if Alphas = nil then
  1125. begin
  1126. GetMem(Alphas, Frame.TransparencySize);
  1127. Move(Frame.Transparency^, Alphas^, Frame.TransparencySize);
  1128. AlphasSize := Frame.TransparencySize;
  1129. end;
  1130. end
  1131. else if not FmtInfo.HasAlphaChannel then
  1132. begin
  1133. FillChar(ColorKey, SizeOf(ColorKey), 0);
  1134. Move(Frame.Transparency^, ColorKey, Min(Frame.TransparencySize, SizeOf(ColorKey)));
  1135. if IsColorFormat then
  1136. SwapValues(ColorKey.R, ColorKey.B);
  1137. SwapEndianWord(@ColorKey, 3);
  1138. // 1/2/4 bit images were converted to 8 bit so we must convert color key too
  1139. if (not Frame.IsJpegFrame) and (Frame.IHDR.ColorType in [0, 4]) then
  1140. case Frame.IHDR.BitDepth of
  1141. 1: ColorKey.B := Word(ColorKey.B * 255);
  1142. 2: ColorKey.B := Word(ColorKey.B * 85);
  1143. 4: ColorKey.B := Word(ColorKey.B * 17);
  1144. end;
  1145. IsColorKeyPresent := True;
  1146. end;
  1147. end;
  1148. procedure ConvertbKGD;
  1149. begin
  1150. FillChar(BackGroundColor, SizeOf(BackGroundColor), 0);
  1151. Move(Frame.Background^, BackGroundColor, Min(Frame.BackgroundSize, SizeOf(BackGroundColor)));
  1152. if IsColorFormat then
  1153. SwapValues(BackGroundColor.R, BackGroundColor.B);
  1154. SwapEndianWord(@BackGroundColor, 3);
  1155. // 1/2/4 bit images were converted to 8 bit so we must convert back color too
  1156. if (not Frame.IsJpegFrame) and (Frame.IHDR.ColorType in [0, 4]) then
  1157. case Frame.IHDR.BitDepth of
  1158. 1: BackGroundColor.B := Word(BackGroundColor.B * 255);
  1159. 2: BackGroundColor.B := Word(BackGroundColor.B * 85);
  1160. 4: BackGroundColor.B := Word(BackGroundColor.B * 17);
  1161. end;
  1162. IsBackGroundPresent := True;
  1163. end;
  1164. procedure ReconstructPalette;
  1165. var
  1166. I: LongInt;
  1167. begin
  1168. with Image do
  1169. begin
  1170. GetMem(Palette, FmtInfo.PaletteEntries * SizeOf(TColor32Rec));
  1171. FillChar(Palette^, FmtInfo.PaletteEntries * SizeOf(TColor32Rec), $FF);
  1172. // if RGB palette was loaded from file then use it
  1173. if Frame.Palette <> nil then
  1174. for I := 0 to Min(Frame.PaletteEntries, FmtInfo.PaletteEntries) - 1 do
  1175. with Palette[I] do
  1176. begin
  1177. R := Frame.Palette[I].B;
  1178. G := Frame.Palette[I].G;
  1179. B := Frame.Palette[I].R;
  1180. end;
  1181. // if palette alphas were loaded from file then use them
  1182. if Alphas <> nil then
  1183. begin
  1184. for I := 0 to Min(AlphasSize, FmtInfo.PaletteEntries) - 1 do
  1185. Palette[I].A := Alphas[I];
  1186. end;
  1187. end;
  1188. end;
  1189. procedure ApplyColorKey;
  1190. var
  1191. DestFmt: TImageFormat;
  1192. Col32, Bkg32: TColor32Rec;
  1193. OldPixel, NewPixel: Pointer;
  1194. begin
  1195. case Image.Format of
  1196. ifGray8: DestFmt := ifA8Gray8;
  1197. ifGray16: DestFmt := ifA16Gray16;
  1198. ifR8G8B8: DestFmt := ifA8R8G8B8;
  1199. ifR16G16B16: DestFmt := ifA16R16G16B16;
  1200. else
  1201. DestFmt := ifUnknown;
  1202. end;
  1203. if DestFmt <> ifUnknown then
  1204. begin
  1205. if not IsBackGroundPresent then
  1206. BackGroundColor := ColorKey;
  1207. ConvertImage(Image, DestFmt);
  1208. // Now back color and color key must be converted to image's data format, looks ugly
  1209. case Image.Format of
  1210. ifA8Gray8:
  1211. begin
  1212. Col32 := Color32(0, 0, $FF, Byte(ColorKey.B));
  1213. Bkg32 := Color32(0, 0, 0, Byte(BackGroundColor.B));
  1214. end;
  1215. ifA16Gray16:
  1216. begin
  1217. ColorKey.G := $FFFF;
  1218. end;
  1219. ifA8R8G8B8:
  1220. begin
  1221. Col32 := Color32($FF, Byte(ColorKey.R), Byte(ColorKey.G), Byte(ColorKey.B));
  1222. Bkg32 := Color32(0, Byte(BackGroundColor.R), Byte(BackGroundColor.G), Byte(BackGroundColor.B));
  1223. end;
  1224. ifA16R16G16B16:
  1225. begin
  1226. ColorKey.A := $FFFF;
  1227. end;
  1228. end;
  1229. if Image.Format in [ifA8Gray8, ifA8R8G8B8] then
  1230. begin
  1231. OldPixel := @Col32;
  1232. NewPixel := @Bkg32;
  1233. end
  1234. else
  1235. begin
  1236. OldPixel := @ColorKey;
  1237. NewPixel := @BackGroundColor;
  1238. end;
  1239. ReplaceColor(Image, 0, 0, Image.Width, Image.Height, OldPixel, NewPixel);
  1240. end;
  1241. end;
  1242. begin
  1243. Alphas := nil;
  1244. IsColorKeyPresent := False;
  1245. IsBackGroundPresent := False;
  1246. GetImageFormatInfo(Image.Format, FmtInfo);
  1247. IsColorFormat := (Frame.IsJpegFrame and (Frame.JHDR.ColorType in [10, 14])) or
  1248. (not Frame.IsJpegFrame and (Frame.IHDR.ColorType in [2, 6]));
  1249. // Convert some chunk data to useful format
  1250. if Frame.TransparencySize > 0 then
  1251. ConverttRNS;
  1252. if Frame.BackgroundSize > 0 then
  1253. ConvertbKGD;
  1254. // Build palette for indexed images
  1255. if FmtInfo.IsIndexed then
  1256. ReconstructPalette;
  1257. // Apply color keying
  1258. if IsColorKeyPresent and not FmtInfo.HasAlphaChannel then
  1259. ApplyColorKey;
  1260. FreeMemNil(Alphas);
  1261. end;
  1262. { TNGFileSaver class implementation }
  1263. procedure TNGFileSaver.StoreImageToPNGFrame(const IHDR: TIHDR; Bits: Pointer;
  1264. FmtInfo: TImageFormatInfo; IDATStream: TMemoryStream);
  1265. var
  1266. TotalBuffer, CompBuffer, ZeroLine, PrevLine: Pointer;
  1267. FilterLines: array[0..4] of PByteArray;
  1268. TotalSize, CompSize, I, BytesPerLine, BytesPerPixel: Integer;
  1269. Filter: Byte;
  1270. Adaptive: Boolean;
  1271. procedure FilterScanline(Filter: Byte; BytesPerPixel: LongInt; Line, PrevLine, Target: PByteArray);
  1272. var
  1273. I: LongInt;
  1274. begin
  1275. case Filter of
  1276. 0:
  1277. begin
  1278. // No filter
  1279. Move(Line^, Target^, BytesPerLine);
  1280. end;
  1281. 1:
  1282. begin
  1283. // Sub filter
  1284. Move(Line^, Target^, BytesPerPixel);
  1285. for I := BytesPerPixel to BytesPerLine - 1 do
  1286. Target[I] := (Line[I] - Line[I - BytesPerPixel]) and $FF;
  1287. end;
  1288. 2:
  1289. begin
  1290. // Up filter
  1291. for I := 0 to BytesPerLine - 1 do
  1292. Target[I] := (Line[I] - PrevLine[I]) and $FF;
  1293. end;
  1294. 3:
  1295. begin
  1296. // Average filter
  1297. for I := 0 to BytesPerPixel - 1 do
  1298. Target[I] := (Line[I] - PrevLine[I] shr 1) and $FF;
  1299. for I := BytesPerPixel to BytesPerLine - 1 do
  1300. Target[I] := (Line[I] - (Line[I - BytesPerPixel] + PrevLine[I]) shr 1) and $FF;
  1301. end;
  1302. 4:
  1303. begin
  1304. // Paeth filter
  1305. for I := 0 to BytesPerPixel - 1 do
  1306. Target[I] := (Line[I] - PaethPredictor(0, PrevLine[I], 0)) and $FF;
  1307. for I := BytesPerPixel to BytesPerLine - 1 do
  1308. Target[I] := (Line[I] - PaethPredictor(Line[I - BytesPerPixel], PrevLine[I], PrevLine[I - BytesPerPixel])) and $FF;
  1309. end;
  1310. end;
  1311. end;
  1312. procedure AdaptiveFilter(var Filter: Byte; BytesPerPixel: LongInt; Line, PrevLine, Target: PByteArray);
  1313. var
  1314. I, J, BestTest: LongInt;
  1315. Sums: array[0..4] of LongInt;
  1316. begin
  1317. // Compute the output scanline using all five filters,
  1318. // and select the filter that gives the smallest sum of
  1319. // absolute values of outputs
  1320. FillChar(Sums, SizeOf(Sums), 0);
  1321. BestTest := MaxInt;
  1322. for I := 0 to 4 do
  1323. begin
  1324. FilterScanline(I, BytesPerPixel, Line, PrevLine, FilterLines[I]);
  1325. for J := 0 to BytesPerLine - 1 do
  1326. Sums[I] := Sums[I] + Abs(ShortInt(FilterLines[I][J]));
  1327. if Sums[I] < BestTest then
  1328. begin
  1329. Filter := I;
  1330. BestTest := Sums[I];
  1331. end;
  1332. end;
  1333. Move(FilterLines[Filter]^, Target^, BytesPerLine);
  1334. end;
  1335. begin
  1336. // Select precompression filter and compression level
  1337. Adaptive := False;
  1338. Filter := 0;
  1339. case PreFilter of
  1340. 6:
  1341. if not ((IHDR.BitDepth < 8) or (IHDR.ColorType = 3)) then
  1342. Adaptive := True;
  1343. 0..4: Filter := PreFilter;
  1344. else
  1345. if IHDR.ColorType in [2, 6] then
  1346. Filter := 4
  1347. end;
  1348. // Prepare data for compression
  1349. CompBuffer := nil;
  1350. FillChar(FilterLines, SizeOf(FilterLines), 0);
  1351. BytesPerPixel := Max(1, FmtInfo.BytesPerPixel);
  1352. BytesPerLine := FmtInfo.GetPixelsSize(FmtInfo.Format, LongInt(IHDR.Width), 1);
  1353. TotalSize := (BytesPerLine + 1) * LongInt(IHDR.Height);
  1354. GetMem(TotalBuffer, TotalSize);
  1355. GetMem(ZeroLine, BytesPerLine);
  1356. FillChar(ZeroLine^, BytesPerLine, 0);
  1357. PrevLine := ZeroLine;
  1358. if Adaptive then
  1359. begin
  1360. for I := 0 to 4 do
  1361. GetMem(FilterLines[I], BytesPerLine);
  1362. end;
  1363. try
  1364. // Process next scanlines
  1365. for I := 0 to IHDR.Height - 1 do
  1366. begin
  1367. // Filter scanline
  1368. if Adaptive then
  1369. begin
  1370. AdaptiveFilter(Filter, BytesPerPixel, @PByteArray(Bits)[I * BytesPerLine],
  1371. PrevLine, @PByteArray(TotalBuffer)[I * (BytesPerLine + 1) + 1]);
  1372. end
  1373. else
  1374. begin
  1375. FilterScanline(Filter, BytesPerPixel, @PByteArray(Bits)[I * BytesPerLine],
  1376. PrevLine, @PByteArray(TotalBuffer)[I * (BytesPerLine + 1) + 1]);
  1377. end;
  1378. PrevLine := @PByteArray(Bits)[I * BytesPerLine];
  1379. // Swap red and blue if necessary
  1380. if (IHDR.ColorType in [2, 6]) and not FmtInfo.IsRBSwapped then
  1381. begin
  1382. SwapRGB(@PByteArray(TotalBuffer)[I * (BytesPerLine + 1) + 1],
  1383. IHDR.Width, IHDR.BitDepth, BytesPerPixel);
  1384. end;
  1385. // Images with 16 bit channels must be swapped because of PNG's big endianess
  1386. if IHDR.BitDepth = 16 then
  1387. begin
  1388. SwapEndianWord(@PByteArray(TotalBuffer)[I * (BytesPerLine + 1) + 1],
  1389. BytesPerLine div SizeOf(Word));
  1390. end;
  1391. // Set filter used for this scanline
  1392. PByteArray(TotalBuffer)[I * (BytesPerLine + 1)] := Filter;
  1393. end;
  1394. // Compress IDAT data
  1395. CompressBuf(TotalBuffer, TotalSize, CompBuffer, CompSize,
  1396. CompressLevel, ZLibStrategy);
  1397. // Write IDAT data to stream
  1398. IDATStream.WriteBuffer(CompBuffer^, CompSize);
  1399. finally
  1400. FreeMem(TotalBuffer);
  1401. FreeMem(CompBuffer);
  1402. FreeMem(ZeroLine);
  1403. if Adaptive then
  1404. for I := 0 to 4 do
  1405. FreeMem(FilterLines[I]);
  1406. end;
  1407. end;
  1408. {$IFNDEF DONT_LINK_JNG}
  1409. procedure TNGFileSaver.StoreImageToJNGFrame(const JHDR: TJHDR;
  1410. const Image: TImageData; IDATStream, JDATStream,
  1411. JDAAStream: TMemoryStream);
  1412. var
  1413. ColorImage, AlphaImage: TImageData;
  1414. FmtInfo: TImageFormatInfo;
  1415. AlphaPtr: PByte;
  1416. GrayPtr: PWordRec;
  1417. ColorPtr: PColor32Rec;
  1418. I: LongInt;
  1419. FakeIHDR: TIHDR;
  1420. procedure SaveJpegToStream(Stream: TStream; const Image: TImageData);
  1421. var
  1422. JpegFormat: TCustomIOJpegFileFormat;
  1423. Handle: TImagingHandle;
  1424. DynImages: TDynImageDataArray;
  1425. begin
  1426. JpegFormat := TCustomIOJpegFileFormat.Create;
  1427. JpegFormat.SetCustomIO(StreamIO);
  1428. // Only JDAT stream can be saved progressive
  1429. if Stream = JDATStream then
  1430. JpegFormat.FProgressive := Progressive
  1431. else
  1432. JpegFormat.FProgressive := False;
  1433. JpegFormat.FQuality := Quality;
  1434. SetLength(DynImages, 1);
  1435. DynImages[0] := Image;
  1436. Handle := StreamIO.Open(Pointer(Stream), omCreate);
  1437. try
  1438. JpegFormat.SaveData(Handle, DynImages, 0);
  1439. finally
  1440. StreamIO.Close(Handle);
  1441. SetLength(DynImages, 0);
  1442. JpegFormat.Free;
  1443. end;
  1444. end;
  1445. begin
  1446. GetImageFormatInfo(Image.Format, FmtInfo);
  1447. InitImage(ColorImage);
  1448. InitImage(AlphaImage);
  1449. if FmtInfo.HasAlphaChannel then
  1450. begin
  1451. // Create new image for alpha channel and color image without alpha
  1452. CloneImage(Image, ColorImage);
  1453. NewImage(Image.Width, Image.Height, ifGray8, AlphaImage);
  1454. case Image.Format of
  1455. ifA8Gray8: ConvertImage(ColorImage, ifGray8);
  1456. ifA8R8G8B8: ConvertImage(ColorImage, ifR8G8B8);
  1457. end;
  1458. // Store source image's alpha to separate image
  1459. AlphaPtr := AlphaImage.Bits;
  1460. if Image.Format = ifA8Gray8 then
  1461. begin
  1462. GrayPtr := Image.Bits;
  1463. for I := 0 to Image.Width * Image.Height - 1 do
  1464. begin
  1465. AlphaPtr^ := GrayPtr.High;
  1466. Inc(GrayPtr);
  1467. Inc(AlphaPtr);
  1468. end;
  1469. end
  1470. else
  1471. begin
  1472. ColorPtr := Image.Bits;
  1473. for I := 0 to Image.Width * Image.Height - 1 do
  1474. begin
  1475. AlphaPtr^ := ColorPtr.A;
  1476. Inc(ColorPtr);
  1477. Inc(AlphaPtr);
  1478. end;
  1479. end;
  1480. // Write color image to stream as JPEG
  1481. SaveJpegToStream(JDATStream, ColorImage);
  1482. if LossyAlpha then
  1483. begin
  1484. // Write alpha image to stream as JPEG
  1485. SaveJpegToStream(JDAAStream, AlphaImage);
  1486. end
  1487. else
  1488. begin
  1489. // Alpha channel is PNG compressed
  1490. FakeIHDR.Width := JHDR.Width;
  1491. FakeIHDR.Height := JHDR.Height;
  1492. FakeIHDR.ColorType := 0;
  1493. FakeIHDR.BitDepth := JHDR.AlphaSampleDepth;
  1494. FakeIHDR.Filter := JHDR.AlphaFilter;
  1495. FakeIHDR.Interlacing := JHDR.AlphaInterlacing;
  1496. GetImageFormatInfo(AlphaImage.Format, FmtInfo);
  1497. StoreImageToPNGFrame(FakeIHDR, AlphaImage.Bits, FmtInfo, IDATStream);
  1498. end;
  1499. FreeImage(ColorImage);
  1500. FreeImage(AlphaImage);
  1501. end
  1502. else
  1503. begin
  1504. // Simply write JPEG to stream
  1505. SaveJpegToStream(JDATStream, Image);
  1506. end;
  1507. end;
  1508. {$ENDIF}
  1509. procedure TNGFileSaver.AddFrame(const Image: TImageData; IsJpegFrame: Boolean);
  1510. var
  1511. Frame: TFrameInfo;
  1512. FmtInfo: TImageFormatInfo;
  1513. Index: Integer;
  1514. procedure StorePalette;
  1515. var
  1516. Pal: PPalette24;
  1517. Alphas: PByteArray;
  1518. I, PalBytes: LongInt;
  1519. AlphasDiffer: Boolean;
  1520. begin
  1521. // Fill and save RGB part of palette to PLTE chunk
  1522. PalBytes := FmtInfo.PaletteEntries * SizeOf(TColor24Rec);
  1523. GetMem(Pal, PalBytes);
  1524. AlphasDiffer := False;
  1525. for I := 0 to FmtInfo.PaletteEntries - 1 do
  1526. begin
  1527. Pal[I].B := Image.Palette[I].R;
  1528. Pal[I].G := Image.Palette[I].G;
  1529. Pal[I].R := Image.Palette[I].B;
  1530. if Image.Palette[I].A < 255 then
  1531. AlphasDiffer := True;
  1532. end;
  1533. Frame.Palette := Pal;
  1534. Frame.PaletteEntries := FmtInfo.PaletteEntries;
  1535. // Fill and save alpha part (if there are any alphas < 255) of palette to tRNS chunk
  1536. if AlphasDiffer then
  1537. begin
  1538. PalBytes := FmtInfo.PaletteEntries * SizeOf(Byte);
  1539. GetMem(Alphas, PalBytes);
  1540. for I := 0 to FmtInfo.PaletteEntries - 1 do
  1541. Alphas[I] := Image.Palette[I].A;
  1542. Frame.Transparency := Alphas;
  1543. Frame.TransparencySize := PalBytes;
  1544. end;
  1545. end;
  1546. procedure FillFrameControlChunk(const IHDR: TIHDR; var fcTL: TfcTL);
  1547. var
  1548. Delay: Integer;
  1549. begin
  1550. fcTL.SeqNumber := 0; // Decided when writing to file
  1551. fcTL.Width := IHDR.Width;
  1552. fcTL.Height := IHDR.Height;
  1553. fcTL.XOffset := 0;
  1554. fcTL.YOffset := 0;
  1555. fcTL.DelayNumer := 1;
  1556. fcTL.DelayDenom := 3;
  1557. if FileFormat.FMetadata.HasMetaItemForSaving(SMetaFrameDelay, Index) then
  1558. begin
  1559. // Metadata contains frame delay information in milliseconds
  1560. Delay := FileFormat.FMetadata.MetaItemsForSavingMulti[SMetaFrameDelay, Index];
  1561. fcTL.DelayNumer := Delay;
  1562. fcTL.DelayDenom := 1000;
  1563. end;
  1564. fcTL.DisposeOp := DisposeOpNone;
  1565. fcTL.BlendOp := BlendOpSource;
  1566. SwapEndianUInt32(@fcTL, 5);
  1567. fcTL.DelayNumer := SwapEndianWord(fcTL.DelayNumer);
  1568. fcTL.DelayDenom := SwapEndianWord(fcTL.DelayDenom);
  1569. end;
  1570. begin
  1571. // Add new frame
  1572. Frame := AddFrameInfo;
  1573. Frame.IsJpegFrame := IsJpegFrame;
  1574. Index := Length(Frames) - 1;
  1575. with Frame do
  1576. begin
  1577. GetImageFormatInfo(Image.Format, FmtInfo);
  1578. if IsJpegFrame then
  1579. begin
  1580. {$IFNDEF DONT_LINK_JNG}
  1581. // Fill JNG header
  1582. JHDR.Width := Image.Width;
  1583. JHDR.Height := Image.Height;
  1584. case Image.Format of
  1585. ifGray8: JHDR.ColorType := 8;
  1586. ifR8G8B8: JHDR.ColorType := 10;
  1587. ifA8Gray8: JHDR.ColorType := 12;
  1588. ifA8R8G8B8: JHDR.ColorType := 14;
  1589. end;
  1590. JHDR.SampleDepth := 8; // 8-bit samples and quantization tables
  1591. JHDR.Compression := 8; // Huffman coding
  1592. JHDR.Interlacing := Iff(Progressive, 8, 0);
  1593. JHDR.AlphaSampleDepth := Iff(FmtInfo.HasAlphaChannel, 8, 0);
  1594. JHDR.AlphaCompression := Iff(LossyAlpha, 8, 0);
  1595. JHDR.AlphaFilter := 0;
  1596. JHDR.AlphaInterlacing := 0;
  1597. StoreImageToJNGFrame(JHDR, Image, IDATMemory, JDATMemory, JDAAMemory);
  1598. // Finally swap endian
  1599. SwapEndianUInt32(@JHDR, 2);
  1600. {$ENDIF}
  1601. end
  1602. else
  1603. begin
  1604. // Fill PNG header
  1605. IHDR.Width := Image.Width;
  1606. IHDR.Height := Image.Height;
  1607. IHDR.Compression := 0;
  1608. IHDR.Filter := 0;
  1609. IHDR.Interlacing := 0;
  1610. IHDR.BitDepth := FmtInfo.BytesPerPixel * 8;
  1611. // Select appropiate PNG color type and modify bitdepth
  1612. if FmtInfo.HasGrayChannel then
  1613. begin
  1614. IHDR.ColorType := 0;
  1615. if FmtInfo.HasAlphaChannel then
  1616. begin
  1617. IHDR.ColorType := 4;
  1618. IHDR.BitDepth := IHDR.BitDepth div 2;
  1619. end;
  1620. end
  1621. else if FmtInfo.Format = ifBinary then
  1622. begin
  1623. IHDR.ColorType := 0;
  1624. IHDR.BitDepth := 1;
  1625. end
  1626. else if FmtInfo.IsIndexed then
  1627. IHDR.ColorType := 3
  1628. else if FmtInfo.HasAlphaChannel then
  1629. begin
  1630. IHDR.ColorType := 6;
  1631. IHDR.BitDepth := IHDR.BitDepth div 4;
  1632. end
  1633. else
  1634. begin
  1635. IHDR.ColorType := 2;
  1636. IHDR.BitDepth := IHDR.BitDepth div 3;
  1637. end;
  1638. if FileType = ngAPNG then
  1639. begin
  1640. // Fill fcTL chunk of APNG file
  1641. FillFrameControlChunk(IHDR, fcTL);
  1642. end;
  1643. // Compress PNG image and store it to stream
  1644. StoreImageToPNGFrame(IHDR, Image.Bits, FmtInfo, IDATMemory);
  1645. // Store palette if necesary
  1646. if FmtInfo.IsIndexed then
  1647. StorePalette;
  1648. // Finally swap endian
  1649. SwapEndianUInt32(@IHDR, 2);
  1650. end;
  1651. end;
  1652. end;
  1653. function TNGFileSaver.SaveFile(Handle: TImagingHandle): Boolean;
  1654. var
  1655. I: LongInt;
  1656. Chunk: TChunkHeader;
  1657. SeqNo: UInt32;
  1658. function GetNextSeqNo: UInt32;
  1659. begin
  1660. // Seq numbers of fcTL and fdAT are "interleaved" as they share the counter.
  1661. // Example: first fcTL for IDAT has seq=0, next is fcTL for seond frame with
  1662. // seq=1, then first fdAT with seq=2, fcTL seq=3, fdAT=4, ...
  1663. Result := SwapEndianUInt32(SeqNo);
  1664. Inc(SeqNo);
  1665. end;
  1666. function CalcChunkCrc(const ChunkHdr: TChunkHeader; Data: Pointer;
  1667. Size: LongInt): UInt32;
  1668. begin
  1669. Result := $FFFFFFFF;
  1670. CalcCrc32(Result, @ChunkHdr.ChunkID, SizeOf(ChunkHdr.ChunkID));
  1671. CalcCrc32(Result, Data, Size);
  1672. Result := SwapEndianUInt32(Result xor $FFFFFFFF);
  1673. end;
  1674. procedure WriteChunk(var Chunk: TChunkHeader; ChunkData: Pointer);
  1675. var
  1676. ChunkCrc: UInt32;
  1677. SizeToWrite: LongInt;
  1678. begin
  1679. SizeToWrite := Chunk.DataSize;
  1680. Chunk.DataSize := SwapEndianUInt32(Chunk.DataSize);
  1681. ChunkCrc := CalcChunkCrc(Chunk, ChunkData, SizeToWrite);
  1682. GetIO.Write(Handle, @Chunk, SizeOf(Chunk));
  1683. if SizeToWrite <> 0 then
  1684. GetIO.Write(Handle, ChunkData, SizeToWrite);
  1685. GetIO.Write(Handle, @ChunkCrc, SizeOf(ChunkCrc));
  1686. end;
  1687. procedure WritefdAT(Frame: TFrameInfo);
  1688. var
  1689. ChunkCrc: UInt32;
  1690. ChunkSeqNo: UInt32;
  1691. begin
  1692. Chunk.ChunkID := fdATChunk;
  1693. ChunkSeqNo := GetNextSeqNo;
  1694. // fdAT saves seq number UInt32 before compressed pixels
  1695. Chunk.DataSize := Frame.IDATMemory.Size + SizeOf(UInt32);
  1696. Chunk.DataSize := SwapEndianUInt32(Chunk.DataSize);
  1697. // Calc CRC
  1698. ChunkCrc := $FFFFFFFF;
  1699. CalcCrc32(ChunkCrc, @Chunk.ChunkID, SizeOf(Chunk.ChunkID));
  1700. CalcCrc32(ChunkCrc, @ChunkSeqNo, SizeOf(ChunkSeqNo));
  1701. CalcCrc32(ChunkCrc, Frame.IDATMemory.Memory, Frame.IDATMemory.Size);
  1702. ChunkCrc := SwapEndianUInt32(ChunkCrc xor $FFFFFFFF);
  1703. // Write out all fdAT data
  1704. GetIO.Write(Handle, @Chunk, SizeOf(Chunk));
  1705. GetIO.Write(Handle, @ChunkSeqNo, SizeOf(ChunkSeqNo));
  1706. GetIO.Write(Handle, Frame.IDATMemory.Memory, Frame.IDATMemory.Size);
  1707. GetIO.Write(Handle, @ChunkCrc, SizeOf(ChunkCrc));
  1708. end;
  1709. procedure WriteGlobalMetaDataChunks(Frame: TFrameInfo);
  1710. var
  1711. XRes, YRes: Double;
  1712. begin
  1713. if FileFormat.FMetadata.GetPhysicalPixelSize(ruDpm, XRes, YRes, True) then
  1714. begin
  1715. // Save pHYs chunk
  1716. Frame.pHYs.UnitSpecifier := 1;
  1717. // PNG stores physical resolution as dots per meter
  1718. Frame.pHYs.PixelsPerUnitX := Round(XRes);
  1719. Frame.pHYs.PixelsPerUnitY := Round(YRes);
  1720. Chunk.DataSize := SizeOf(Frame.pHYs);
  1721. Chunk.ChunkID := pHYsChunk;
  1722. SwapEndianUInt32(@Frame.pHYs, SizeOf(Frame.pHYs) div SizeOf(UInt32));
  1723. WriteChunk(Chunk, @Frame.pHYs);
  1724. end;
  1725. end;
  1726. procedure WritePNGMainImageChunks(Frame: TFrameInfo);
  1727. begin
  1728. with Frame do
  1729. begin
  1730. // Write IHDR chunk
  1731. Chunk.DataSize := SizeOf(IHDR);
  1732. Chunk.ChunkID := IHDRChunk;
  1733. WriteChunk(Chunk, @IHDR);
  1734. // Write PLTE chunk if data is present
  1735. if Palette <> nil then
  1736. begin
  1737. Chunk.DataSize := PaletteEntries * SizeOf(TColor24Rec);
  1738. Chunk.ChunkID := PLTEChunk;
  1739. WriteChunk(Chunk, Palette);
  1740. end;
  1741. // Write tRNS chunk if data is present
  1742. if Transparency <> nil then
  1743. begin
  1744. Chunk.DataSize := TransparencySize;
  1745. Chunk.ChunkID := tRNSChunk;
  1746. WriteChunk(Chunk, Transparency);
  1747. end;
  1748. end;
  1749. // Write metadata related chunks
  1750. WriteGlobalMetaDataChunks(Frame);
  1751. end;
  1752. begin
  1753. Result := False;
  1754. SeqNo := 0;
  1755. case FileType of
  1756. ngPNG, ngAPNG: GetIO.Write(Handle, @PNGSignature, SizeOf(TChar8));
  1757. ngMNG: GetIO.Write(Handle, @MNGSignature, SizeOf(TChar8));
  1758. ngJNG: GetIO.Write(Handle, @JNGSignature, SizeOf(TChar8));
  1759. end;
  1760. if FileType = ngMNG then
  1761. begin
  1762. // MNG - main header before frames
  1763. SwapEndianUInt32(@MHDR, SizeOf(MHDR) div SizeOf(UInt32));
  1764. Chunk.DataSize := SizeOf(MHDR);
  1765. Chunk.ChunkID := MHDRChunk;
  1766. WriteChunk(Chunk, @MHDR);
  1767. end
  1768. else if FileType = ngAPNG then
  1769. begin
  1770. // APNG - IHDR and global chunks for all frames, then acTL chunk, then frames
  1771. // (fcTL+IDAT, fcTL+fdAT, fcTL+fdAT, fcTL+fdAT, ....)
  1772. WritePNGMainImageChunks(Frames[0]);
  1773. // Animation control chunk
  1774. acTL.NumFrames := Length(Frames);
  1775. if FileFormat.FMetadata.HasMetaItemForSaving(SMetaAnimationLoops) then
  1776. begin
  1777. // Number of plays of APNG animation
  1778. acTL.NumPlay:= FileFormat.FMetadata.MetaItemsForSaving[SMetaAnimationLoops];
  1779. end
  1780. else
  1781. acTL.NumPlay := 0;
  1782. SwapEndianUInt32(@acTL, SizeOf(acTL) div SizeOf(UInt32));
  1783. Chunk.DataSize := SizeOf(acTL);
  1784. Chunk.ChunkID := acTLChunk;
  1785. WriteChunk(Chunk, @acTL);
  1786. end;
  1787. for I := 0 to Length(Frames) - 1 do
  1788. with Frames[I] do
  1789. begin
  1790. if IsJpegFrame then
  1791. begin
  1792. // Write JHDR chunk
  1793. Chunk.DataSize := SizeOf(JHDR);
  1794. Chunk.ChunkID := JHDRChunk;
  1795. WriteChunk(Chunk, @JHDR);
  1796. // Write metadata related chunks
  1797. WriteGlobalMetaDataChunks(Frames[I]);
  1798. // Write JNG image data
  1799. Chunk.DataSize := JDATMemory.Size;
  1800. Chunk.ChunkID := JDATChunk;
  1801. WriteChunk(Chunk, JDATMemory.Memory);
  1802. // Write alpha channel if present
  1803. if JHDR.AlphaSampleDepth > 0 then
  1804. begin
  1805. if JHDR.AlphaCompression = 0 then
  1806. begin
  1807. // Alpha is PNG compressed
  1808. Chunk.DataSize := IDATMemory.Size;
  1809. Chunk.ChunkID := IDATChunk;
  1810. WriteChunk(Chunk, IDATMemory.Memory);
  1811. end
  1812. else
  1813. begin
  1814. // Alpha is JNG compressed
  1815. Chunk.DataSize := JDAAMemory.Size;
  1816. Chunk.ChunkID := JDAAChunk;
  1817. WriteChunk(Chunk, JDAAMemory.Memory);
  1818. end;
  1819. end;
  1820. // Write image end
  1821. Chunk.DataSize := 0;
  1822. Chunk.ChunkID := IENDChunk;
  1823. WriteChunk(Chunk, nil);
  1824. end
  1825. else if FileType <> ngAPNG then
  1826. begin
  1827. // Regular PNG frame (single PNG image or MNG frame)
  1828. WritePNGMainImageChunks(Frames[I]);
  1829. // Write PNG image data
  1830. Chunk.DataSize := IDATMemory.Size;
  1831. Chunk.ChunkID := IDATChunk;
  1832. WriteChunk(Chunk, IDATMemory.Memory);
  1833. // Write image end
  1834. Chunk.DataSize := 0;
  1835. Chunk.ChunkID := IENDChunk;
  1836. WriteChunk(Chunk, nil);
  1837. end
  1838. else if FileType = ngAPNG then
  1839. begin
  1840. // APNG frame - Write fcTL before frame data
  1841. Chunk.DataSize := SizeOf(fcTL);
  1842. Chunk.ChunkID := fcTLChunk;
  1843. fcTl.SeqNumber := GetNextSeqNo;
  1844. WriteChunk(Chunk, @fcTL);
  1845. // Write data - IDAT for first frame and fdAT for following ones
  1846. if I = 0 then
  1847. begin
  1848. Chunk.DataSize := IDATMemory.Size;
  1849. Chunk.ChunkID := IDATChunk;
  1850. WriteChunk(Chunk, IDATMemory.Memory);
  1851. end
  1852. else
  1853. WritefdAT(Frames[I]);
  1854. // Write image end after last frame
  1855. if I = Length(Frames) - 1 then
  1856. begin
  1857. Chunk.DataSize := 0;
  1858. Chunk.ChunkID := IENDChunk;
  1859. WriteChunk(Chunk, nil);
  1860. end;
  1861. end;
  1862. end;
  1863. if FileType = ngMNG then
  1864. begin
  1865. Chunk.DataSize := 0;
  1866. Chunk.ChunkID := MENDChunk;
  1867. WriteChunk(Chunk, nil);
  1868. end;
  1869. end;
  1870. procedure TNGFileSaver.SetFileOptions;
  1871. begin
  1872. PreFilter := FileFormat.FPreFilter;
  1873. CompressLevel := FileFormat.FCompressLevel;
  1874. LossyAlpha := FileFormat.FLossyAlpha;
  1875. Quality := FileFormat.FQuality;
  1876. Progressive := FileFormat.FProgressive;
  1877. ZLibStrategy := FileFormat.FZLibStrategy;
  1878. end;
  1879. { TAPNGAnimator class implementation }
  1880. class procedure TAPNGAnimator.Animate(var Images: TDynImageDataArray;
  1881. const acTL: TacTL; const SrcFrames: array of TFrameInfo);
  1882. var
  1883. I, SrcIdx, Offset, Len: Integer;
  1884. DestFrames: TDynImageDataArray;
  1885. SrcCanvas, DestCanvas: TImagingCanvas;
  1886. PreviousCache: TImageData;
  1887. DestFormat: TImageFormat;
  1888. FormatInfo: TImageFormatInfo;
  1889. AnimatingNeeded, BlendingNeeded: Boolean;
  1890. procedure CheckFrames;
  1891. var
  1892. I: Integer;
  1893. begin
  1894. for I := 0 to Len - 1 do
  1895. with SrcFrames[I] do
  1896. begin
  1897. if (FrameWidth <> Integer(IHDR.Width)) or (FrameHeight <> Integer(IHDR.Height)) or (Len <> Integer(acTL.NumFrames)) or
  1898. (not ((fcTL.DisposeOp = DisposeOpNone) and (fcTL.BlendOp = BlendOpSource)) and
  1899. not ((fcTL.DisposeOp = DisposeOpBackground) and (fcTL.BlendOp = BlendOpSource)) and
  1900. not ((fcTL.DisposeOp = DisposeOpBackground) and (fcTL.BlendOp = BlendOpOver))) then
  1901. begin
  1902. AnimatingNeeded := True;
  1903. end;
  1904. if fcTL.BlendOp = BlendOpOver then
  1905. BlendingNeeded := True;
  1906. if AnimatingNeeded and BlendingNeeded then
  1907. Exit;
  1908. end;
  1909. end;
  1910. begin
  1911. AnimatingNeeded := False;
  1912. BlendingNeeded := False;
  1913. Len := Length(SrcFrames);
  1914. CheckFrames;
  1915. if (Len = 0) or not AnimatingNeeded then
  1916. Exit;
  1917. if (Len = Integer(acTL.NumFrames) + 1) and (SrcFrames[0].fcTL.Width = 0) then
  1918. begin
  1919. // If default image (stored in IDAT chunk) isn't part of animation we ignore it
  1920. Offset := 1;
  1921. Len := Len - 1;
  1922. end
  1923. else
  1924. Offset := 0;
  1925. DestFormat := Images[0].Format;
  1926. GetImageFormatInfo(DestFormat, FormatInfo);
  1927. if BlendingNeeded and FormatInfo.IsIndexed then // alpha blending needed -> destination cannot be indexed
  1928. DestFormat := ifA8R8G8B8;
  1929. SetLength(DestFrames, Len);
  1930. DestCanvas := ImagingCanvases.FindBestCanvasForImage(DestFormat).Create;
  1931. SrcCanvas := ImagingCanvases.FindBestCanvasForImage(Images[0]).Create;
  1932. InitImage(PreviousCache);
  1933. NewImage(SrcFrames[0].IHDR.Width, SrcFrames[0].IHDR.Height, DestFormat, PreviousCache);
  1934. for I := 0 to Len - 1 do
  1935. begin
  1936. SrcIdx := I + Offset;
  1937. NewImage(SrcFrames[SrcIdx].IHDR.Width, SrcFrames[SrcIdx].IHDR.Height,
  1938. DestFormat, DestFrames[I]);
  1939. if DestFrames[I].Format = ifIndex8 then
  1940. Move(Images[SrcIdx].Palette^, DestFrames[I].Palette^, 256 * SizeOf(TColor32));
  1941. DestCanvas.CreateForData(@DestFrames[I]);
  1942. if (SrcFrames[SrcIdx].fcTL.DisposeOp = DisposeOpPrevious) and (SrcFrames[SrcIdx - 1].fcTL.DisposeOp <> DisposeOpPrevious) then
  1943. begin
  1944. // Cache current output buffer so we may return to it later (previous dispose op)
  1945. CopyRect(DestFrames[I - 1], 0, 0, DestFrames[I - 1].Width, DestFrames[I - 1].Height,
  1946. PreviousCache, 0, 0);
  1947. end;
  1948. if (I = 0) or (SrcIdx = 0) then
  1949. begin
  1950. // Clear whole frame with transparent black color (default for first frame)
  1951. DestCanvas.FillColor32 := pcClear;
  1952. DestCanvas.Clear;
  1953. end
  1954. else if SrcFrames[SrcIdx - 1].fcTL.DisposeOp = DisposeOpBackground then
  1955. begin
  1956. // Restore background color (clear) on previous frame's area and leave previous content outside of it
  1957. CopyRect(DestFrames[I - 1], 0, 0, DestFrames[I - 1].Width, DestFrames[I - 1].Height,
  1958. DestFrames[I], 0, 0);
  1959. DestCanvas.FillColor32 := pcClear;
  1960. DestCanvas.FillRect(BoundsToRect(SrcFrames[SrcIdx - 1].fcTL.XOffset, SrcFrames[SrcIdx - 1].fcTL.YOffset,
  1961. SrcFrames[SrcIdx - 1].FrameWidth, SrcFrames[SrcIdx - 1].FrameHeight));
  1962. end
  1963. else if SrcFrames[SrcIdx - 1].fcTL.DisposeOp = DisposeOpNone then
  1964. begin
  1965. // Clone previous frame - no change to output buffer
  1966. CopyRect(DestFrames[I - 1], 0, 0, DestFrames[I - 1].Width, DestFrames[I - 1].Height,
  1967. DestFrames[I], 0, 0);
  1968. end
  1969. else if SrcFrames[SrcIdx - 1].fcTL.DisposeOp = DisposeOpPrevious then
  1970. begin
  1971. // Revert to previous frame (cached, can't just restore DestFrames[I - 2])
  1972. CopyRect(PreviousCache, 0, 0, PreviousCache.Width, PreviousCache.Height,
  1973. DestFrames[I], 0, 0);
  1974. end;
  1975. // Copy pixels or alpha blend them over
  1976. if SrcFrames[SrcIdx].fcTL.BlendOp = BlendOpSource then
  1977. begin
  1978. CopyRect(Images[SrcIdx], 0, 0, Images[SrcIdx].Width, Images[SrcIdx].Height,
  1979. DestFrames[I], SrcFrames[SrcIdx].fcTL.XOffset, SrcFrames[SrcIdx].fcTL.YOffset);
  1980. end
  1981. else if SrcFrames[SrcIdx].fcTL.BlendOp = BlendOpOver then
  1982. begin
  1983. SrcCanvas.CreateForData(@Images[SrcIdx]);
  1984. SrcCanvas.DrawAlpha(SrcCanvas.ClipRect, DestCanvas,
  1985. SrcFrames[SrcIdx].fcTL.XOffset, SrcFrames[SrcIdx].fcTL.YOffset);
  1986. end;
  1987. FreeImage(Images[SrcIdx]);
  1988. end;
  1989. DestCanvas.Free;
  1990. SrcCanvas.Free;
  1991. FreeImage(PreviousCache);
  1992. // Assign dest frames to final output images
  1993. Images := DestFrames;
  1994. end;
  1995. { TNetworkGraphicsFileFormat class implementation }
  1996. procedure TNetworkGraphicsFileFormat.Define;
  1997. begin
  1998. inherited;
  1999. FFeatures := [ffLoad, ffSave];
  2000. FPreFilter := NGDefaultPreFilter;
  2001. FCompressLevel := NGDefaultCompressLevel;
  2002. FLossyAlpha := NGDefaultLossyAlpha;
  2003. FLossyCompression := NGDefaultLossyCompression;
  2004. FQuality := NGDefaultQuality;
  2005. FProgressive := NGDefaultProgressive;
  2006. FZLibStrategy := NGDefaultZLibStrategy;
  2007. end;
  2008. procedure TNetworkGraphicsFileFormat.CheckOptionsValidity;
  2009. begin
  2010. // Just check if save options has valid values
  2011. if not (FPreFilter in [0..6]) then
  2012. FPreFilter := NGDefaultPreFilter;
  2013. if not (FCompressLevel in [0..9]) then
  2014. FCompressLevel := NGDefaultCompressLevel;
  2015. if not (FQuality in [1..100]) then
  2016. FQuality := NGDefaultQuality;
  2017. end;
  2018. function TNetworkGraphicsFileFormat.GetSupportedFormats: TImageFormats;
  2019. begin
  2020. if FLossyCompression then
  2021. Result := NGLossyFormats
  2022. else
  2023. Result := NGLosslessFormats;
  2024. end;
  2025. procedure TNetworkGraphicsFileFormat.ConvertToSupported(var Image: TImageData;
  2026. const Info: TImageFormatInfo);
  2027. var
  2028. ConvFormat: TImageFormat;
  2029. begin
  2030. if not FLossyCompression then
  2031. begin
  2032. // Convert formats for lossless compression
  2033. if Info.HasGrayChannel then
  2034. begin
  2035. if Info.HasAlphaChannel then
  2036. begin
  2037. if Info.BytesPerPixel <= 2 then
  2038. // Convert <= 16bit grayscale images with alpha to ifA8Gray8
  2039. ConvFormat := ifA8Gray8
  2040. else
  2041. // Convert > 16bit grayscale images with alpha to ifA16Gray16
  2042. ConvFormat := ifA16Gray16
  2043. end
  2044. else
  2045. // Convert grayscale images without alpha to ifGray16
  2046. ConvFormat := ifGray16;
  2047. end
  2048. else
  2049. if Info.IsFloatingPoint then
  2050. // Convert floating point images to 64 bit ARGB (or RGB if no alpha)
  2051. ConvFormat := IffFormat(Info.HasAlphaChannel, ifA16B16G16R16, ifB16G16R16)
  2052. else if Info.HasAlphaChannel or Info.IsSpecial then
  2053. // Convert all other images with alpha or special images to A8R8G8B8
  2054. ConvFormat := ifA8R8G8B8
  2055. else
  2056. // Convert images without alpha to R8G8B8
  2057. ConvFormat := ifR8G8B8;
  2058. end
  2059. else
  2060. begin
  2061. // Convert formats for lossy compression
  2062. if Info.HasGrayChannel then
  2063. ConvFormat := IffFormat(Info.HasAlphaChannel, ifA8Gray8, ifGray8)
  2064. else
  2065. ConvFormat := IffFormat(Info.HasAlphaChannel, ifA8R8G8B8, ifR8G8B8);
  2066. end;
  2067. ConvertImage(Image, ConvFormat);
  2068. end;
  2069. function TNetworkGraphicsFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
  2070. var
  2071. ReadCount: LongInt;
  2072. Sig: TChar8;
  2073. begin
  2074. Result := False;
  2075. if Handle <> nil then
  2076. with GetIO do
  2077. begin
  2078. FillChar(Sig, SizeOf(Sig), 0);
  2079. ReadCount := Read(Handle, @Sig, SizeOf(Sig));
  2080. Seek(Handle, -ReadCount, smFromCurrent);
  2081. Result := (ReadCount = SizeOf(Sig)) and (Sig = FSignature);
  2082. end;
  2083. end;
  2084. { TPNGFileFormat class implementation }
  2085. procedure TPNGFileFormat.Define;
  2086. begin
  2087. inherited;
  2088. FName := SPNGFormatName;
  2089. FFeatures := FFeatures + [ffMultiImage];
  2090. FLoadAnimated := PNGDefaultLoadAnimated;
  2091. AddMasks(SPNGMasks);
  2092. FSignature := PNGSignature;
  2093. RegisterOption(ImagingPNGPreFilter, @FPreFilter);
  2094. RegisterOption(ImagingPNGCompressLevel, @FCompressLevel);
  2095. RegisterOption(ImagingPNGLoadAnimated, @FLoadAnimated);
  2096. RegisterOption(ImagingPNGZLibStrategy, @FZLibStrategy);
  2097. end;
  2098. function TPNGFileFormat.LoadData(Handle: TImagingHandle;
  2099. var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
  2100. var
  2101. I, Len: LongInt;
  2102. NGFileLoader: TNGFileLoader;
  2103. begin
  2104. Result := False;
  2105. NGFileLoader := TNGFileLoader.Create(Self);
  2106. try
  2107. // Use NG file parser to load file
  2108. if NGFileLoader.LoadFile(Handle) and (Length(NGFileLoader.Frames) > 0) then
  2109. begin
  2110. Len := Length(NGFileLoader.Frames);
  2111. SetLength(Images, Len);
  2112. for I := 0 to Len - 1 do
  2113. with NGFileLoader.Frames[I] do
  2114. begin
  2115. // Build actual image bits
  2116. if not IsJpegFrame then
  2117. NGFileLoader.LoadImageFromPNGFrame(FrameWidth, FrameHeight, IHDR, IDATMemory, Images[I]);
  2118. // Build palette, aply color key or background
  2119. NGFileLoader.ApplyFrameSettings(NGFileLoader.Frames[I], Images[I]);
  2120. Result := True;
  2121. end;
  2122. // Animate APNG images
  2123. if (NGFileLoader.FileType = ngAPNG) and FLoadAnimated then
  2124. TAPNGAnimator.Animate(Images, NGFileLoader.acTL, NGFileLoader.Frames);
  2125. end;
  2126. finally
  2127. NGFileLoader.LoadMetaData; // Store metadata
  2128. NGFileLoader.Free;
  2129. end;
  2130. end;
  2131. function TPNGFileFormat.SaveData(Handle: TImagingHandle;
  2132. const Images: TDynImageDataArray; Index: LongInt): Boolean;
  2133. var
  2134. I: Integer;
  2135. ImageToSave: TImageData;
  2136. MustBeFreed: Boolean;
  2137. NGFileSaver: TNGFileSaver;
  2138. DefaultFormat: TImageFormat;
  2139. Screen: TImageData;
  2140. AnimWidth, AnimHeight: Integer;
  2141. begin
  2142. Result := False;
  2143. DefaultFormat := ifDefault;
  2144. AnimWidth := 0;
  2145. AnimHeight := 0;
  2146. NGFileSaver := TNGFileSaver.Create(Self);
  2147. // Save images with more frames as APNG format
  2148. if Length(Images) > 1 then
  2149. begin
  2150. NGFileSaver.FileType := ngAPNG;
  2151. // Get max dimensions of frames
  2152. AnimWidth := Images[FFirstIdx].Width;
  2153. AnimHeight := Images[FFirstIdx].Height;
  2154. for I := FFirstIdx + 1 to FLastIdx do
  2155. begin
  2156. AnimWidth := Max(AnimWidth, Images[I].Width);
  2157. AnimHeight := Max(AnimHeight, Images[I].Height);
  2158. end;
  2159. end
  2160. else
  2161. NGFileSaver.FileType := ngPNG;
  2162. NGFileSaver.SetFileOptions;
  2163. with NGFileSaver do
  2164. try
  2165. // Store all frames to be saved frames file saver
  2166. for I := FFirstIdx to FLastIdx do
  2167. begin
  2168. if MakeCompatible(Images[I], ImageToSave, MustBeFreed) then
  2169. try
  2170. if FileType = ngAPNG then
  2171. begin
  2172. // IHDR chunk is shared for all frames so all frames must have the
  2173. // same data format as the first image.
  2174. if I = FFirstIdx then
  2175. begin
  2176. DefaultFormat := ImageToSave.Format;
  2177. // Subsequenet frames may be bigger than the first one.
  2178. // APNG doens't support this - max allowed size is what's written in
  2179. // IHDR - size of main/default/first image. If some frame is
  2180. // bigger than the first one we need to resize (create empty bigger
  2181. // image and copy) the first frame so all following frames could fit to
  2182. // its area.
  2183. if (ImageToSave.Width <> AnimWidth) or (ImageToSave.Height <> AnimHeight) then
  2184. begin
  2185. InitImage(Screen);
  2186. NewImage(AnimWidth, AnimHeight, ImageToSave.Format, Screen);
  2187. CopyRect(ImageToSave, 0, 0, ImageToSave.Width, ImageToSave.Height, Screen, 0, 0);
  2188. if MustBeFreed then
  2189. FreeImage(ImageToSave);
  2190. ImageToSave := Screen;
  2191. end;
  2192. end
  2193. else if ImageToSave.Format <> DefaultFormat then
  2194. begin
  2195. if MustBeFreed then
  2196. ConvertImage(ImageToSave, DefaultFormat)
  2197. else
  2198. begin
  2199. CloneImage(Images[I], ImageToSave);
  2200. ConvertImage(ImageToSave, DefaultFormat);
  2201. MustBeFreed := True;
  2202. end;
  2203. end;
  2204. end;
  2205. // Add image as PNG frame
  2206. AddFrame(ImageToSave, False);
  2207. finally
  2208. if MustBeFreed then
  2209. FreeImage(ImageToSave);
  2210. end
  2211. else
  2212. Exit;
  2213. end;
  2214. // Finally save PNG file
  2215. SaveFile(Handle);
  2216. Result := True;
  2217. finally
  2218. NGFileSaver.Free;
  2219. end;
  2220. end;
  2221. {$IFNDEF DONT_LINK_MNG}
  2222. { TMNGFileFormat class implementation }
  2223. procedure TMNGFileFormat.Define;
  2224. begin
  2225. inherited;
  2226. FName := SMNGFormatName;
  2227. FFeatures := FFeatures + [ffMultiImage];
  2228. AddMasks(SMNGMasks);
  2229. FSignature := MNGSignature;
  2230. RegisterOption(ImagingMNGLossyCompression, @FLossyCompression);
  2231. RegisterOption(ImagingMNGLossyAlpha, @FLossyAlpha);
  2232. RegisterOption(ImagingMNGPreFilter, @FPreFilter);
  2233. RegisterOption(ImagingMNGCompressLevel, @FCompressLevel);
  2234. RegisterOption(ImagingMNGQuality, @FQuality);
  2235. RegisterOption(ImagingMNGProgressive, @FProgressive);
  2236. end;
  2237. function TMNGFileFormat.LoadData(Handle: TImagingHandle;
  2238. var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
  2239. var
  2240. NGFileLoader: TNGFileLoader;
  2241. I, Len: LongInt;
  2242. begin
  2243. Result := False;
  2244. NGFileLoader := TNGFileLoader.Create(Self);
  2245. try
  2246. // Use NG file parser to load file
  2247. if NGFileLoader.LoadFile(Handle) then
  2248. begin
  2249. Len := Length(NGFileLoader.Frames);
  2250. if Len > 0 then
  2251. begin
  2252. SetLength(Images, Len);
  2253. for I := 0 to Len - 1 do
  2254. with NGFileLoader.Frames[I] do
  2255. begin
  2256. // Build actual image bits
  2257. if IsJpegFrame then
  2258. NGFileLoader.LoadImageFromJNGFrame(FrameWidth, FrameHeight, JHDR, IDATMemory, JDATMemory, JDAAMemory, Images[I])
  2259. else
  2260. NGFileLoader.LoadImageFromPNGFrame(FrameWidth, FrameHeight, IHDR, IDATMemory, Images[I]);
  2261. // Build palette, aply color key or background
  2262. NGFileLoader.ApplyFrameSettings(NGFileLoader.Frames[I], Images[I]);
  2263. end;
  2264. end
  2265. else
  2266. begin
  2267. // Some MNG files (with BASI-IEND streams) dont have actual pixel data
  2268. SetLength(Images, 1);
  2269. NewImage(NGFileLoader.MHDR.FrameWidth, NGFileLoader.MHDR.FrameWidth, ifDefault, Images[0]);
  2270. end;
  2271. Result := True;
  2272. end;
  2273. finally
  2274. NGFileLoader.LoadMetaData; // Store metadata
  2275. NGFileLoader.Free;
  2276. end;
  2277. end;
  2278. function TMNGFileFormat.SaveData(Handle: TImagingHandle;
  2279. const Images: TDynImageDataArray; Index: LongInt): Boolean;
  2280. var
  2281. NGFileSaver: TNGFileSaver;
  2282. I, LargestWidth, LargestHeight: LongInt;
  2283. ImageToSave: TImageData;
  2284. MustBeFreed: Boolean;
  2285. begin
  2286. Result := False;
  2287. LargestWidth := 0;
  2288. LargestHeight := 0;
  2289. NGFileSaver := TNGFileSaver.Create(Self);
  2290. NGFileSaver.FileType := ngMNG;
  2291. NGFileSaver.SetFileOptions;
  2292. with NGFileSaver do
  2293. try
  2294. // Store all frames to be saved frames file saver
  2295. for I := FFirstIdx to FLastIdx do
  2296. begin
  2297. if MakeCompatible(Images[I], ImageToSave, MustBeFreed) then
  2298. try
  2299. // Add image as PNG or JNG frame
  2300. AddFrame(ImageToSave, FLossyCompression);
  2301. // Remember largest frame width and height
  2302. LargestWidth := Iff(LargestWidth < ImageToSave.Width, ImageToSave.Width, LargestWidth);
  2303. LargestHeight := Iff(LargestHeight < ImageToSave.Height, ImageToSave.Height, LargestHeight);
  2304. finally
  2305. if MustBeFreed then
  2306. FreeImage(ImageToSave);
  2307. end
  2308. else
  2309. Exit;
  2310. end;
  2311. // Fill MNG header
  2312. MHDR.FrameWidth := LargestWidth;
  2313. MHDR.FrameHeight := LargestHeight;
  2314. MHDR.TicksPerSecond := 0;
  2315. MHDR.NominalLayerCount := 0;
  2316. MHDR.NominalFrameCount := Length(Frames);
  2317. MHDR.NominalPlayTime := 0;
  2318. MHDR.SimplicityProfile := 473; // 111011001 binary, defines MNG-VLC with transparency and JNG support
  2319. // Finally save MNG file
  2320. SaveFile(Handle);
  2321. Result := True;
  2322. finally
  2323. NGFileSaver.Free;
  2324. end;
  2325. end;
  2326. {$ENDIF}
  2327. {$IFNDEF DONT_LINK_JNG}
  2328. { TJNGFileFormat class implementation }
  2329. procedure TJNGFileFormat.Define;
  2330. begin
  2331. inherited;
  2332. FName := SJNGFormatName;
  2333. AddMasks(SJNGMasks);
  2334. FSignature := JNGSignature;
  2335. FLossyCompression := True;
  2336. RegisterOption(ImagingJNGLossyAlpha, @FLossyAlpha);
  2337. RegisterOption(ImagingJNGAlphaPreFilter, @FPreFilter);
  2338. RegisterOption(ImagingJNGAlphaCompressLevel, @FCompressLevel);
  2339. RegisterOption(ImagingJNGQuality, @FQuality);
  2340. RegisterOption(ImagingJNGProgressive, @FProgressive);
  2341. end;
  2342. function TJNGFileFormat.LoadData(Handle: TImagingHandle;
  2343. var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
  2344. var
  2345. NGFileLoader: TNGFileLoader;
  2346. begin
  2347. Result := False;
  2348. NGFileLoader := TNGFileLoader.Create(Self);
  2349. try
  2350. // Use NG file parser to load file
  2351. if NGFileLoader.LoadFile(Handle) and (Length(NGFileLoader.Frames) > 0) then
  2352. with NGFileLoader.Frames[0] do
  2353. begin
  2354. SetLength(Images, 1);
  2355. // Build actual image bits
  2356. if IsJpegFrame then
  2357. NGFileLoader.LoadImageFromJNGFrame(FrameWidth, FrameHeight, JHDR, IDATMemory, JDATMemory, JDAAMemory, Images[0]);
  2358. // Build palette, aply color key or background
  2359. NGFileLoader.ApplyFrameSettings(NGFileLoader.Frames[0], Images[0]);
  2360. Result := True;
  2361. end;
  2362. finally
  2363. NGFileLoader.LoadMetaData; // Store metadata
  2364. NGFileLoader.Free;
  2365. end;
  2366. end;
  2367. function TJNGFileFormat.SaveData(Handle: TImagingHandle;
  2368. const Images: TDynImageDataArray; Index: LongInt): Boolean;
  2369. var
  2370. NGFileSaver: TNGFileSaver;
  2371. ImageToSave: TImageData;
  2372. MustBeFreed: Boolean;
  2373. begin
  2374. // Make image JNG compatible, store it in saver, and save it to file
  2375. Result := MakeCompatible(Images[Index], ImageToSave, MustBeFreed);
  2376. if Result then
  2377. begin
  2378. NGFileSaver := TNGFileSaver.Create(Self);
  2379. with NGFileSaver do
  2380. try
  2381. FileType := ngJNG;
  2382. SetFileOptions;
  2383. AddFrame(ImageToSave, True);
  2384. SaveFile(Handle);
  2385. finally
  2386. // Free NG saver and compatible image
  2387. NGFileSaver.Free;
  2388. if MustBeFreed then
  2389. FreeImage(ImageToSave);
  2390. end;
  2391. end;
  2392. end;
  2393. {$ENDIF}
  2394. initialization
  2395. RegisterImageFileFormat(TPNGFileFormat);
  2396. {$IFNDEF DONT_LINK_MNG}
  2397. RegisterImageFileFormat(TMNGFileFormat);
  2398. {$ENDIF}
  2399. {$IFNDEF DONT_LINK_JNG}
  2400. RegisterImageFileFormat(TJNGFileFormat);
  2401. {$ENDIF}
  2402. finalization
  2403. {
  2404. File Notes:
  2405. -- TODOS ----------------------------------------------------
  2406. - nothing now
  2407. -- 0.77 Changes/Bug Fixes -----------------------------------
  2408. - Reads and writes APNG animation loop count metadata.
  2409. - Writes frame delays of APNG from metadata.
  2410. - Fixed color keys in 8bit depth PNG/MNG loading.
  2411. - Fixed needless (and sometimes buggy) conversion to format with alpha
  2412. channel in FPC (GetMem(0) <> nil!).
  2413. - Added support for optional ZLib compression strategy.
  2414. - Added loading and saving of ifBinary (1bit black and white)
  2415. format images. During loading grayscale 1bpp and indexed 1bpp
  2416. (with only black and white colors in palette) are treated as ifBinary.
  2417. ifBinary are saved as 1bpp grayscale PNGs.
  2418. -- 0.26.5 Changes/Bug Fixes ---------------------------------
  2419. - Reads frame delays from APNG files into metadata.
  2420. - Added loading and saving of metadata from these chunks: pHYs.
  2421. - Simplified decoding of 1/2/4 bit images a bit (less code).
  2422. -- 0.26.3 Changes/Bug Fixes ---------------------------------
  2423. - Added APNG saving support.
  2424. - Added APNG support to NG loader and animating to PNG loader.
  2425. -- 0.26.1 Changes/Bug Fixes ---------------------------------
  2426. - Changed file format conditional compilation to reflect changes
  2427. in LINK symbols.
  2428. -- 0.24.3 Changes/Bug Fixes ---------------------------------
  2429. - Changes for better thread safety.
  2430. -- 0.23 Changes/Bug Fixes -----------------------------------
  2431. - Added loading of global palettes and transparencies in MNG files
  2432. (and by doing so fixed crash when loading images with global PLTE or tRNS).
  2433. -- 0.21 Changes/Bug Fixes -----------------------------------
  2434. - Small changes in converting to supported formats.
  2435. - MakeCompatible method moved to base class, put ConvertToSupported here.
  2436. GetSupportedFormats removed, it is now set in constructor.
  2437. - Made public properties for options registered to SetOption/GetOption
  2438. functions.
  2439. - Changed extensions to filename masks.
  2440. - Changed SaveData, LoadData, and MakeCompatible methods according
  2441. to changes in base class in Imaging unit.
  2442. -- 0.17 Changes/Bug Fixes -----------------------------------
  2443. - MNG and JNG support added, PNG support redesigned to support NG file handlers
  2444. - added classes for working with NG file formats
  2445. - stuff from old ImagingPng unit added and that unit was deleted
  2446. - unit created and initial stuff added
  2447. -- 0.15 Changes/Bug Fixes -----------------------------------
  2448. - when saving indexed images save alpha to tRNS?
  2449. - added some defines and ifdefs to dzlib unit to allow choosing
  2450. impaszlib, fpc's paszlib, zlibex or other zlib implementation
  2451. - added colorkeying support
  2452. - fixed 16bit channel image handling - pixels were not swapped
  2453. - fixed arithmetic overflow (in paeth filter) in FPC
  2454. - data of unknown chunks are skipped and not needlesly loaded
  2455. -- 0.13 Changes/Bug Fixes -----------------------------------
  2456. - adaptive filtering added to PNG saving
  2457. - TPNGFileFormat class added
  2458. }
  2459. end.