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.
|