Statistics
| Branch: | Tag: | Revision:

root / Imaging / ImagingNetworkGraphics.pas @ 0:95bd93c28625

History | View | Annotate | Download (67.4 kB)

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