ImagingNetworkGraphics.pas 86 KB

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