Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (115.8 kB)

1
{
2
  $Id: Imaging.pas 99 2007-06-26 04:12:01Z 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 is heart of Imaging library. It contains basic functions for
30
  manipulating image data as well as various image file format support.}
31
unit Imaging;
32
33
{$I ImagingOptions.inc}
34
35
interface
36
37
uses
38
  ImagingTypes, SysUtils, Classes;
39
40
type
41
  { Default Imaging excepton class.}
42
  EImagingError = class(Exception);
43
44
  { Dynamic array of TImageData records.}
45
  TDynImageDataArray = array of TImageData;
46
47
48
{ ------------------------------------------------------------------------
49
                       Low Level Interface Functions
50
  ------------------------------------------------------------------------}
51
52
{ General Functions }
53
54
{ Initializes image (all is set to zeroes). Call this for each image
55
  before using it (before calling every other function) to be sure there
56
  are no random-filled bytes (which would cause errors later).}
57
procedure InitImage(var Image: TImageData);
58
{ Creates empty image of given dimensions and format. Image is filled with
59
  transparent black color (A=0, R=0, G=0, B=0).}
60
function NewImage(Width, Height: LongInt; Format: TImageFormat;
61
  var Image: TImageData): Boolean;
62
{ Returns True if given TImageData record is valid.}
63
function TestImage(const Image: TImageData): Boolean;
64
{ Frees given image data. Ater this call image is in the same state
65
  as after calling InitImage. If image is not valid (dost not pass TestImage
66
  test) it is only zeroed by calling InitImage.}
67
procedure FreeImage(var Image: TImageData);
68
{ Call FreeImage() on all images in given dynamic array and sets its
69
  length to zero.}
70
procedure FreeImagesInArray(var Images: TDynImageDataArray);
71
{ Returns True if all TImageData records in given array are valid. Returns False
72
  if at least one is invalid or if array is empty.}
73
function TestImagesInArray(const Images: TDynImageDataArray): Boolean;
74
{ Checks given file for every supported image file format and if
75
  the file is in one of them returns its string identifier
76
  (which can be used in LoadFromStream/LoadFromMem type functions).
77
  If file is not in any of the supported formats empty string is returned.}
78
function DetermineFileFormat(const FileName: string): string;
79
{ Checks given stream for every supported image file format and if
80
  the stream is in one of them returns its string identifier
81
  (which can be used in LoadFromStream/LoadFromMem type functions).
82
  If stream is not in any of the supported formats empty string is returned.}
83
function DetermineStreamFormat(Stream: TStream): string;
84
{ Checks given memory for every supported image file format and if
85
  the memory is in one of them returns its string identifier
86
  (which can be used in LoadFromStream/LoadFromMem type functions).
87
  If memory is not in any of the supported formats empty string is returned.}
88
function DetermineMemoryFormat(Data: Pointer; Size: LongInt): string;
89
{ Checks that an apropriate file format is supported purely from inspecting
90
  the given file name's extension (not contents of the file itself).
91
  The file need not exist.}
92
function IsFileFormatSupported(const FileName: string): Boolean;
93
{ Enumerates all registered image file formats. Descriptive name,
94
  default extension, masks (like '*.jpg,*.jfif') and some capabilities
95
  of each format are returned. To enumerate all formats start with Index at 0 and
96
  call EnumFileFormats with given Index in loop until it returns False (Index is
97
  automatically increased by 1 in function's body on successful call).}
98
function EnumFileFormats(var Index: LongInt; var Name, DefaultExt, Masks: string;
99
  var CanSaveImages, IsMultiImageFormat: Boolean): Boolean;
100
101
{ Loading Functions }
102
103
{ Loads single image from given file.}
104
function LoadImageFromFile(const FileName: string; var Image: TImageData): Boolean;
105
{ Loads single image from given stream. If function fails stream position
106
  is not changed.}
107
function LoadImageFromStream(Stream: TStream; var Image: TImageData): Boolean;
108
{ Loads single image from given memory location.}
109
function LoadImageFromMemory(Data: Pointer; Size: LongInt; var Image: TImageData): Boolean;
110
{ Loads multiple images from given file.}
111
function LoadMultiImageFromFile(const FileName: string;
112
  var Images: TDynImageDataArray): Boolean;
113
{ Loads multiple images from given stream. If function fails stream position
114
  is not changed.}
115
function LoadMultiImageFromStream(Stream: TStream;
116
  var Images: TDynImageDataArray): Boolean;
117
{ Loads multiple images from given memory location.}
118
function LoadMultiImageFromMemory(Data: Pointer; Size: LongInt;
119
  var Images: TDynImageDataArray): Boolean;
120
121
{ Saving Functions }
122
123
{ Saves single image to given file.}
124
function SaveImageToFile(const FileName: string; const Image: TImageData): Boolean;
125
{ Saves single image to given stream. If function fails stream position
126
  is not changed. Ext identifies desired image file format (jpg, png, dds, ...).}
127
function SaveImageToStream(const Ext: string; Stream: TStream;
128
  const Image: TImageData): Boolean;
129
{ Saves single image to given memory location. Memory must be allocated and its
130
  size is passed in Size parameter in which number of written bytes is returned.
131
  Ext identifies desired image file format (jpg, png, dds, ...).}
132
function SaveImageToMemory(const Ext: string; Data: Pointer; var Size: LongInt;
133
  const Image: TImageData): Boolean;
134
{ Saves multiple images to given file. If format supports
135
  only single level images and there are multiple images to be saved,
136
  they are saved as sequence of files img000.jpg, img001.jpg ....).}
137
function SaveMultiImageToFile(const FileName: string;
138
  const Images: TDynImageDataArray): Boolean;
139
{ Saves multiple images to given stream. If format supports
140
  only single level images and there are multiple images to be saved,
141
  they are saved one after another to the stream. If function fails stream
142
  position is not changed. Ext identifies desired image file format (jpg, png, dds, ...).}
143
function SaveMultiImageToStream(const Ext: string; Stream: TStream;
144
  const Images: TDynImageDataArray): Boolean;
145
{ Saves multiple images to given memory location. If format supports
146
  only single level images and there are multiple images to be saved,
147
  they are saved one after another to the memory. Memory must be allocated and
148
  its size is passed in Size parameter in which number of written bytes is returned.
149
  Ext identifies desired image file format (jpg, png, dds, ...).}
150
function SaveMultiImageToMemory(const Ext: string; Data: Pointer;
151
  var Size: LongInt; const Images: TDynImageDataArray): Boolean;
152
153
{ Manipulation Functions }
154
155
{ Creates identical copy of image data. Clone should be initialized
156
  by InitImage or it should be vaild image which will be freed by CloneImage.}
157
function CloneImage(const Image: TImageData; var Clone: TImageData): Boolean;
158
{ Converts image to the given format.}
159
function ConvertImage(var Image: TImageData; DestFormat: TImageFormat): Boolean;
160
{ Flips given image. Reverses the image along its horizontal axis ? the top
161
  becomes the bottom and vice versa.}
162
function FlipImage(var Image: TImageData): Boolean;
163
{ Mirrors given image. Reverses the image along its vertical axis ? the left
164
  side becomes the right and vice versa.}
165
function MirrorImage(var Image: TImageData): Boolean;
166
{ Resizes given image to new dimensions. Nearest, bilinear, or bicubic filtering
167
  can be used. Input Image must already be created - use NewImage to create new images.}
168
function ResizeImage(var Image: TImageData; NewWidth, NewHeight: LongInt;
169
  Filter: TResizeFilter): Boolean;
170
{ Swaps SrcChannel and DstChannel color or alpha channels of image.
171
  Use ChannelRed, ChannelBlue, ChannelGreen, ChannelAlpha constants to
172
  identify channels.}
173
function SwapChannels(var Image: TImageData; SrcChannel, DstChannel: LongInt): Boolean;
174
{ Reduces the number of colors of the Image. Currently MaxColors must be in
175
  range <2, 4096>. Color reduction works also for alpha channel. Note that for
176
  large images and big number of colors it can be very slow.
177
  Output format of the image is the same as input format.}
178
function ReduceColors(var Image: TImageData; MaxColors: LongInt): Boolean;
179
{ Generates mipmaps for image. Levels is the number of desired mipmaps levels
180
  with zero (or some invalid number) meaning all possible levels.}
181
function GenerateMipMaps(const Image: TImageData; Levels: LongInt;
182
  var MipMaps: TDynImageDataArray): Boolean;
183
{ Maps image to existing palette producing image in ifIndex8 format.
184
  Pal must be allocated to at least Entries * SizeOf(TColor32Rec) bytes.
185
  As resulting image is in 8bit indexed format Entries must be lower or
186
  equal to 256.}
187
function MapImageToPalette(var Image: TImageData; Pal: PPalette32;
188
  Entries: LongInt): Boolean;
189
{ Splits image into XChunks x YChunks subimages. Default size of each chunk is
190
  ChunkWidth x ChunkHeight. If PreserveSize si True chunks at the edges of
191
  the image are also ChunkWidth x ChunkHeight sized and empty space is filled
192
  with Fill pixels. After calling this function XChunks contains number of
193
  chunks along x axis and YChunks along y axis. To access chunk [X, Y] use this
194
  index: Chunks[Y * XChunks + X].}
195
function SplitImage(var Image: TImageData; var Chunks: TDynImageDataArray;
196
  ChunkWidth, ChunkHeight: LongInt; var XChunks, YChunks: LongInt;
197
  PreserveSize: Boolean; Fill: Pointer): Boolean;
198
{ Creates palette with MaxColors based on the colors of images in Images array.
199
  Use it when you want to convert several images to indexed format using
200
  single palette for all of them. If ConvertImages is True images in array
201
  are converted to indexed format using resulting palette. if it is False
202
  images are left intact and only resulting palatte is returned in Pal.
203
  Pal must be allocated to have at least MaxColors entries.}
204
function MakePaletteForImages(var Images: TDynImageDataArray; Pal: PPalette32;
205
  MaxColors: LongInt; ConvertImages: Boolean): Boolean;
206
{ Rotates image by 90, 180, 270, -90, -180, or -270 degrees counterclockwise.
207
  Only multiples of 90 degrees are allowed.}
208
function RotateImage(var Image: TImageData; Angle: LongInt): Boolean;
209
210
{ Drawing/Pixel functions }
211
212
{ Copies rectangular part of SrcImage to DstImage. No blending is performed -
213
  alpha is simply copied to destination image. Operates also with
214
  negative X and Y coordinates.
215
  Note that copying is fastest for images in the same data format
216
  (and slowest for images in special formats).}
217
function CopyRect(const SrcImage: TImageData; SrcX, SrcY, Width, Height: LongInt;
218
  var DstImage: TImageData; DstX, DstY: LongInt): Boolean;
219
{ Fills given rectangle of image with given pixel fill data. Fill should point
220
  to the pixel in the same format as the given image is in.}
221
function FillRect(var Image: TImageData; X, Y, Width, Height: LongInt; FillColor: Pointer): Boolean;
222
{ Replaces pixels with OldPixel in the given rectangle by NewPixel.
223
  OldPixel and NewPixel should point to the pixels in the same format
224
  as the given image is in.}
225
function ReplaceColor(var Image: TImageData; X, Y, Width, Height: LongInt;
226
  OldColor, NewColor: Pointer): Boolean;
227
{ Stretches the contents of the source rectangle to the destination rectangle
228
  with optional resampling. No blending is performed - alpha is
229
  simply copied/resampled to destination image. Note that stretching is
230
  fastest for images in the same data format (and slowest for
231
  images in special formats).}
232
function StretchRect(const SrcImage: TImageData; SrcX, SrcY, SrcWidth,
233
  SrcHeight: LongInt; var DstImage: TImageData; DstX, DstY, DstWidth,
234
  DstHeight: LongInt; Filter: TResizeFilter): Boolean;
235
{ Copies pixel of Image at [X, Y] to memory pointed at by Pixel. Doesn't
236
  work with special formats.}
237
procedure GetPixelDirect(const Image: TImageData; X, Y: LongInt; Pixel: Pointer);
238
{ Copies pixel from memory pointed at by Pixel to Image at position [X, Y].
239
  Doesn't work with special formats.}
240
procedure SetPixelDirect(const Image: TImageData; X, Y: LongInt; Pixel: Pointer);
241
{ Function for getting pixel colors. Native pixel is read from Image and
242
  then translated to 32 bit ARGB. Works for all image formats (except special)
243
  so it is not very fast.}
244
function GetPixel32(const Image: TImageData; X, Y: LongInt): TColor32Rec;
245
{ Procedure for setting pixel colors. Input 32 bit ARGB color is translated to
246
  native format and then written to Image. Works for all image formats (except special)
247
  so it is not very fast.}
248
procedure SetPixel32(const Image: TImageData; X, Y: LongInt; const Color: TColor32Rec);
249
{ Function for getting pixel colors. Native pixel is read from Image and
250
  then translated to FP ARGB. Works for all image formats (except special)
251
  so it is not very fast.}
252
function GetPixelFP(const Image: TImageData; X, Y: LongInt): TColorFPRec;
253
{ Procedure for setting pixel colors. Input FP ARGB color is translated to
254
  native format and then written to Image. Works for all image formats (except special)
255
  so it is not very fast.}
256
procedure SetPixelFP(const Image: TImageData; X, Y: LongInt; const Color: TColorFPRec); 
257
258
{ Palette Functions }
259
260
{ Allocates new palette with Entries ARGB color entries.}
261
procedure NewPalette(Entries: LongInt; var Pal: PPalette32);
262
{ Frees given palette.}
263
procedure FreePalette(var Pal: PPalette32);
264
{ Copies Count palette entries from SrcPal starting at index SrcIdx to
265
  DstPal at index DstPal.}
266
procedure CopyPalette(SrcPal, DstPal: PPalette32; SrcIdx, DstIdx, Count: LongInt);
267
{ Returns index of color in palette or index of nearest color if exact match
268
  is not found. Pal must have at least Entries color entries.}
269
function FindColor(Pal: PPalette32; Entries: LongInt; Color: TColor32): LongInt;
270
{ Creates grayscale palette where each color channel has the same value.
271
  Pal must have at least Entries color entries.}
272
procedure FillGrayscalePalette(Pal: PPalette32; Entries: LongInt);
273
{ Creates palette with given bitcount for each channel.
274
  2^(RBits + GBits + BBits) should be equl to Entries. Examples:
275
  (3, 3, 2) will create palette with all possible colors of R3G3B2 format
276
  and (8, 0, 0) will create palette with 256 shades of red.
277
  Pal must be allocated to at least Entries * SizeOf(TColor32Rec) bytes.}
278
procedure FillCustomPalette(Pal: PPalette32; Entries: LongInt; RBits, GBits,
279
  BBits: Byte; Alpha: Byte = $FF);
280
{ Swaps SrcChannel and DstChannel color or alpha channels of palette.
281
  Use ChannelRed, ChannelBlue, ChannelGreen, ChannelAlpha constants to
282
  identify channels. Pal must be allocated to at least
283
  Entries * SizeOf(TColor32Rec) bytes.}
284
procedure SwapChannelsOfPalette(Pal: PPalette32; Entries, SrcChannel,
285
  DstChannel: LongInt);
286
287
{ Options Functions }
288
289
{ Sets value of integer option specified by OptionId parameter.
290
  Option Ids are constans starting ImagingXXX.}
291
function SetOption(OptionId, Value: LongInt): Boolean;
292
{ Returns value of integer option specified by OptionId parameter. If OptionId is
293
  invalid, InvalidOption is returned. Option Ids are constans
294
  starting ImagingXXX.}
295
function GetOption(OptionId: LongInt): LongInt;
296
{ Pushes current values of all options on the stack. Returns True
297
  if successfull (max stack depth is 8 now). }
298
function PushOptions: Boolean;
299
{ Pops back values of all options from the top of the stack. Returns True
300
  if successfull (max stack depth is 8 now). }
301
function PopOptions: Boolean;
302
303
{ Image Format Functions }
304
305
{ Returns short information about given image format.}
306
function GetImageFormatInfo(Format: TImageFormat; var Info: TImageFormatInfo): Boolean;
307
{ Returns size in bytes of Width x Height area of pixels. Works for all formats.}
308
function GetPixelsSize(Format: TImageFormat; Width, Height: LongInt): LongInt;
309
310
{ IO Functions }
311
312
{ User can set his own file IO functions used when loading from/saving to
313
  files by this function.}
314
procedure SetUserFileIO(OpenReadProc: TOpenReadProc; OpenWriteProc:
315
  TOpenWriteProc; CloseProc: TCloseProc; EofProc: TEofProc; SeekProc:
316
  TSeekProc; TellProc: TTellProc; ReadProc: TReadProc; WriteProc: TWriteProc);
317
{ Sets file IO functions to Imaging default.}
318
procedure ResetFileIO;
319
320
321
{ ------------------------------------------------------------------------
322
                           Other Imaging Stuff
323
  ------------------------------------------------------------------------}
324
325
type
326
  { Set of TImageFormat enum.}
327
  TImageFormats = set of TImageFormat;
328
329
  { Record containg set of IO functions internaly used by image loaders/savers.}
330
  TIOFunctions = record
331
    OpenRead: TOpenReadProc;
332
    OpenWrite: TOpenWriteProc;
333
    Close: TCloseProc;
334
    Eof: TEofProc;
335
    Seek: TSeekProc;
336
    Tell: TTellProc;
337
    Read: TReadProc;
338
    Write: TWriteProc;
339
  end;
340
  PIOFunctions = ^TIOFunctions;
341
342
  { Base class for various image file format loaders/savers which
343
    descend from this class. If you want to add support for new image file
344
    format the best way is probably to look at TImageFileFormat descendants'
345
    implementations that are already part of Imaging.}
346
  {$TYPEINFO ON}
347
  TImageFileFormat = class(TObject)
348
  private
349
    FExtensions: TStringList;
350
    FMasks: TStringList;
351
    { Does various checks and actions before LoadData method is called.}
352
    function PrepareLoad(Handle: TImagingHandle; var Images: TDynImageDataArray;
353
      OnlyFirstFrame: Boolean): Boolean;
354
    { Processes some actions according to result of LoadData.}
355
    function PostLoadCheck(var Images: TDynImageDataArray; LoadResult: Boolean): Boolean;
356
    { Helper function to be called in SaveData methods of descendants (ensures proper
357
      index and sets FFirstIdx and FLastIdx for multi-images).}
358
    function PrepareSave(Handle: TImagingHandle; const Images: TDynImageDataArray;
359
      var Index: LongInt): Boolean;
360
  protected
361
    FName: string;
362
    FCanLoad: Boolean;
363
    FCanSave: Boolean;
364
    FIsMultiImageFormat: Boolean;
365
    FSupportedFormats: TImageFormats;
366
    FFirstIdx, FLastIdx: LongInt;
367
    { Defines filename masks for this image file format. AMasks should be
368
      in format '*.ext1,*.ext2,umajo.*'.}
369
    procedure AddMasks(const AMasks: string);
370
    function GetFormatInfo(Format: TImageFormat): TImageFormatInfo;
371
    { Returns set of TImageData formats that can be saved in this file format
372
      without need for conversion.}
373
    function GetSupportedFormats: TImageFormats; virtual;
374
    { Method which must be overrided in descendants if they' are be capable
375
      of loading images. Images are already freed and length is set to zero
376
      whenever this method gets called. Also Handle is assured to be valid
377
      and contains data that passed TestFormat method's check.}
378
    function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
379
      OnlyFirstFrame: Boolean): Boolean; virtual;
380
    { Method which must be overrided in descendants if they are be capable
381
      of saving images. Images are checked to have length >0 and
382
      that they contain valid images. For single-image file formats
383
      Index contain valid index to Images array (to image which should be saved).
384
      Multi-image formats should use FFirstIdx and FLastIdx fields to
385
      to get all images that are to be saved.}
386
    function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
387
      Index: LongInt): Boolean; virtual;
388
    { This method is called internaly by MakeCompatible when input image
389
      is in format not supported by this file format. Image is clone of
390
      MakeCompatible's input and Info is its extended format info.}
391
    procedure ConvertToSupported(var Image: TImageData;
392
      const Info: TImageFormatInfo); virtual;
393
    { Returns True if given image is supported for saving by this file format.
394
      Most file formats don't need to override this method. It checks
395
      (in this base class) if Image's format is in SupportedFromats set.
396
      But you may override it if you want further checks
397
      (proper widht and height for example).}
398
    function IsSupported(const Image: TImageData): Boolean; virtual;
399
  public
400
    constructor Create; virtual;
401
    destructor Destroy; override;
402
403
    { Loads images from file source.}
404
    function LoadFromFile(const FileName: string; var Images: TDynImageDataArray;
405
      OnlyFirstLevel: Boolean = False): Boolean;
406
    { Loads images from stream source.}
407
    function LoadFromStream(Stream: TStream; var Images: TDynImageDataArray;
408
      OnlyFirstLevel: Boolean = False): Boolean;
409
    { Loads images from memory source.}
410
    function LoadFromMemory(Data: Pointer; Size: LongInt;
411
      var Images: TDynImageDataArray; OnlyFirstLevel: Boolean = False): Boolean;
412
413
    { Saves images to file. If format supports only single level images and
414
      there are multiple images to be saved, they are saved as sequence of
415
      independent images (for example SaveToFile saves sequence of
416
      files img000.jpg, img001.jpg ....).}
417
    function SaveToFile(const FileName: string; const Images: TDynImageDataArray;
418
      OnlyFirstLevel: Boolean = False): Boolean;
419
    { Saves images to stream. If format supports only single level images and
420
      there are multiple images to be saved, they are saved as sequence of
421
      independent images.}
422
    function SaveToStream(Stream: TStream; const Images: TDynImageDataArray;
423
      OnlyFirstLevel: Boolean = False): Boolean;
424
    { Saves images to memory. If format supports only single level images and
425
      there are multiple images to be saved, they are saved as sequence of
426
      independent images. Data must be already allocated and their size passed
427
      as Size parameter, number of written bytes is then returned in the same
428
      parameter.}
429
    function SaveToMemory(Data: Pointer; var Size: LongInt;
430
      const Images: TDynImageDataArray; OnlyFirstLevel: Boolean = False): Boolean;
431
432
    { Makes Image compatible with this file format (that means it is in one
433
      of data formats in Supported formats set). If input is already
434
      in supported format then Compatible just use value from input
435
      (Compatible := Image) so must not free it after you are done with it
436
      (image bits pointer points to input image's bits).
437
      If input is not in supported format then it is cloned to Compatible
438
      and concerted to one of supported formats (which one dependeds on
439
      this file format). If image is cloned MustBeFreed is set to True
440
      to indicated that you must free Compatible after you are done with it.}
441
    function MakeCompatible(const Image: TImageData; var Compatible: TImageData;
442
      out MustBeFreed: Boolean): Boolean;   
443
    { Returns True if data located in source identified by Handle
444
      represent valid image in current format.}
445
    function TestFormat(Handle: TImagingHandle): Boolean; virtual;
446
    { Resturns True if the given FileName matches filter for this file format.
447
      For most formats it just checks filename extensions.
448
      It uses filename masks in from Masks property so it can recognize
449
      filenames like this 'umajoXXXumajo.j0j' if one of themasks is
450
      'umajo*umajo.j?j'.}
451
    function TestFileName(const FileName: string): Boolean;
452
    { Descendants use this method to check if their options (registered with
453
      constant Ids for SetOption/GetOption interface or accessible as properties
454
      of descendants) have valid values and make necessary changes.}
455
    procedure CheckOptionsValidity; virtual;
456
457
    { Description of this format.}
458
    property Name: string read FName;
459
    { Indicates whether images in this format can be loaded.}
460
    property CanLoad: Boolean read FCanLoad;
461
    { Indicates whether images in this format can be saved.}
462
    property CanSave: Boolean read FCanSave;
463
    { Indicates whether images in this format can contain multiple image levels.}
464
    property IsMultiImageFormat: Boolean read FIsMultiImageFormat;
465
    { List of filename extensions for this format.}
466
    property Extensions: TStringList read FExtensions;
467
    { List of filename mask that are used to associate filenames
468
      with TImageFileFormat descendants. Typical mask looks like
469
      '*.bmp' or 'texture.*' (supports file formats which use filename instead
470
      of extension to identify image files).}
471
    property Masks: TStringList read FMasks;
472
    { Set of TImageFormats supported by saving functions of this format. Images
473
      can be saved only in one those formats.}
474
    property SupportedFormats: TImageFormats read GetSupportedFormats;
475
  end;
476
  {$TYPEINFO OFF}
477
478
  { Class reference for TImageFileFormat class}
479
  TImageFileFormatClass = class of TImageFileFormat;
480
481
{ Returns symbolic name of given format.}
482
function GetFormatName(Format: TImageFormat): string;
483
{ Returns string with information about given Image.}
484
function ImageToStr(const Image: TImageData): string;
485
{ Returns Imaging version string in format 'Major.Minor.Patch'.}
486
function GetVersionStr: string;
487
{ If Condition is True then TruePart is retured, otherwise FalsePart is returned.}
488
function IffFormat(Condition: Boolean; const TruePart, FalsePart: TImageFormat): TImageFormat;
489
{ Registers new image loader/saver so it can be used by LoadFrom/SaveTo
490
  functions.}
491
procedure RegisterImageFileFormat(AClass: TImageFileFormatClass);
492
{ Registers new option so it can be used by SetOption and GetOption functions.
493
  Returns True if registration was succesful - that is Id is valid and is
494
  not already taken by another option.}
495
function RegisterOption(OptionId: LongInt; Variable: PLongInt): Boolean;
496
{ Returns image format loader/saver according to given extension
497
  or nil if not found.}
498
function FindImageFileFormatByExt(const Ext: string): TImageFileFormat;
499
{ Returns image format loader/saver according to given filename
500
  or nil if not found.}
501
function FindImageFileFormatByName(const FileName: string): TImageFileFormat;
502
{ Returns image format loader/saver based on its class
503
  or nil if not found or not registered.}
504
function FindImageFileFormatByClass(AClass: TImageFileFormatClass): TImageFileFormat;
505
{ Returns number of registered image file format loaders/saver.}
506
function GetFileFormatCount: LongInt;
507
{ Returns image file format loader/saver at given index. Index must be
508
  in range [0..GetFileFormatCount - 1] otherwise nil is returned.}
509
function GetFileFormatAtIndex(Index: LongInt): TImageFileFormat;
510
{ Returns filter string for usage with open and save picture dialogs
511
  which contains all registered image file formats.
512
  Set OpenFileFilter to True if you want filter for open dialog
513
  and to False if you want save dialog filter (formats that cannot save to files
514
  are not added then).
515
  For open dialog filter for all known graphic files
516
  (like All(*.jpg;*.png;....) is added too at the first index.}
517
function GetImageFileFormatsFilter(OpenFileFilter: Boolean): string;
518
{ Returns file extension (without dot) of image format selected
519
  by given filter index. Used filter string is defined by GetImageFileFormatsFilter
520
  function. This function can be used with save dialogs (with filters created
521
  by GetImageFileFormatsFilter) to get the extension of file format selected
522
  in dialog quickly. Index is in range 1..N (as FilterIndex property
523
  of TOpenDialog/TSaveDialog)}
524
function GetFilterIndexExtension(Index: LongInt; OpenFileFilter: Boolean): string;
525
{ Returns filter index of image file format of file specified by FileName. Used filter
526
  string is defined by GetImageFileFormatsFilter function.
527
  Returned index is in range 1..N (as FilterIndex property of TOpenDialog/TSaveDialog)}
528
function GetFileNameFilterIndex(const FileName: string; OpenFileFilter: Boolean): LongInt;
529
{ Returns current IO functions.}
530
function GetIO: TIOFunctions;
531
{ Raises EImagingError with given message.}
532
procedure RaiseImaging(const Msg: string; const Args: array of const);
533
534
implementation
535
536
uses
537
{$IFDEF LINK_BITMAP}
538
  ImagingBitmap,
539
{$ENDIF}
540
{$IFDEF LINK_JPEG}
541
  ImagingJpeg,
542
{$ENDIF}
543
{$IF Defined(LINK_PNG) or Defined(LINK_MNG) or Defined(LINK_JNG)}
544
  ImagingNetworkGraphics,
545
{$IFEND}
546
{$IFDEF LINK_GIF}
547
  ImagingGif,
548
{$ENDIF}
549
{$IFDEF LINK_DDS}
550
  ImagingDds,
551
{$ENDIF}
552
{$IFDEF LINK_TARGA}
553
  ImagingTarga,
554
{$ENDIF}
555
{$IFDEF LINK_PNM}
556
  ImagingPortableMaps,
557
{$ENDIF}
558
{$IFDEF LINK_EXTRAS}
559
  ImagingExtras,
560
{$ENDIF}
561
  ImagingFormats, ImagingUtility, ImagingIO;
562
563
resourcestring
564
  SImagingTitle = 'Vampyre Imaging Library';
565
  SExceptMsg = 'Exception Message';
566
  SAllFilter = 'All Images';
567
  SUnknownFormat = 'Unknown and unsupported format';
568
  SErrorFreeImage = 'Error while freeing image. %s';
569
  SErrorCloneImage = 'Error while cloning image. %s';
570
  SErrorFlipImage = 'Error while flipping image. %s';
571
  SErrorMirrorImage = 'Error while mirroring image. %s';
572
  SErrorResizeImage = 'Error while resizing image.  %s';
573
  SErrorSwapImage = 'Error while swapping channels of image. %s';
574
  SFileFormatCanNotLoad = 'Image Format "%s" does not support loading images.';
575
  SFileFormatCanNotSave = 'Image Format "%s" does not support saving images.';
576
  SErrorNewImage = 'Error while creating image data with params: Width=%d ' +
577
    'Height=%d Format=%s.';
578
  SErrorConvertImage = 'Error while converting image to format "%s". %s';
579
  SImageInfo = 'Image @%p info: Width = %dpx, Height = %dpx, ' +
580
    'Format = %s, Size = %.0n %s, Bits @%p, Palette @%p.';
581
  SImageInfoInvalid = 'Access violation encountered when getting info on ' +
582
    'image at address %p.';
583
  SFileNotValid = 'File "%s" is not valid image in "%s" format.';
584
  SStreamNotValid = 'Stream %p does not contain valid image in "%s" format.';
585
  SMemoryNotValid = 'Memory %p (%d Bytes) does not contain valid image ' +
586
    'in "%s" format.';
587
  SErrorLoadingFile = 'Error while loading images from file "%s" (file format: %s).';
588
  SErrorLoadingStream = 'Error while loading images from stream %p (file format: %s).';
589
  SErrorLoadingMemory = 'Error while loading images from memory %p (%d Bytes) (file format: %s).';
590
  SErrorSavingFile = 'Error while saving images to file "%s" (file format: %s).';
591
  SErrorSavingStream = 'Error while saving images to stream %p (file format: %s).';
592
  SErrorSavingMemory = 'Error while saving images to memory %p (%d Bytes) (file format: %s).';
593
  SErrorFindColor = 'Error while finding color in palette @%p with %d entries.';
594
  SErrorGrayscalePalette = 'Error while filling grayscale palette @%p with %d entries.';
595
  SErrorCustomPalette = 'Error while filling custom palette @%p with %d entries.';
596
  SErrorSwapPalette = 'Error while swapping channels of palette @%p with %d entries.';
597
  SErrorReduceColors = 'Error while reducing number of colors of image to %d. %s';
598
  SErrorGenerateMipMaps = 'Error while generating %d mipmap levels for image %s';
599
  SImagesNotValid = 'One or more images are not valid.';
600
  SErrorCopyRect = 'Error while copying rect from image %s to image %s.';
601
  SErrorMapImage = 'Error while mapping image %s to palette.';
602
  SErrorFillRect = 'Error while filling rectangle X:%d Y:%d W:%d H:%d in image %s';
603
  SErrorSplitImage = 'Error while splitting image %s to %dx%d sized chunks.';
604
  SErrorMakePaletteForImages = 'Error while making %d color palette for %d images.';
605
  SErrorNewPalette = 'Error while creating new palette with %d entries';
606
  SErrorFreePalette = 'Error while freeing palette @%p';
607
  SErrorCopyPalette = 'Error while copying %d entries from palette @%p to @%p';
608
  SErrorReplaceColor = 'Error while replacing colors in rectangle X:%d Y:%d W:%d H:%d of image %s';
609
  SErrorRotateImage = 'Error while rotating image %s by %d degrees';
610
  SErrorStretchRect = 'Error while stretching rect from image %s to image %s.';
611
612
const
613
  // initial size of array with options information
614
  InitialOptions = 256;
615
  // max depth of the option stack
616
  OptionStackDepth = 8;
617
  // do not change the default format now, its too late
618
  DefaultImageFormat: TImageFormat = ifA8R8G8B8;
619
620
type
621
  TOptionArray = array of PLongInt;
622
  TOptionValueArray = array of LongInt;
623
624
  TOptionStack = class(TObject)
625
  private
626
    FStack: array[0..OptionStackDepth - 1] of TOptionValueArray;
627
    FPosition: LongInt;
628
  public
629
    constructor Create;
630
    destructor Destroy; override;
631
    function Push: Boolean;
632
    function Pop: Boolean;
633
  end;
634
635
var
636
  // currently set IO functions
637
  IO: TIOFunctions;
638
  // list with all registered TImageFileFormat classes
639
  ImageFileFormats: TList = nil;
640
  // array with registered options (pointers to their values)
641
  Options: TOptionArray = nil;
642
  // array containing addional infomation about every image format
643
  ImageFormatInfos: TImageFormatInfoArray;
644
  // stack used by PushOptions/PopOtions functions
645
  OptionStack: TOptionStack = nil;
646
var
647
  // variable for ImagingColorReduction option
648
  ColorReductionMask: LongInt = $FF;
649
  // variable for ImagingLoadOverrideFormat option
650
  LoadOverrideFormat: TImageFormat = ifUnknown;
651
  // variable for ImagingSaveOverrideFormat option
652
  SaveOverrideFormat: TImageFormat = ifUnknown;
653
  // variable for ImagingSaveOverrideFormat option
654
  MipMapFilter: TSamplingFilter = sfLinear;
655
656
657
{ Internal unit functions }
658
659
{ Modifies option value to be in the allowed range. Works only
660
  for options registered in this unit.}
661
function CheckOptionValue(OptionId, Value: LongInt): LongInt; forward;
662
{ Sets IO functions to file IO.}
663
procedure SetFileIO; forward;
664
{ Sets IO functions to stream IO.}
665
procedure SetStreamIO; forward;
666
{ Sets IO functions to memory IO.}
667
procedure SetMemoryIO; forward;
668
{ Inits image format infos array.}
669
procedure InitImageFormats; forward;
670
{ Freew image format infos array.}
671
procedure FreeImageFileFormats; forward;
672
{ Creates options array and stack.}
673
procedure InitOptions; forward;
674
{ Frees options array and stack.}
675
procedure FreeOptions; forward;
676
677
{$IFDEF USE_INLINE}
678
{ Those inline functions are copied here from ImagingFormats
679
  because Delphi 9/10 cannot inline them if they are declared in
680
  circularly dependent units.}
681
682
procedure CopyPixel(Src, Dest: Pointer; BytesPerPixel: LongInt); inline;
683
begin
684
  case BytesPerPixel of
685
    1: PByte(Dest)^ := PByte(Src)^;
686
    2: PWord(Dest)^ := PWord(Src)^;
687
    3: PColor24Rec(Dest)^ := PColor24Rec(Src)^;
688
    4: PLongWord(Dest)^ := PLongWord(Src)^;
689
    6: PColor48Rec(Dest)^ := PColor48Rec(Src)^;
690
    8: PInt64(Dest)^ := PInt64(Src)^;
691
    16: PColorFPRec(Dest)^ := PColorFPRec(Src)^;
692
  end;
693
end;
694
695
function ComparePixels(PixelA, PixelB: Pointer; BytesPerPixel: LongInt): Boolean; inline;
696
begin
697
  case BytesPerPixel of
698
    1: Result := PByte(PixelA)^ = PByte(PixelB)^;
699
    2: Result := PWord(PixelA)^ = PWord(PixelB)^;
700
    3: Result := (PWord(PixelA)^ = PWord(PixelB)^) and
701
         (PColor24Rec(PixelA).R = PColor24Rec(PixelB).R);
702
    4: Result := PLongWord(PixelA)^ = PLongWord(PixelB)^;
703
    6: Result := (PLongWord(PixelA)^ = PLongWord(PixelB)^) and
704
         (PColor48Rec(PixelA).R = PColor48Rec(PixelB).R);
705
    8: Result := PInt64(PixelA)^ = PInt64(PixelB)^;
706
    16: Result := (PFloatHelper(PixelA).Data2 = PFloatHelper(PixelB).Data2) and
707
          (PFloatHelper(PixelA).Data1 = PFloatHelper(PixelB).Data1);
708
  else
709
    Result := False;
710
  end;
711
end;
712
{$ENDIF}
713
714
{ ------------------------------------------------------------------------
715
                       Low Level Interface Functions
716
  ------------------------------------------------------------------------}
717
718
{ General Functions }
719
720
procedure InitImage(var Image: TImageData);
721
begin
722
  FillChar(Image, SizeOf(Image), 0);
723
end;
724
725
function NewImage(Width, Height: LongInt; Format: TImageFormat; var Image:
726
  TImageData): Boolean;
727
var
728
  FInfo: PImageFormatInfo;
729
begin
730
  Assert((Width >= 0) and (Height >= 0));
731
  Assert(IsImageFormatValid(Format));
732
  Result := False;
733
  FreeImage(Image);
734
  try
735
    Image.Width := Width;
736
    Image.Height := Height;
737
    // Select default data format if selected
738
    if (Format = ifDefault)  then
739
      Image.Format := DefaultImageFormat
740
    else
741
      Image.Format := Format;
742
    // Get extended format info
743
    FInfo := ImageFormatInfos[Image.Format];
744
    if FInfo = nil then
745
    begin
746
      InitImage(Image);
747
      Exit;
748
    end;
749
    // Check image dimensions and calculate its size in bytes
750
    FInfo.CheckDimensions(FInfo.Format, Image.Width, Image.Height);
751
    Image.Size := FInfo.GetPixelsSize(FInfo.Format, Image.Width, Image.Height);
752
    if Image.Size = 0 then
753
    begin
754
      InitImage(Image);
755
      Exit;
756
    end;
757
    // Image bits are allocated and set to zeroes
758
    GetMem(Image.Bits, Image.Size);
759
    FillChar(Image.Bits^, Image.Size, 0);
760
    // Palette is allocated and set to zeroes
761
    if FInfo.PaletteEntries > 0 then
762
    begin
763
      GetMem(Image.Palette, FInfo.PaletteEntries * SizeOf(TColor32Rec));
764
      FillChar(Image.Palette^, FInfo.PaletteEntries * SizeOf(TColor32Rec), 0);
765
    end;
766
    Result := TestImage(Image);
767
  except
768
    RaiseImaging(SErrorNewImage, [Width, Height, GetFormatName(Format)]);
769
  end;
770
end;
771
772
function TestImage(const Image: TImageData): Boolean;
773
begin
774
  try
775
    Result := (LongInt(Image.Format) >= LongInt(Low(TImageFormat))) and
776
      (LongInt(Image.Format) <= LongInt(High(TImageFormat))) and
777
      (ImageFormatInfos[Image.Format] <> nil) and
778
      (Assigned(ImageFormatInfos[Image.Format].GetPixelsSize) and
779
      (ImageFormatInfos[Image.Format].GetPixelsSize(Image.Format,
780
      Image.Width, Image.Height) = Image.Size));
781
  except
782
    // Possible int overflows or other errors 
783
    Result := False;
784
  end;
785
end;
786
787
procedure FreeImage(var Image: TImageData);
788
begin
789
  try
790
    if TestImage(Image) then
791
    begin
792
      FreeMemNil(Image.Bits);
793
      FreeMemNil(Image.Palette);
794
    end;
795
    InitImage(Image);
796
  except
797
    RaiseImaging(SErrorFreeImage, [ImageToStr(Image)]);
798
  end;
799
end;
800
801
procedure FreeImagesInArray(var Images: TDynImageDataArray);
802
var
803
  I: LongInt;
804
begin
805
  if Length(Images) > 0 then
806
  begin
807
    for I := 0 to Length(Images) - 1 do
808
      FreeImage(Images[I]);
809
    SetLength(Images, 0);
810
  end;
811
end;
812
813
function TestImagesInArray(const Images: TDynImageDataArray): Boolean;
814
var
815
  I: LongInt;
816
begin
817
  if Length(Images) > 0 then
818
  begin
819
    Result := True;
820
    for I := 0 to Length(Images) - 1 do
821
    begin
822
      Result := Result and TestImage(Images[I]);
823
      if not Result then
824
        Break;
825
    end;
826
  end
827
  else
828
    Result := False;
829
end;
830
831
function DetermineFileFormat(const FileName: string): string;
832
var
833
  I: LongInt;
834
  Fmt: TImageFileFormat;
835
  Handle: TImagingHandle;
836
begin
837
  Assert(FileName <> '');
838
  Result := '';
839
  SetFileIO;
840
  try
841
    Handle := IO.OpenRead(PChar(FileName));
842
    try
843
      // First file format according to FileName and test if the data in
844
      // file is really in that format
845
      for I := 0 to ImageFileFormats.Count - 1 do
846
      begin
847
        Fmt := TImageFileFormat(ImageFileFormats[I]);
848
        if Fmt.TestFileName(FileName) and Fmt.TestFormat(Handle) then
849
        begin
850
          Result := Fmt.Extensions[0];
851
          Exit;
852
        end;
853
      end;
854
      // No file format was found with filename search so try data-based search
855
      for I := 0 to ImageFileFormats.Count - 1 do
856
      begin
857
        Fmt := TImageFileFormat(ImageFileFormats[I]);
858
        if Fmt.TestFormat(Handle) then
859
        begin
860
          Result := Fmt.Extensions[0];
861
          Exit;
862
        end;
863
      end;
864
    finally
865
      IO.Close(Handle);
866
    end;
867
  except
868
    Result := '';
869
  end;
870
end;
871
872
function DetermineStreamFormat(Stream: TStream): string;
873
var
874
  I: LongInt;
875
  Fmt: TImageFileFormat;
876
  Handle: TImagingHandle;
877
begin
878
  Assert(Stream <> nil);
879
  Result := '';
880
  SetStreamIO;
881
  try
882
    Handle := IO.OpenRead(Pointer(Stream));
883
    try
884
      for I := 0 to ImageFileFormats.Count - 1 do
885
      begin
886
        Fmt := TImageFileFormat(ImageFileFormats[I]);
887
        if Fmt.TestFormat(Handle) then
888
        begin
889
          Result := Fmt.Extensions[0];
890
          Exit;
891
        end;
892
      end;
893
    finally
894
      IO.Close(Handle);
895
    end;
896
  except
897
    Result := '';
898
  end;
899
end;
900
901
function DetermineMemoryFormat(Data: Pointer; Size: LongInt): string;
902
var
903
  I: LongInt;
904
  Fmt: TImageFileFormat;
905
  Handle: TImagingHandle;
906
  IORec: TMemoryIORec;
907
begin
908
  Assert((Data <> nil) and (Size > 0));
909
  Result := '';
910
  SetMemoryIO;
911
  IORec.Data := Data;
912
  IORec.Position := 0;
913
  IORec.Size := Size;
914
  try
915
    Handle := IO.OpenRead(@IORec);
916
    try
917
      for I := 0 to ImageFileFormats.Count - 1 do
918
      begin
919
        Fmt := TImageFileFormat(ImageFileFormats[I]);
920
        if Fmt.TestFormat(Handle) then
921
        begin
922
          Result := Fmt.Extensions[0];
923
          Exit;
924
        end;
925
      end;
926
    finally
927
      IO.Close(Handle);
928
    end;
929
  except
930
    Result := '';
931
  end;
932
end;
933
934
function IsFileFormatSupported(const FileName: string): Boolean;
935
begin
936
  Result := FindImageFileFormatByName(FileName) <> nil;
937
end;
938
939
function EnumFileFormats(var Index: LongInt; var Name, DefaultExt, Masks: string;
940
  var CanSaveImages, IsMultiImageFormat: Boolean): Boolean;
941
var
942
  FileFmt: TImageFileFormat;
943
begin
944
  FileFmt := GetFileFormatAtIndex(Index);
945
  Result := FileFmt <> nil;
946
  if Result then
947
  begin
948
    Name := FileFmt.Name;
949
    DefaultExt := FileFmt.Extensions[0];
950
    Masks := FileFmt.Masks.DelimitedText; 
951
    CanSaveImages := FileFmt.CanSave;
952
    IsMultiImageFormat := FileFmt.IsMultiImageFormat;
953
    Inc(Index);
954
  end
955
  else
956
  begin
957
    Name := '';
958
    DefaultExt := '';
959
    Masks := '';
960
    CanSaveImages := False;
961
    IsMultiImageFormat := False;
962
  end;
963
end;
964
965
{ Loading Functions }
966
967
function LoadImageFromFile(const FileName: string; var Image: TImageData):
968
  Boolean;
969
var
970
  Format: TImageFileFormat;
971
  IArray: TDynImageDataArray;
972
  I: LongInt;
973
begin
974
  Assert(FileName <> '');
975
  Result := False;
976
  Format := FindImageFileFormatByExt(DetermineFileFormat(FileName));
977
  if Format <> nil then
978
  begin
979
    FreeImage(Image);
980
    Result := Format.LoadFromFile(FileName, IArray, True);
981
    if Result and (Length(IArray) > 0) then
982
    begin
983
      Image := IArray[0];
984
      for I := 1 to Length(IArray) - 1 do
985
        FreeImage(IArray[I]);
986
    end
987
    else
988
      Result := False;
989
  end;
990
end;
991
992
function LoadImageFromStream(Stream: TStream; var Image: TImageData): Boolean;
993
var
994
  Format: TImageFileFormat;
995
  IArray: TDynImageDataArray;
996
  I: LongInt;
997
begin
998
  Assert(Stream <> nil);
999
  Result := False;
1000
  Format := FindImageFileFormatByExt(DetermineStreamFormat(Stream));
1001
  if Format <> nil then
1002
  begin
1003
    FreeImage(Image);
1004
    Result := Format.LoadFromStream(Stream, IArray, True);
1005
    if Result and (Length(IArray) > 0) then
1006
    begin
1007
      Image := IArray[0];
1008
      for I := 1 to Length(IArray) - 1 do
1009
        FreeImage(IArray[I]);
1010
    end
1011
    else
1012
      Result := False;
1013
  end;
1014
end;
1015
1016
function LoadImageFromMemory(Data: Pointer; Size: LongInt; var Image: TImageData): Boolean;
1017
var
1018
  Format: TImageFileFormat;
1019
  IArray: TDynImageDataArray;
1020
  I: LongInt;
1021
begin
1022
  Assert((Data <> nil) and (Size > 0));
1023
  Result := False;
1024
  Format := FindImageFileFormatByExt(DetermineMemoryFormat(Data, Size));
1025
  if Format <> nil then
1026
  begin
1027
    FreeImage(Image);
1028
    Result := Format.LoadFromMemory(Data, Size, IArray, True);
1029
    if Result and (Length(IArray) > 0) then
1030
    begin
1031
      Image := IArray[0];
1032
      for I := 1 to Length(IArray) - 1 do
1033
        FreeImage(IArray[I]);
1034
    end
1035
    else
1036
      Result := False;
1037
  end;
1038
end;
1039
1040
function LoadMultiImageFromFile(const FileName: string; var Images:
1041
  TDynImageDataArray): Boolean;
1042
var
1043
  Format: TImageFileFormat;
1044
begin
1045
  Assert(FileName <> '');
1046
  Result := False;
1047
  Format := FindImageFileFormatByExt(DetermineFileFormat(FileName));
1048
  if Format <> nil then
1049
  begin
1050
    FreeImagesInArray(Images);
1051
    Result := Format.LoadFromFile(FileName, Images);
1052
  end;
1053
end;
1054
1055
function LoadMultiImageFromStream(Stream: TStream; var Images: TDynImageDataArray): Boolean;
1056
var
1057
  Format: TImageFileFormat;
1058
begin
1059
  Assert(Stream <> nil);
1060
  Result := False;
1061
  Format := FindImageFileFormatByExt(DetermineStreamFormat(Stream));
1062
  if Format <> nil then
1063
  begin
1064
    FreeImagesInArray(Images);
1065
    Result := Format.LoadFromStream(Stream, Images);
1066
  end;
1067
end;
1068
1069
function LoadMultiImageFromMemory(Data: Pointer; Size: LongInt;
1070
  var Images: TDynImageDataArray): Boolean;
1071
var
1072
  Format: TImageFileFormat;
1073
begin
1074
  Assert((Data <> nil) and (Size > 0));
1075
  Result := False;
1076
  Format := FindImageFileFormatByExt(DetermineMemoryFormat(Data, Size));
1077
  if Format <> nil then
1078
  begin
1079
    FreeImagesInArray(Images);
1080
    Result := Format.LoadFromMemory(Data, Size, Images);
1081
  end;
1082
end;
1083
1084
{ Saving Functions }
1085
1086
function SaveImageToFile(const FileName: string; const Image: TImageData): Boolean;
1087
var
1088
  Format: TImageFileFormat;
1089
  IArray: TDynImageDataArray;
1090
begin
1091
  Assert(FileName <> '');
1092
  Result := False;
1093
  Format := FindImageFileFormatByName(FileName);
1094
  if Format <> nil then
1095
  begin
1096
    SetLength(IArray, 1);
1097
    IArray[0] := Image;
1098
    Result := Format.SaveToFile(FileName, IArray, True);
1099
  end;
1100
end;
1101
1102
function SaveImageToStream(const Ext: string; Stream: TStream;
1103
  const Image: TImageData): Boolean;
1104
var
1105
  Format: TImageFileFormat;
1106
  IArray: TDynImageDataArray;
1107
begin
1108
  Assert((Ext <> '') and (Stream <> nil));
1109
  Result := False;
1110
  Format := FindImageFileFormatByExt(Ext);
1111
  if Format <> nil then
1112
  begin
1113
    SetLength(IArray, 1);
1114
    IArray[0] := Image;
1115
    Result := Format.SaveToStream(Stream, IArray, True);
1116
  end;
1117
end;
1118
1119
function SaveImageToMemory(const Ext: string; Data: Pointer; var Size: LongInt;
1120
  const Image: TImageData): Boolean;
1121
var
1122
  Format: TImageFileFormat;
1123
  IArray: TDynImageDataArray;
1124
begin
1125
  Assert((Ext <> '') and (Data <> nil) and (Size > 0));
1126
  Result := False;
1127
  Format := FindImageFileFormatByExt(Ext);
1128
  if Format <> nil then
1129
  begin
1130
    SetLength(IArray, 1);
1131
    IArray[0] := Image;
1132
    Result := Format.SaveToMemory(Data, Size, IArray, True);
1133
  end;
1134
end;
1135
1136
function SaveMultiImageToFile(const FileName: string;
1137
  const Images: TDynImageDataArray): Boolean;
1138
var
1139
  Format: TImageFileFormat;
1140
begin
1141
  Assert(FileName <> '');
1142
  Result := False;
1143
  Format := FindImageFileFormatByName(FileName);
1144
  if Format <> nil then
1145
    Result := Format.SaveToFile(FileName, Images);
1146
end;
1147
1148
function SaveMultiImageToStream(const Ext: string; Stream: TStream;
1149
  const Images: TDynImageDataArray): Boolean;
1150
var
1151
  Format: TImageFileFormat;
1152
begin
1153
  Assert((Ext <> '') and (Stream <> nil));
1154
  Result := False;
1155
  Format := FindImageFileFormatByExt(Ext);
1156
  if Format <> nil then
1157
    Result := Format.SaveToStream(Stream, Images);
1158
end;
1159
1160
function SaveMultiImageToMemory(const Ext: string; Data: Pointer;
1161
  var Size: LongInt; const Images: TDynImageDataArray): Boolean;
1162
var
1163
  Format: TImageFileFormat;
1164
begin
1165
  Assert((Ext <> '') and (Data <> nil) and (Size > 0));
1166
  Result := False;
1167
  Format := FindImageFileFormatByExt(Ext);
1168
  if Format <> nil then
1169
    Result := Format.SaveToMemory(Data, Size, Images);
1170
end;
1171
1172
{ Manipulation Functions }
1173
1174
function CloneImage(const Image: TImageData; var Clone: TImageData): Boolean;
1175
var
1176
  Info: PImageFormatInfo;
1177
begin
1178
  Result := False;
1179
  if TestImage(Image) then
1180
  try
1181
    if TestImage(Clone) and (Image.Bits <> Clone.Bits) then
1182
      FreeImage(Clone)
1183
    else
1184
      InitImage(Clone);
1185
1186
    Info := ImageFormatInfos[Image.Format];
1187
    Clone.Width := Image.Width;
1188
    Clone.Height := Image.Height;
1189
    Clone.Format := Image.Format;
1190
    Clone.Size := Image.Size;
1191
1192
    if Info.PaletteEntries > 0 then
1193
    begin
1194
      GetMem(Clone.Palette, Info.PaletteEntries * SizeOf(TColor32Rec));
1195
      Move(Image.Palette^, Clone.Palette^, Info.PaletteEntries *
1196
        SizeOf(TColor32Rec));
1197
    end;
1198
1199
    GetMem(Clone.Bits, Clone.Size);
1200
    Move(Image.Bits^, Clone.Bits^, Clone.Size);
1201
    Result := True;
1202
  except
1203
    RaiseImaging(SErrorCloneImage, [ImageToStr(Image)]);
1204
  end;
1205
end;
1206
1207
function ConvertImage(var Image: TImageData; DestFormat: TImageFormat): Boolean;
1208
var
1209
  NewData: Pointer;
1210
  NewPal: PPalette32;
1211
  NewSize, NumPixels: LongInt;
1212
  SrcInfo, DstInfo: PImageFormatInfo;
1213
begin
1214
  Assert(IsImageFormatValid(DestFormat));
1215
  Result := False;
1216
  if TestImage(Image) then
1217
  with Image do
1218
  try
1219
    // If default format is set we use DefaultImageFormat
1220
    if DestFormat = ifDefault then
1221
      DestFormat := DefaultImageFormat;
1222
    SrcInfo := ImageFormatInfos[Format];
1223
    DstInfo := ImageFormatInfos[DestFormat];
1224
    if SrcInfo = DstInfo then
1225
    begin
1226
      // There is nothing to convert - src is alredy in dest format
1227
      Result := True;
1228
      Exit;
1229
    end;
1230
    // Exit Src or Dest format is invalid 
1231
    if (SrcInfo = nil) or (DstInfo = nil) then Exit;
1232
    // If dest format is just src with swapped channels we call
1233
    // SwapChannels instead
1234
    if (SrcInfo.RBSwapFormat = DestFormat) and
1235
      (DstInfo.RBSwapFormat = SrcInfo.Format) then
1236
    begin
1237
      Result := SwapChannels(Image, ChannelRed, ChannelBlue);
1238
      Image.Format := SrcInfo.RBSwapFormat;
1239
      Exit;
1240
    end;
1241
1242
    if (not SrcInfo.IsSpecial) and (not DstInfo.IsSpecial) then
1243
    begin
1244
      NumPixels := Width * Height;
1245
      NewSize := NumPixels * DstInfo.BytesPerPixel;
1246
      GetMem(NewData, NewSize);
1247
      FillChar(NewData^, NewSize, 0);
1248
      GetMem(NewPal, DstInfo.PaletteEntries * SizeOf(TColor32Rec));
1249
      FillChar(NewPal^, DstInfo.PaletteEntries * SizeOf(TColor32Rec), 0);
1250
1251
      if SrcInfo.IsIndexed then
1252
      begin
1253
        // Source: indexed format
1254
        if DstInfo.IsIndexed then
1255
          IndexToIndex(NumPixels, Bits, NewData, SrcInfo, DstInfo, Palette, NewPal)
1256
        else if DstInfo.HasGrayChannel then
1257
          IndexToGray(NumPixels, Bits, NewData, SrcInfo, DstInfo, Palette)
1258
        else if DstInfo.IsFloatingPoint then
1259
          IndexToFloat(NumPixels, Bits, NewData, SrcInfo, DstInfo, Palette)
1260
        else
1261
          IndexToChannel(NumPixels, Bits, NewData, SrcInfo, DstInfo, Palette);
1262
      end
1263
      else if SrcInfo.HasGrayChannel then
1264
      begin
1265
        // Source: grayscale format
1266
        if DstInfo.IsIndexed then
1267
          GrayToIndex(NumPixels, Bits, NewData, SrcInfo, DstInfo, NewPal)
1268
        else if DstInfo.HasGrayChannel then
1269
          GrayToGray(NumPixels, Bits, NewData, SrcInfo, DstInfo)
1270
        else if DstInfo.IsFloatingPoint then
1271
          GrayToFloat(NumPixels, Bits, NewData, SrcInfo, DstInfo)
1272
        else
1273
          GrayToChannel(NumPixels, Bits, NewData, SrcInfo, DstInfo);
1274
      end
1275
      else if SrcInfo.IsFloatingPoint then
1276
      begin
1277
        // Source: floating point format
1278
        if DstInfo.IsIndexed then
1279
          FloatToIndex(NumPixels, Bits, NewData, SrcInfo, DstInfo, NewPal)
1280
        else if DstInfo.HasGrayChannel then
1281
          FloatToGray(NumPixels, Bits, NewData, SrcInfo, DstInfo)
1282
        else if DstInfo.IsFloatingPoint then
1283
          FloatToFloat(NumPixels, Bits, NewData, SrcInfo, DstInfo)
1284
        else
1285
          FloatToChannel(NumPixels, Bits, NewData, SrcInfo, DstInfo);
1286
      end
1287
      else
1288
      begin
1289
        // Source: standard multi channel image
1290
        if DstInfo.IsIndexed then
1291
          ChannelToIndex(NumPixels, Bits, NewData, SrcInfo, DstInfo, NewPal)
1292
        else if DstInfo.HasGrayChannel then
1293
          ChannelToGray(NumPixels, Bits, NewData, SrcInfo, DstInfo)
1294
        else if DstInfo.IsFloatingPoint then
1295
          ChannelToFloat(NumPixels, Bits, NewData, SrcInfo, DstInfo)
1296
        else
1297
          ChannelToChannel(NumPixels, Bits, NewData, SrcInfo, DstInfo);
1298
      end;
1299
1300
      FreeMemNil(Bits);
1301
      FreeMemNil(Palette);
1302
      Format := DestFormat;
1303
      Bits := NewData;
1304
      Size := NewSize;
1305
      Palette := NewPal;
1306
    end
1307
    else
1308
      ConvertSpecial(Image, SrcInfo, DstInfo);
1309
1310
    Assert(SrcInfo.Format <> Image.Format);
1311
1312
    Result := True;
1313
  except
1314
    RaiseImaging(SErrorConvertImage, [GetFormatName(DestFormat), ImageToStr(Image)]);
1315
  end;
1316
end;
1317
1318
function FlipImage(var Image: TImageData): Boolean;
1319
var
1320
  P1, P2, Buff: Pointer;
1321
  WidthBytes, I: LongInt;
1322
  OldFmt: TImageFormat;
1323
begin
1324
  Result := False;
1325
  OldFmt := Image.Format;
1326
  if TestImage(Image) then
1327
  with Image do
1328
  try
1329
    if ImageFormatInfos[OldFmt].IsSpecial then
1330
      ConvertImage(Image, ifDefault);
1331
1332
    WidthBytes := Width * ImageFormatInfos[Format].BytesPerPixel;
1333
    GetMem(Buff, WidthBytes);
1334
    try
1335
      // Swap all scanlines of image
1336
      for I := 0 to Height div 2 - 1 do
1337
      begin
1338
        P1 := @PByteArray(Bits)[I * WidthBytes];
1339
        P2 := @PByteArray(Bits)[(Height - I - 1) * WidthBytes];
1340
        Move(P1^, Buff^, WidthBytes);
1341
        Move(P2^, P1^, WidthBytes);
1342
        Move(Buff^, P2^, WidthBytes);
1343
      end;
1344
    finally
1345
      FreeMemNil(Buff);
1346
    end;
1347
1348
    if OldFmt <> Format then
1349
      ConvertImage(Image, OldFmt);
1350
1351
    Result := True;  
1352
  except
1353
    RaiseImaging(SErrorFlipImage, [ImageToStr(Image)]);
1354
  end;
1355
end;
1356
1357
function MirrorImage(var Image: TImageData): Boolean;
1358
var
1359
  Scanline: PByte;
1360
  Buff: TColorFPRec;
1361
  Bpp, Y, X, WidthDiv2, WidthBytes, XLeft, XRight: LongInt;
1362
  OldFmt: TImageFormat;
1363
begin
1364
  Result := False;
1365
  OldFmt := Image.Format;
1366
  if TestImage(Image) then
1367
  with Image do
1368
  try
1369
    if ImageFormatInfos[OldFmt].IsSpecial then
1370
      ConvertImage(Image, ifDefault);
1371
1372
    Bpp := ImageFormatInfos[Format].BytesPerPixel;
1373
    WidthDiv2 := Width div 2;
1374
    WidthBytes := Width * Bpp;
1375
    // Mirror all pixels on each scanline of image
1376
    for Y := 0 to Height - 1 do
1377
    begin
1378
      Scanline := @PByteArray(Bits)[Y * WidthBytes];
1379
      XLeft := 0;
1380
      XRight := (Width - 1) * Bpp;
1381
      for X := 0 to WidthDiv2 - 1 do
1382
      begin
1383
        CopyPixel(@PByteArray(Scanline)[XLeft], @Buff, Bpp);
1384
        CopyPixel(@PByteArray(Scanline)[XRight],
1385
          @PByteArray(Scanline)[XLeft], Bpp);
1386
        CopyPixel(@Buff, @PByteArray(Scanline)[XRight], Bpp);
1387
        Inc(XLeft, Bpp);
1388
        Dec(XRight, Bpp);
1389
      end;
1390
    end;
1391
1392
    if OldFmt <> Format then
1393
      ConvertImage(Image, OldFmt);
1394
1395
    Result := True;
1396
  except
1397
    RaiseImaging(SErrorMirrorImage, [ImageToStr(Image)]);
1398
  end;
1399
end;
1400
1401
function ResizeImage(var Image: TImageData; NewWidth, NewHeight: LongInt;
1402
  Filter: TResizeFilter): Boolean;
1403
var
1404
  WorkImage: TImageData;
1405
begin
1406
  Assert((NewWidth > 0) and (NewHeight > 0));
1407
  Result := False;
1408
  if TestImage(Image) and ((Image.Width <> NewWidth) or (Image.Height <> NewHeight)) then
1409
  try
1410
    InitImage(WorkImage);
1411
    // Create new image with desired dimensions
1412
    NewImage(NewWidth, NewHeight, Image.Format, WorkImage);
1413
    // Stretch pixels from old image to new one
1414
    StretchRect(Image, 0, 0, Image.Width, Image.Height,
1415
      WorkImage, 0, 0, WorkImage.Width, WorkImage.Height, Filter);
1416
    // Free old image and assign new image to it
1417
    FreeMemNil(Image.Bits);
1418
    if Image.Palette <> nil then
1419
      WorkImage.Palette := Image.Palette;
1420
    Image := WorkImage;
1421
    Result := True;
1422
  except
1423
    RaiseImaging(SErrorResizeImage, [ImageToStr(Image)]);
1424
  end;
1425
end;
1426
1427
function SwapChannels(var Image: TImageData; SrcChannel, DstChannel: LongInt): Boolean;
1428
var
1429
  I, NumPixels: LongInt;
1430
  Info: PImageFormatInfo;
1431
  Swap, Alpha: Word;
1432
  Data: PByte;
1433
  Pix64: TColor64Rec;
1434
  PixF: TColorFPRec;
1435
  SwapF: Single;
1436
begin
1437
  Assert((SrcChannel in [0..3]) and (DstChannel in [0..3]));
1438
  Result := False;
1439
  if TestImage(Image) and (SrcChannel <> DstChannel) then
1440
  with Image do
1441
  try
1442
    NumPixels := Width * Height;
1443
    Info := ImageFormatInfos[Format];
1444
    Data := Bits;
1445
1446
    if (Info.Format = ifR8G8B8) or ((Info.Format = ifA8R8G8B8) and
1447
       (SrcChannel <> ChannelAlpha) and (DstChannel <> ChannelAlpha)) then
1448
    begin
1449
      // Swap channels of most common formats R8G8B8 and A8R8G8B8 (no alpha)
1450
      for I := 0 to NumPixels - 1 do
1451
      with PColor24Rec(Data)^ do
1452
      begin
1453
        Swap := Channels[SrcChannel];
1454
        Channels[SrcChannel] := Channels[DstChannel];
1455
        Channels[DstChannel] := Swap;
1456
        Inc(Data, Info.BytesPerPixel);
1457
      end;
1458
    end
1459
    else if Info.IsIndexed then
1460
    begin
1461
      // Swap palette channels of indexed images
1462
      SwapChannelsOfPalette(Palette, Info.PaletteEntries, SrcChannel, DstChannel)
1463
    end
1464
    else if Info.IsFloatingPoint then
1465
    begin
1466
      // Swap channels of floating point images
1467
      for I := 0 to NumPixels - 1 do
1468
      begin
1469
        FloatGetSrcPixel(Data, Info, PixF);
1470
        with PixF do
1471
        begin
1472
          SwapF := Channels[SrcChannel];
1473
          Channels[SrcChannel] := Channels[DstChannel];
1474
          Channels[DstChannel] := SwapF;
1475
        end;
1476
        FloatSetDstPixel(Data, Info, PixF);
1477
        Inc(Data, Info.BytesPerPixel);
1478
      end;
1479
    end
1480
    else if Info.IsSpecial then
1481
    begin
1482
      // Swap channels of special format images
1483
      ConvertImage(Image, ifDefault);
1484
      SwapChannels(Image, SrcChannel, DstChannel);
1485
      ConvertImage(Image, Info.Format);
1486
    end
1487
    else if Info.HasGrayChannel and Info.HasAlphaChannel and
1488
      ((SrcChannel = ChannelAlpha) or (DstChannel = ChannelAlpha)) then
1489
    begin
1490
      for I := 0 to NumPixels - 1 do
1491
      begin
1492
        // If we have grayscale image with alpha and alpha is channel
1493
        // to be swapped, we swap it. No other alternative for gray images,
1494
        // just alpha and something
1495
        GrayGetSrcPixel(Data, Info, Pix64, Alpha);
1496
        Swap := Alpha;
1497
        Alpha := Pix64.A;
1498
        Pix64.A := Swap;
1499
        GraySetDstPixel(Data, Info, Pix64, Alpha);
1500
        Inc(Data, Info.BytesPerPixel);
1501
      end;
1502
    end
1503
    else
1504
    begin
1505
      // Then do general swap on other channel image formats
1506
      for I := 0 to NumPixels - 1 do
1507
      begin
1508
        ChannelGetSrcPixel(Data, Info, Pix64);
1509
        with Pix64 do
1510
        begin
1511
          Swap := Channels[SrcChannel];
1512
          Channels[SrcChannel] := Channels[DstChannel];
1513
          Channels[DstChannel] := Swap;
1514
        end;
1515
        ChannelSetDstPixel(Data, Info, Pix64);
1516
        Inc(Data, Info.BytesPerPixel);
1517
      end;
1518
    end;
1519
1520
    Result := True;
1521
  except
1522
    RaiseImaging(SErrorSwapImage, [ImageToStr(Image)]);
1523
  end;
1524
end;
1525
1526
function ReduceColors(var Image: TImageData; MaxColors: LongInt): Boolean;
1527
var
1528
  TmpInfo: TImageFormatInfo;
1529
  Data, Index: PWord;
1530
  I, NumPixels: LongInt;
1531
  Pal: PPalette32;
1532
  Col:PColor32Rec;
1533
  OldFmt: TImageFormat;
1534
begin
1535
  Result := False;
1536
  if TestImage(Image) then
1537
  with Image do
1538
  try
1539
    // First create temp image info and allocate output bits and palette
1540
    MaxColors := ClampInt(MaxColors, 2, High(Word));
1541
    OldFmt := Format;
1542
    FillChar(TmpInfo, SizeOf(TmpInfo), 0);
1543
    TmpInfo.PaletteEntries := MaxColors;
1544
    TmpInfo.BytesPerPixel := 2;
1545
    NumPixels := Width * Height;
1546
    GetMem(Data, NumPixels * TmpInfo.BytesPerPixel);
1547
    GetMem(Pal, MaxColors * SizeOf(TColor32Rec));
1548
    ConvertImage(Image, ifA8R8G8B8);
1549
    // We use median cut algorithm to create reduced palette and to
1550
    // fill Data with indices to this palette
1551
    ReduceColorsMedianCut(NumPixels, Bits, PByte(Data),
1552
      ImageFormatInfos[Format], @TmpInfo, MaxColors, ColorReductionMask, Pal);
1553
    Col := Bits;
1554
    Index := Data;
1555
    // Then we write reduced colors to the input image
1556
    for I := 0 to NumPixels - 1 do
1557
    begin
1558
      Col.Color := Pal[Index^].Color;
1559
      Inc(Col);
1560
      Inc(Index);
1561
    end;
1562
    FreeMemNil(Data);
1563
    FreeMemNil(Pal);
1564
    // And convert it to its original format
1565
    ConvertImage(Image, OldFmt);
1566
    Result := True;
1567
  except
1568
    RaiseImaging(SErrorReduceColors, [MaxColors, ImageToStr(Image)]);
1569
  end;
1570
end;
1571
1572
function GenerateMipMaps(const Image: TImageData; Levels: LongInt;
1573
  var MipMaps: TDynImageDataArray): Boolean;
1574
var
1575
  Width, Height, I, Count: LongInt;
1576
begin
1577
  Result := False;
1578
  if TestImage(Image) then
1579
  try
1580
    Width := Image.Width;
1581
    Height := Image.Height;
1582
    // We compute number of possible mipmap levels and if
1583
    // the given levels are invalid or zero we use this value
1584
    Count := GetNumMipMapLevels(Width, Height);
1585
    if (Levels <= 0) or (Levels > Count) then
1586
      Levels := Count;
1587
1588
    FreeImagesInArray(MipMaps);
1589
    SetLength(MipMaps, Levels);
1590
    CloneImage(Image, MipMaps[0]);
1591
1592
    for I := 1 to Levels - 1 do
1593
    begin
1594
      Width := Width shr 1;
1595
      Height := Height shr 1;
1596
      if Width < 1 then Width := 1;
1597
      if Height < 1 then Height := 1;
1598
      FillMipMapLevel(MipMaps[I - 1], Width, Height, MipMaps[I]);
1599
    end;
1600
    Result := True;
1601
  except
1602
    RaiseImaging(SErrorGenerateMipMaps, [Levels, ImageToStr(Image)]);
1603
  end;
1604
end;
1605
1606
function MapImageToPalette(var Image: TImageData; Pal: PPalette32;
1607
  Entries: LongInt): Boolean;
1608
1609
  function FindNearestColor(Pal: PPalette32; Entries: LongInt; Col: TColor32Rec): LongInt;
1610
  var
1611
    I, MinDif, Dif: LongInt;
1612
  begin
1613
    Result := 0;
1614
    MinDif := 1020;
1615
    for I := 0 to Entries - 1 do
1616
    with Pal[I] do
1617
    begin
1618
      Dif := Abs(R - Col.R);
1619
      if Dif > MinDif then Continue;
1620
      Dif := Dif + Abs(G - Col.G);
1621
      if Dif > MinDif then Continue;
1622
      Dif := Dif + Abs(B - Col.B);
1623
      if Dif > MinDif then Continue;
1624
      Dif := Dif + Abs(A - Col.A);
1625
      if Dif < MinDif then
1626
      begin
1627
        MinDif := Dif;
1628
        Result := I;
1629
      end;
1630
    end;
1631
  end;
1632
1633
var
1634
  I, MaxEntries: LongInt;
1635
  PIndex: PByte;
1636
  PColor: PColor32Rec;
1637
  CloneARGB: TImageData;
1638
  Info: PImageFormatInfo;
1639
begin
1640
  Assert((Entries >= 2) and (Entries <= 256));
1641
  Result := False;
1642
1643
  if TestImage(Image) then
1644
  try
1645
    // We create clone of source image in A8R8G8B8 and
1646
    // then recreate source image in ifIndex8 format
1647
    // with palette taken from Pal parameter
1648
    InitImage(CloneARGB);
1649
    CloneImage(Image, CloneARGB);
1650
    ConvertImage(CloneARGB, ifA8R8G8B8);
1651
    FreeImage(Image);
1652
    NewImage(CloneARGB.Width, CloneARGB.Height, ifIndex8, Image);
1653
1654
    Info := ImageFormatInfos[Image.Format];
1655
    MaxEntries := Min(Info.PaletteEntries, Entries);
1656
    Move(Pal^, Image.Palette^, MaxEntries * SizeOf(TColor32Rec));
1657
    PIndex := Image.Bits;
1658
    PColor := CloneARGB.Bits;
1659
1660
    // For every pixel of ARGB clone we find closest color in
1661
    // given palette and assign its index to resulting image's pixel
1662
    // procedure used here is very slow but simple and memory usage friendly
1663
    // (contrary to other methods)
1664
    for I := 0 to Image.Width * Image.Height - 1 do
1665
    begin
1666
      PIndex^ := Byte(FindNearestColor(Image.Palette, MaxEntries, PColor^));
1667
      Inc(PIndex);
1668
      Inc(PColor);
1669
    end;
1670
1671
    FreeImage(CloneARGB);
1672
    Result := True;
1673
  except
1674
    RaiseImaging(SErrorMapImage, [ImageToStr(Image)]);
1675
  end;
1676
end;
1677
1678
function SplitImage(var Image: TImageData; var Chunks: TDynImageDataArray;
1679
  ChunkWidth, ChunkHeight: LongInt; var XChunks, YChunks: LongInt;
1680
  PreserveSize: Boolean; Fill: Pointer): Boolean;
1681
var
1682
  X, Y, XTrunc, YTrunc: LongInt;
1683
  NotOnEdge: Boolean;
1684
  Info: PImageFormatInfo;
1685
  OldFmt: TImageFormat;
1686
begin
1687
  Assert((ChunkWidth > 0) and (ChunkHeight > 0));
1688
  Result := False;
1689
  OldFmt := Image.Format;
1690
  FreeImagesInArray(Chunks);
1691
1692
  if TestImage(Image) then
1693
  try
1694
    Info := ImageFormatInfos[Image.Format];
1695
    if Info.IsSpecial then
1696
      ConvertImage(Image, ifDefault);
1697
1698
    // We compute make sure that chunks are not larger than source image or negative
1699
    ChunkWidth := ClampInt(ChunkWidth, 0, Image.Width);
1700
    ChunkHeight := ClampInt(ChunkHeight, 0, Image.Height);
1701
    // Number of chunks along X and Y axes is computed
1702
    XChunks := Trunc(Ceil(Image.Width / ChunkWidth));
1703
    YChunks := Trunc(Ceil(Image.Height / ChunkHeight));
1704
    SetLength(Chunks, XChunks * YChunks);
1705
1706
    // For every chunk we create new image and copy a portion of
1707
    // the source image to it. If chunk is on the edge of the source image
1708
    // we fill enpty space with Fill pixel data if PreserveSize is set or
1709
    // make the chunk smaller if it is not set
1710
    for Y := 0 to YChunks - 1 do
1711
      for X := 0 to XChunks - 1 do
1712
      begin
1713
        // Determine if current chunk is on the edge of original image
1714
        NotOnEdge := ((X < XChunks - 1) and (Y < YChunks - 1)) or
1715
          ((Image.Width mod ChunkWidth = 0) and (Image.Height mod ChunkHeight = 0));
1716
1717
        if PreserveSize or NotOnEdge then
1718
        begin
1719
          // We should preserve chunk sizes or we are somewhere inside original image
1720
          NewImage(ChunkWidth, ChunkHeight, Image.Format, Chunks[Y * XChunks + X]);
1721
          if (not NotOnEdge) and (Fill <> nil) then
1722
            FillRect(Chunks[Y * XChunks + X], 0, 0, ChunkWidth, ChunkHeight, Fill);
1723
          CopyRect(Image, X * ChunkWidth, Y * ChunkHeight, ChunkWidth, ChunkHeight,
1724
            Chunks[Y * XChunks + X], 0, 0);
1725
        end
1726
        else
1727
        begin
1728
          // Create smaller edge chunk
1729
          XTrunc := Image.Width - (Image.Width div ChunkWidth) * ChunkWidth;
1730
          YTrunc := Image.Height - (Image.Height div ChunkHeight) * ChunkHeight;
1731
          NewImage(XTrunc, YTrunc, Image.Format, Chunks[Y * XChunks + X]);
1732
          CopyRect(Image, X * ChunkWidth, Y * ChunkHeight, XTrunc, YTrunc,
1733
            Chunks[Y * XChunks + X], 0, 0);
1734
        end;
1735
        
1736
        // If source image is in indexed format we copy its palette to chunk
1737
        if Info.IsIndexed then
1738
        begin
1739
          Move(Image.Palette^, Chunks[Y * XChunks + X].Palette^,
1740
            Info.PaletteEntries * SizeOf(TColor32Rec));
1741
        end;
1742
      end;
1743
1744
    if OldFmt <> Image.Format then
1745
    begin
1746
      ConvertImage(Image, OldFmt);
1747
      for X := 0 to Length(Chunks) - 1 do
1748
        ConvertImage(Chunks[X], OldFmt);
1749
    end;
1750
1751
    Result := True;
1752
  except
1753
    RaiseImaging(SErrorSplitImage, [ImageToStr(Image), ChunkWidth, ChunkHeight]);
1754
  end;
1755
end;
1756
1757
function MakePaletteForImages(var Images: TDynImageDataArray; Pal: PPalette32;
1758
  MaxColors: LongInt; ConvertImages: Boolean): Boolean;
1759
var
1760
  I: Integer;
1761
  SrcInfo, DstInfo: PImageFormatInfo;
1762
  Target, TempImage: TImageData;
1763
  DstFormat: TImageFormat;
1764
begin
1765
  Assert((Pal <> nil) and (MaxColors > 0));
1766
  Result := False;
1767
  InitImage(TempImage);
1768
1769
  if TestImagesInArray(Images) then
1770
  try
1771
    // Null the color histogram
1772
    ReduceColorsMedianCut(0, nil, nil, nil, nil, 0, 0, nil, [raCreateHistogram]);
1773
    for I := 0 to Length(Images) - 1 do
1774
    begin
1775
      SrcInfo := ImageFormatInfos[Images[I].Format];
1776
      if SrcInfo.IsIndexed or SrcInfo.IsSpecial then
1777
      begin
1778
        // create temp image in supported format for updating histogram
1779
        CloneImage(Images[I], TempImage);
1780
        ConvertImage(TempImage, ifA8R8G8B8);
1781
        SrcInfo := ImageFormatInfos[TempImage.Format];
1782
      end
1783
      else
1784
        TempImage := Images[I];
1785
1786
      // Update histogram with colors of each input image
1787
      ReduceColorsMedianCut(TempImage.Width * TempImage.Height, TempImage.Bits,
1788
        nil, SrcInfo, nil, MaxColors, ColorReductionMask, nil, [raUpdateHistogram]);
1789
1790
      if Images[I].Bits <> TempImage.Bits then
1791
        FreeImage(TempImage);
1792
    end;
1793
    // Construct reduced color map from the histogram
1794
    ReduceColorsMedianCut(0, nil, nil, nil, nil, MaxColors, ColorReductionMask,
1795
      Pal, [raMakeColorMap]);
1796
1797
    if ConvertImages then
1798
    begin
1799
      DstFormat := ifIndex8;
1800
      DstInfo := ImageFormatInfos[DstFormat];
1801
      MaxColors := Min(DstInfo.PaletteEntries, MaxColors);
1802
1803
      for I := 0 to Length(Images) - 1 do
1804
      begin
1805
        SrcInfo := ImageFormatInfos[Images[I].Format];
1806
        if SrcInfo.IsIndexed or SrcInfo.IsSpecial then
1807
        begin
1808
          // If source image is in format not supported by ReduceColorsMedianCut
1809
          // we convert it
1810
          ConvertImage(Images[I], ifA8R8G8B8);
1811
          SrcInfo := ImageFormatInfos[Images[I].Format];
1812
        end;
1813
1814
        InitImage(Target);
1815
        NewImage(Images[I].Width, Images[I].Height, DstFormat, Target);
1816
        // We map each input image to reduced palette and replace
1817
        // image in array with mapped image
1818
        ReduceColorsMedianCut(Images[I].Width * Images[I].Height, Images[I].Bits,
1819
          Target.Bits, SrcInfo, DstInfo, MaxColors, 0, nil, [raMapImage]);
1820
        Move(Pal^, Target.Palette^, MaxColors * SizeOf(TColor32Rec));
1821
1822
        FreeImage(Images[I]);
1823
        Images[I] := Target;
1824
      end;
1825
    end;
1826
    Result := True;
1827
  except
1828
    RaiseImaging(SErrorMakePaletteForImages, [MaxColors, Length(Images)]);
1829
  end;
1830
end;
1831
1832
function RotateImage(var Image: TImageData; Angle: LongInt): Boolean;
1833
var
1834
  X, Y, BytesPerPixel: LongInt;
1835
  RotImage: TImageData;
1836
  Pix, RotPix: PByte;
1837
  OldFmt: TImageFormat;
1838
begin
1839
  Assert(Angle mod 90 = 0);
1840
  Result := False;
1841
1842
  if TestImage(Image) then
1843
  try
1844
    if (Angle < -360) or (Angle > 360) then Angle := Angle mod 360;
1845
    if (Angle = 0) or (Abs(Angle) = 360) then
1846
    begin
1847
      Result := True;
1848
      Exit;
1849
    end;
1850
1851
    Angle := Iff(Angle = -90, 270, Angle);
1852
    Angle := Iff(Angle = -270, 90, Angle);
1853
    Angle := Iff(Angle = -180, 180, Angle);
1854
1855
    OldFmt := Image.Format;
1856
    if ImageFormatInfos[Image.Format].IsSpecial then
1857
      ConvertImage(Image, ifDefault);
1858
1859
    InitImage(RotImage);
1860
    BytesPerPixel := ImageFormatInfos[Image.Format].BytesPerPixel;
1861
1862
    if ((Angle = 90) or (Angle = 270)) and (Image.Width <> Image.Height) then
1863
      NewImage(Image.Height, Image.Width, Image.Format, RotImage)
1864
    else
1865
      NewImage(Image.Width, Image.Height, Image.Format, RotImage);
1866
1867
    RotPix := RotImage.Bits;
1868
    case Angle of
1869
      90:
1870
        begin
1871
          for Y := 0 to RotImage.Height - 1 do
1872
          begin
1873
            Pix := @PByteArray(Image.Bits)[(Image.Width - Y - 1) * BytesPerPixel];
1874
            for X := 0 to RotImage.Width - 1 do
1875
            begin
1876
              CopyPixel(Pix, RotPix, BytesPerPixel);
1877
              Inc(RotPix, BytesPerPixel);
1878
              Inc(Pix, Image.Width * BytesPerPixel);
1879
            end;
1880
          end;
1881
        end;
1882
      180:
1883
        begin
1884
          Pix := @PByteArray(Image.Bits)[((Image.Height - 1) * Image.Width +
1885
            (Image.Width - 1)) * BytesPerPixel];
1886
          for Y := 0 to RotImage.Height - 1 do
1887
            for X := 0 to RotImage.Width - 1 do
1888
            begin
1889
              CopyPixel(Pix, RotPix, BytesPerPixel);
1890
              Inc(RotPix, BytesPerPixel);
1891
              Dec(Pix, BytesPerPixel);
1892
            end;
1893
        end;
1894
      270:
1895
        begin
1896
          for Y := 0 to RotImage.Height - 1 do
1897
          begin
1898
            Pix := @PByteArray(Image.Bits)[((Image.Height - 1) * Image.Width +
1899
              Y) * BytesPerPixel];
1900
            for X := 0 to RotImage.Width - 1 do
1901
            begin
1902
              CopyPixel(Pix, RotPix, BytesPerPixel);
1903
              Inc(RotPix, BytesPerPixel);
1904
              Dec(Pix, Image.Width * BytesPerPixel);
1905
            end;
1906
          end;
1907
        end;
1908
    end;
1909
1910
    FreeMemNil(Image.Bits);
1911
    RotImage.Palette := Image.Palette;
1912
    Image := RotImage;
1913
1914
    if OldFmt <> Image.Format then
1915
      ConvertImage(Image, OldFmt);
1916
1917
    Result := True;
1918
  except
1919
    RaiseImaging(SErrorRotateImage, [ImageToStr(Image), Angle]);
1920
  end;
1921
end;
1922
1923
{ Drawing/Pixel functions }
1924
1925
function CopyRect(const SrcImage: TImageData; SrcX, SrcY, Width, Height: LongInt;
1926
  var DstImage: TImageData; DstX, DstY: LongInt): Boolean;
1927
var
1928
  Info: PImageFormatInfo;
1929
  I, SrcWidthBytes, DstWidthBytes, MoveBytes: LongInt;
1930
  SrcPointer, DstPointer: PByte;
1931
  WorkImage: TImageData;
1932
  OldFormat: TImageFormat;
1933
begin
1934
  Result := False;
1935
  OldFormat := ifUnknown;
1936
  if TestImage(SrcImage) and TestImage(DstImage) then
1937
  try
1938
    // Make sure we are still copying image to image, not invalid pointer to protected memory
1939
    ClipCopyBounds(SrcX, SrcY, Width, Height, DstX, DstY, SrcImage.Width, SrcImage.Height,
1940
      Rect(0, 0, DstImage.Width, DstImage.Height));
1941
1942
    if (Width > 0) and (Height > 0) then
1943
    begin
1944
      Info := ImageFormatInfos[DstImage.Format];
1945
      if Info.IsSpecial then
1946
      begin
1947
        // If dest image is in special format we convert it to default
1948
        OldFormat := Info.Format;
1949
        ConvertImage(DstImage, ifDefault);
1950
        Info := ImageFormatInfos[DstImage.Format];
1951
      end;
1952
      if SrcImage.Format <> DstImage.Format then
1953
      begin
1954
        // If images are in different format source is converted to dest's format
1955
        InitImage(WorkImage);
1956
        CloneImage(SrcImage, WorkImage);
1957
        ConvertImage(WorkImage, DstImage.Format);
1958
      end
1959
      else
1960
        WorkImage := SrcImage;
1961
1962
      MoveBytes := Width * Info.BytesPerPixel;
1963
      DstWidthBytes := DstImage.Width * Info.BytesPerPixel;
1964
      DstPointer := @PByteArray(DstImage.Bits)[DstY * DstWidthBytes +
1965
        DstX * Info.BytesPerPixel];
1966
      SrcWidthBytes := WorkImage.Width * Info.BytesPerPixel;
1967
      SrcPointer := @PByteArray(WorkImage.Bits)[SrcY * SrcWidthBytes +
1968
        SrcX * Info.BytesPerPixel];
1969
1970
      for I := 0 to Height - 1 do
1971
      begin
1972
        Move(SrcPointer^, DstPointer^, MoveBytes);
1973
        Inc(SrcPointer, SrcWidthBytes);
1974
        Inc(DstPointer, DstWidthBytes);
1975
      end;
1976
      // If dest image was in special format we convert it back
1977
      if OldFormat <> ifUnknown then
1978
        ConvertImage(DstImage, OldFormat);
1979
      // Working image must be freed if it is not the same as source image
1980
      if WorkImage.Bits <> SrcImage.Bits then
1981
        FreeImage(WorkImage);
1982
1983
      Result := True;
1984
    end;
1985
  except
1986
    RaiseImaging(SErrorCopyRect, [ImageToStr(SrcImage), ImageToStr(DstImage)]);
1987
  end;
1988
end;
1989
1990
function FillRect(var Image: TImageData; X, Y, Width, Height: LongInt;
1991
  FillColor: Pointer): Boolean;
1992
var
1993
  Info: PImageFormatInfo;
1994
  I, J, ImageWidthBytes, RectWidthBytes, Bpp: Longint;
1995
  LinePointer, PixPointer: PByte;
1996
  OldFmt: TImageFormat;
1997
begin
1998
  Result := False;
1999
  if TestImage(Image) then
2000
  try
2001
    ClipRectBounds(X, Y, Width, Height, Rect(0, 0, Image.Width, Image.Height));
2002
2003
    if (Width > 0) and (Height > 0) then
2004
    begin
2005
      OldFmt := Image.Format;
2006
      if ImageFormatInfos[OldFmt].IsSpecial then
2007
        ConvertImage(Image, ifDefault);
2008
2009
      Info := ImageFormatInfos[Image.Format];
2010
      Bpp := Info.BytesPerPixel;
2011
      ImageWidthBytes := Image.Width * Bpp;
2012
      RectWidthBytes := Width * Bpp;
2013
      LinePointer := @PByteArray(Image.Bits)[Y * ImageWidthBytes + X * Bpp];
2014
2015
      for I := 0 to Height - 1 do
2016
      begin
2017
        case Bpp of
2018
          1: FillMemoryByte(LinePointer, RectWidthBytes, PByte(FillColor)^);
2019
          2: FillMemoryWord(LinePointer, RectWidthBytes, PWord(FillColor)^);
2020
          4: FillMemoryLongWord(LinePointer, RectWidthBytes, PLongWord(FillColor)^);
2021
        else
2022
          PixPointer := LinePointer;
2023
          for J := 0 to Width - 1 do
2024
          begin
2025
            CopyPixel(FillColor, PixPointer, Bpp);
2026
            Inc(PixPointer, Bpp);
2027
          end;
2028
        end;
2029
        Inc(LinePointer, ImageWidthBytes);
2030
      end;
2031
2032
      if OldFmt <> Image.Format then
2033
        ConvertImage(Image, OldFmt);
2034
    end;
2035
2036
    Result := True;
2037
  except
2038
    RaiseImaging(SErrorFillRect, [X, Y, Width, Height, ImageToStr(Image)]);
2039
  end;
2040
end;
2041
2042
function ReplaceColor(var Image: TImageData; X, Y, Width, Height: LongInt;
2043
  OldColor, NewColor: Pointer): Boolean;
2044
var
2045
  Info: PImageFormatInfo;
2046
  I, J, WidthBytes, Bpp: Longint;
2047
  LinePointer, PixPointer: PByte;
2048
  OldFmt: TImageFormat;
2049
begin
2050
  Assert((OldColor <> nil) and (NewColor <> nil));
2051
  Result := False;
2052
  if TestImage(Image) then
2053
  try
2054
    ClipRectBounds(X, Y, Width, Height, Rect(0, 0, Image.Width, Image.Height));
2055
2056
    if (Width > 0) and (Height > 0) then
2057
    begin
2058
      OldFmt := Image.Format;
2059
      if ImageFormatInfos[OldFmt].IsSpecial then
2060
        ConvertImage(Image, ifDefault);
2061
2062
      Info := ImageFormatInfos[Image.Format];
2063
      Bpp := Info.BytesPerPixel;
2064
      WidthBytes := Image.Width * Bpp;
2065
      LinePointer := @PByteArray(Image.Bits)[Y * WidthBytes + X * Bpp];
2066
2067
      for I := 0 to Height - 1 do
2068
      begin
2069
        PixPointer := LinePointer;
2070
        for J := 0 to Width - 1 do
2071
        begin
2072
          if ComparePixels(PixPointer, OldColor, Bpp) then
2073
            CopyPixel(NewColor, PixPointer, Bpp);
2074
          Inc(PixPointer, Bpp);
2075
        end;
2076
        Inc(LinePointer, WidthBytes);
2077
      end;
2078
2079
      if OldFmt <> Image.Format then
2080
        ConvertImage(Image, OldFmt);
2081
    end;
2082
2083
    Result := True;
2084
  except
2085
    RaiseImaging(SErrorReplaceColor, [X, Y, Width, Height, ImageToStr(Image)]);
2086
  end;
2087
end;
2088
2089
function StretchRect(const SrcImage: TImageData; SrcX, SrcY, SrcWidth,
2090
  SrcHeight: LongInt; var DstImage: TImageData; DstX, DstY, DstWidth,
2091
  DstHeight: LongInt; Filter: TResizeFilter): Boolean;
2092
var
2093
  Info: PImageFormatInfo;
2094
  WorkImage: TImageData;
2095
  OldFormat: TImageFormat;
2096
begin
2097
  Result := False;
2098
  OldFormat := ifUnknown;
2099
  if TestImage(SrcImage) and TestImage(DstImage) then
2100
  try
2101
    // Make sure we are still copying image to image, not invalid pointer to protected memory
2102
    ClipStretchBounds(SrcX, SrcY, SrcWidth, SrcHeight, DstX, DstY, DstWidth, DstHeight,
2103
      SrcImage.Width, SrcImage.Height, Rect(0, 0, DstImage.Width, DstImage.Height));
2104
2105
    if (SrcWidth = DstWidth) and (SrcHeight = DstHeight) then
2106
    begin
2107
      // If source and dest rectangles have the same size call CopyRect
2108
      Result := CopyRect(SrcImage, SrcX, SrcY, SrcWidth, SrcHeight, DstImage, DstX, DstY);
2109
    end
2110
    else if (SrcWidth > 0) and (SrcHeight > 0) and (DstWidth > 0) and (DstHeight > 0) then
2111
    begin
2112
      // If source and dest rectangles don't have the same size we do stretch
2113
      Info := ImageFormatInfos[DstImage.Format];
2114
2115
      if Info.IsSpecial then
2116
      begin
2117
        // If dest image is in special format we convert it to default
2118
        OldFormat := Info.Format;
2119
        ConvertImage(DstImage, ifDefault);
2120
        Info := ImageFormatInfos[DstImage.Format];
2121
      end;
2122
2123
      if SrcImage.Format <> DstImage.Format then
2124
      begin
2125
        // If images are in different format source is converted to dest's format
2126
        InitImage(WorkImage);
2127
        CloneImage(SrcImage, WorkImage);
2128
        ConvertImage(WorkImage, DstImage.Format);
2129
      end
2130
      else
2131
        WorkImage := SrcImage;
2132
2133
      // Only pixel resize is supported for indexed images
2134
      if Info.IsIndexed then
2135
        Filter := rfNearest;
2136
2137
      case Filter of
2138
        rfNearest: StretchNearest(WorkImage, SrcX, SrcY, SrcWidth, SrcHeight,
2139
          DstImage, DstX, DstY, DstWidth, DstHeight);
2140
        rfBilinear: StretchResample(WorkImage, SrcX, SrcY, SrcWidth, SrcHeight,
2141
          DstImage, DstX, DstY, DstWidth, DstHeight, sfLinear);
2142
        rfBicubic: StretchResample(WorkImage, SrcX, SrcY, SrcWidth, SrcHeight,
2143
          DstImage, DstX, DstY, DstWidth, DstHeight, sfCatmullRom);
2144
      end;
2145
2146
      // If dest image was in special format we convert it back
2147
      if OldFormat <> ifUnknown then
2148
        ConvertImage(DstImage, OldFormat);
2149
      // Working image must be freed if it is not the same as source image
2150
      if WorkImage.Bits <> SrcImage.Bits then
2151
        FreeImage(WorkImage);
2152
2153
      Result := True;
2154
    end;
2155
  except
2156
    RaiseImaging(SErrorStretchRect, [ImageToStr(SrcImage), ImageToStr(DstImage)]);
2157
  end;
2158
end;
2159
2160
procedure GetPixelDirect(const Image: TImageData; X, Y: LongInt; Pixel: Pointer);
2161
var
2162
  BytesPerPixel: LongInt;
2163
begin
2164
  Assert(Pixel <> nil);
2165
  BytesPerPixel := ImageFormatInfos[Image.Format].BytesPerPixel;
2166
  CopyPixel(@PByteArray(Image.Bits)[(Y * Image.Width + X) * BytesPerPixel],
2167
    Pixel, BytesPerPixel);
2168
end;
2169
2170
procedure SetPixelDirect(const Image: TImageData; X, Y: LongInt; Pixel: Pointer);
2171
var
2172
  BytesPerPixel: LongInt;
2173
begin
2174
  Assert(Pixel <> nil);
2175
  BytesPerPixel := ImageFormatInfos[Image.Format].BytesPerPixel;
2176
  CopyPixel(Pixel, @PByteArray(Image.Bits)[(Y * Image.Width + X) * BytesPerPixel],
2177
    BytesPerPixel);
2178
end;
2179
2180
function GetPixel32(const Image: TImageData; X, Y: LongInt): TColor32Rec;
2181
var
2182
  Info: PImageFormatInfo;
2183
  Data: PByte;
2184
begin
2185
  Info := ImageFormatInfos[Image.Format];
2186
  Data := @PByteArray(Image.Bits)[(Y * Image.Width + X) * Info.BytesPerPixel];
2187
  Result := GetPixel32Generic(Data, Info, Image.Palette);
2188
end;
2189
2190
procedure SetPixel32(const Image: TImageData; X, Y: LongInt; const Color: TColor32Rec);
2191
var
2192
  Info: PImageFormatInfo;
2193
  Data: PByte;
2194
begin
2195
  Info := ImageFormatInfos[Image.Format];
2196
  Data := @PByteArray(Image.Bits)[(Y * Image.Width + X) * Info.BytesPerPixel];
2197
  SetPixel32Generic(Data, Info, Image.Palette, Color);
2198
end;
2199
2200
function GetPixelFP(const Image: TImageData; X, Y: LongInt): TColorFPRec;
2201
var
2202
  Info: PImageFormatInfo;
2203
  Data: PByte;
2204
begin
2205
  Info := ImageFormatInfos[Image.Format];
2206
  Data := @PByteArray(Image.Bits)[(Y * Image.Width + X) * Info.BytesPerPixel];
2207
  Result := GetPixelFPGeneric(Data, Info, Image.Palette);
2208
end;
2209
2210
procedure SetPixelFP(const Image: TImageData; X, Y: LongInt; const Color: TColorFPRec);
2211
var
2212
  Info: PImageFormatInfo;
2213
  Data: PByte;
2214
begin
2215
  Info := ImageFormatInfos[Image.Format];
2216
  Data := @PByteArray(Image.Bits)[(Y * Image.Width + X) * Info.BytesPerPixel];
2217
  SetPixelFPGeneric(Data, Info, Image.Palette, Color);
2218
end;
2219
2220
{ Palette Functions }
2221
2222
procedure NewPalette(Entries: LongInt; var Pal: PPalette32);
2223
begin
2224
  Assert((Entries > 2) and (Entries <= 65535));
2225
  try
2226
    GetMem(Pal, Entries * SizeOf(TColor32Rec));
2227
    FillChar(Pal^, Entries * SizeOf(TColor32Rec), $FF);
2228
  except
2229
    RaiseImaging(SErrorNewPalette, [Entries]);
2230
  end;
2231
end;
2232
2233
procedure FreePalette(var Pal: PPalette32);
2234
begin
2235
  try
2236
    FreeMemNil(Pal);
2237
  except
2238
    RaiseImaging(SErrorFreePalette, [Pal]);
2239
  end;
2240
end;
2241
2242
procedure CopyPalette(SrcPal, DstPal: PPalette32; SrcIdx, DstIdx, Count: LongInt);
2243
begin
2244
  Assert((SrcPal <> nil) and (DstPal <> nil));
2245
  Assert((SrcIdx >= 0) and (DstIdx >= 0) and (Count >= 0));
2246
  try
2247
    Move(SrcPal[SrcIdx], DstPal[DstIdx], Count * SizeOf(TColor32Rec));
2248
  except
2249
    RaiseImaging(SErrorCopyPalette, [Count, SrcPal, DstPal]);
2250
  end;
2251
end;
2252
2253
function FindColor(Pal: PPalette32; Entries: LongInt; Color: TColor32):
2254
  LongInt;
2255
var
2256
  Col: TColor32Rec;
2257
  I, MinDif, Dif: LongInt;
2258
begin
2259
  Assert(Pal <> nil);
2260
  Result := -1;
2261
  Col.Color := Color;
2262
  try
2263
    // First try to find exact match
2264
    for I := 0 to Entries - 1 do
2265
    with Pal[I] do
2266
    begin
2267
      if (A = Col.A) and (R = Col.R) and
2268
        (G = Col.G) and (B = Col.B) then
2269
      begin
2270
        Result := I;
2271
        Exit;
2272
      end;
2273
    end;
2274
2275
    // If exact match was not found, find nearest color
2276
    MinDif := 1020;
2277
    for I := 0 to Entries - 1 do
2278
    with Pal[I] do
2279
    begin
2280
      Dif := Abs(R - Col.R);
2281
      if Dif > MinDif then Continue;
2282
      Dif := Dif + Abs(G - Col.G);
2283
      if Dif > MinDif then Continue;
2284
      Dif := Dif + Abs(B - Col.B);
2285
      if Dif > MinDif then Continue;
2286
      Dif := Dif + Abs(A - Col.A);
2287
      if Dif < MinDif then
2288
      begin
2289
        MinDif := Dif;
2290
        Result := I;
2291
      end;
2292
    end;
2293
  except
2294
    RaiseImaging(SErrorFindColor, [Pal, Entries]);
2295
  end;
2296
end;
2297
2298
procedure FillGrayscalePalette(Pal: PPalette32; Entries: LongInt);
2299
var
2300
  I: LongInt;
2301
begin
2302
  Assert(Pal <> nil);
2303
  try
2304
    for I := 0 to Entries - 1 do
2305
    with Pal[I] do
2306
    begin
2307
      A := $FF;
2308
      R := Byte(I);
2309
      G := Byte(I);
2310
      B := Byte(I);
2311
    end;
2312
  except
2313
    RaiseImaging(SErrorGrayscalePalette, [Pal, Entries]);
2314
  end;
2315
end;
2316
2317
procedure FillCustomPalette(Pal: PPalette32; Entries: LongInt; RBits, GBits,
2318
  BBits: Byte; Alpha: Byte = $FF);
2319
var
2320
  I, TotalBits, MaxEntries: LongInt;
2321
begin
2322
  Assert(Pal <> nil);
2323
  TotalBits := RBits + GBits + BBits;
2324
  MaxEntries := Min(Pow2Int(TotalBits), Entries);
2325
  FillChar(Pal^, Entries * SizeOf(TColor32Rec), 0);
2326
  try
2327
    for I := 0 to MaxEntries - 1 do
2328
    with Pal[I] do
2329
    begin
2330
      A := Alpha;
2331
      if RBits > 0 then
2332
        R := ((I shr Max(0, GBits + BBits - 1)) and (1 shl RBits - 1)) * 255 div (1 shl RBits - 1);
2333
      if GBits > 0 then
2334
        G := ((I shr Max(0, BBits - 1)) and (1 shl GBits - 1)) * 255 div (1 shl GBits - 1);
2335
      if BBits > 0 then
2336
        B := ((I shr 0) and (1 shl BBits - 1)) * 255 div (1 shl BBits - 1);
2337
    end;
2338
  except
2339
    RaiseImaging(SErrorCustomPalette, [Pal, Entries]);
2340
  end;
2341
end;
2342
2343
procedure SwapChannelsOfPalette(Pal: PPalette32; Entries, SrcChannel,
2344
  DstChannel: LongInt);
2345
var
2346
  I: LongInt;
2347
  Swap: Byte;
2348
begin
2349
  Assert(Pal <> nil);
2350
  Assert((SrcChannel in [0..3]) and (DstChannel in [0..3]));
2351
  try
2352
    for I := 0 to Entries - 1 do
2353
    with Pal[I] do
2354
    begin
2355
      Swap := Channels[SrcChannel];
2356
      Channels[SrcChannel] := Channels[DstChannel];
2357
      Channels[DstChannel] := Swap;
2358
    end;
2359
  except
2360
    RaiseImaging(SErrorSwapPalette, [Pal, Entries]);
2361
  end;
2362
end;
2363
2364
{ Options Functions }
2365
2366
function SetOption(OptionId, Value: LongInt): Boolean;
2367
begin
2368
  Result := False;
2369
  if (OptionId >= 0) and (OptionId < Length(Options)) and
2370
    (Options[OptionID] <> nil) then
2371
  begin
2372
    Options[OptionID]^ := CheckOptionValue(OptionId, Value);
2373
    Result := True;
2374
  end;
2375
end;
2376
2377
function GetOption(OptionId: LongInt): LongInt;
2378
begin
2379
  Result := InvalidOption;
2380
  if (OptionId >= 0) and (OptionId < Length(Options)) and
2381
    (Options[OptionID] <> nil) then
2382
  begin
2383
    Result := Options[OptionID]^;
2384
  end;
2385
end;
2386
2387
function PushOptions: Boolean;
2388
begin
2389
  Result := OptionStack.Push;
2390
end;
2391
2392
function PopOptions: Boolean;
2393
begin
2394
  Result := OptionStack.Pop;
2395
end;
2396
2397
{ Image Format Functions }
2398
2399
function GetImageFormatInfo(Format: TImageFormat; var Info: TImageFormatInfo): Boolean;
2400
begin
2401
  FillChar(Info, SizeOf(Info), 0);
2402
  if ImageFormatInfos[Format] <> nil then
2403
  begin
2404
    Info := ImageFormatInfos[Format]^;
2405
    Result := True;
2406
  end
2407
  else
2408
    Result := False;
2409
end;
2410
2411
function GetPixelsSize(Format: TImageFormat; Width, Height: LongInt): LongInt;
2412
begin
2413
  if ImageFormatInfos[Format] <> nil then
2414
    Result := ImageFormatInfos[Format].GetPixelsSize(Format, Width, Height)
2415
  else
2416
    Result := 0;
2417
end;
2418
2419
{ IO Functions }
2420
2421
procedure SetUserFileIO(OpenReadProc: TOpenReadProc; OpenWriteProc:
2422
  TOpenWriteProc;
2423
  CloseProc: TCloseProc; EofProc: TEofProc; SeekProc: TSeekProc; TellProc:
2424
  TTellProc; ReadProc: TReadProc; WriteProc: TWriteProc);
2425
begin
2426
  FileIO.OpenRead := OpenReadProc;
2427
  FileIO.OpenWrite := OpenWriteProc;
2428
  FileIO.Close := CloseProc;
2429
  FileIO.Eof := EofProc;
2430
  FileIO.Seek := SeekProc;
2431
  FileIO.Tell := TellProc;
2432
  FileIO.Read := ReadProc;
2433
  FileIO.Write := WriteProc;
2434
end;
2435
2436
procedure ResetFileIO;
2437
begin
2438
  FileIO := OriginalFileIO;
2439
end;
2440
2441
2442
{ ------------------------------------------------------------------------
2443
                           Other Imaging Stuff
2444
  ------------------------------------------------------------------------}
2445
2446
function GetFormatName(Format: TImageFormat): string;
2447
begin
2448
  if ImageFormatInfos[Format] <> nil then
2449
    Result := ImageFormatInfos[Format].Name
2450
  else
2451
    Result := SUnknownFormat;
2452
end;
2453
2454
function ImageToStr(const Image: TImageData): string;
2455
var
2456
  ImgSize: Integer;
2457
begin
2458
  if TestImage(Image) then
2459
  with Image do
2460
  begin
2461
    ImgSize := Size;
2462
    if ImgSize > 8192 then
2463
      ImgSize := ImgSize div 1024;
2464
    Result := SysUtils.Format(SImageInfo, [@Image, Width, Height,
2465
      GetFormatName(Format), ImgSize + 0.0, Iff(ImgSize = Size, 'B', 'KiB'), Bits,
2466
      Palette]);
2467
  end
2468
  else
2469
    Result := SysUtils.Format(SImageInfoInvalid, [@Image]);
2470
end;
2471
2472
function GetVersionStr: string;
2473
begin
2474
  Result := Format('%.1d.%.2d.%.1d', [ImagingVersionMajor,
2475
    ImagingVersionMinor, ImagingVersionPatch]);
2476
end;
2477
2478
function IffFormat(Condition: Boolean; const TruePart, FalsePart: TImageFormat): TImageFormat;
2479
begin
2480
  if Condition then
2481
    Result := TruePart
2482
  else
2483
    Result := FalsePart;
2484
end;
2485
2486
procedure RegisterImageFileFormat(AClass: TImageFileFormatClass);
2487
begin
2488
  Assert(AClass <> nil);
2489
  if ImageFileFormats = nil then
2490
    ImageFileFormats := TList.Create;
2491
  if ImageFileFormats <> nil then
2492
    ImageFileFormats.Add(AClass.Create);
2493
end;
2494
2495
function RegisterOption(OptionId: LongInt; Variable: PLongInt): Boolean;
2496
begin
2497
  Result := False;
2498
  if Options = nil then
2499
    InitOptions;
2500
2501
  Assert(Variable <> nil);
2502
2503
  if OptionId >= Length(Options) then
2504
    SetLength(Options, OptionId + InitialOptions);
2505
  if (OptionId >= 0) and (OptionId < Length(Options)) and (Options[OptionId] = nil) then
2506
  begin
2507
    Options[OptionId] := Variable;
2508
    Result := True;
2509
  end;
2510
end;
2511
2512
function FindImageFileFormatByExt(const Ext: string): TImageFileFormat;
2513
var
2514
  I: LongInt;
2515
begin
2516
  Result := nil;
2517
  for I := 0 to ImageFileFormats.Count - 1 do
2518
    if TImageFileFormat(ImageFileFormats[I]).Extensions.IndexOf(Ext) >= 0 then
2519
    begin
2520
      Result := TImageFileFormat(ImageFileFormats[I]);
2521
      Exit;
2522
    end;
2523
end;
2524
2525
function FindImageFileFormatByName(const FileName: string): TImageFileFormat;
2526
var
2527
  I: LongInt;
2528
begin
2529
  Result := nil;
2530
  for I := 0 to ImageFileFormats.Count - 1 do
2531
    if TImageFileFormat(ImageFileFormats[I]).TestFileName(FileName) then
2532
    begin
2533
      Result := TImageFileFormat(ImageFileFormats[I]);
2534
      Exit;
2535
    end;
2536
end;
2537
2538
function FindImageFileFormatByClass(AClass: TImageFileFormatClass): TImageFileFormat;
2539
var
2540
  I: LongInt;
2541
begin
2542
  Result := nil;
2543
  for I := 0 to ImageFileFormats.Count - 1 do
2544
    if TImageFileFormat(ImageFileFormats[I]) is AClass then
2545
    begin
2546
      Result := TObject(ImageFileFormats[I]) as TImageFileFormat;
2547
      Break;
2548
    end;
2549
end;
2550
2551
function GetFileFormatCount: LongInt;
2552
begin
2553
  Result := ImageFileFormats.Count;
2554
end;
2555
2556
function GetFileFormatAtIndex(Index: LongInt): TImageFileFormat;
2557
begin
2558
  if (Index >= 0) and (Index < ImageFileFormats.Count) then
2559
    Result := TImageFileFormat(ImageFileFormats[Index])
2560
  else
2561
    Result := nil;
2562
end;
2563
2564
function GetImageFileFormatsFilter(OpenFileFilter: Boolean): string;
2565
var
2566
  I, J, Count: LongInt;
2567
  Descriptions: string;
2568
  Filters, CurFilter: string;
2569
  FileFormat: TImageFileFormat;
2570
begin
2571
  Descriptions := '';
2572
  Filters := '';
2573
  Count := 0;
2574
2575
  for I := 0 to ImageFileFormats.Count - 1 do
2576
  begin
2577
    FileFormat := TObject(ImageFileFormats[I]) as TImageFileFormat;
2578
2579
    // If we are creating filter for save dialog and this format cannot save
2580
    // files the we skip it
2581
    if not OpenFileFilter and not FileFormat.CanSave then
2582
      Continue;
2583
2584
    CurFilter := '';
2585
    for J := 0 to FileFormat.Masks.Count - 1 do
2586
    begin
2587
      CurFilter := CurFilter + FileFormat.Masks[J];
2588
      if J < FileFormat.Masks.Count - 1 then
2589
        CurFilter := CurFilter + ';';
2590
    end;
2591
2592
    FmtStr(Descriptions, '%s%s (%s)|%2:s', [Descriptions, FileFormat.Name, CurFilter]);
2593
    if Filters <> '' then
2594
      FmtStr(Filters, '%s;%s', [Filters, CurFilter])
2595
    else
2596
      Filters := CurFilter;
2597
2598
    if I < ImageFileFormats.Count - 1 then
2599
        Descriptions := Descriptions + '|';
2600
2601
    Inc(Count);
2602
  end;
2603
2604
  if (Count > 1) and OpenFileFilter then
2605
    FmtStr(Descriptions, '%s (%s)|%1:s|%s', [SAllFilter, Filters, Descriptions]);
2606
2607
  Result := Descriptions;
2608
end;
2609
2610
function GetFilterIndexExtension(Index: LongInt; OpenFileFilter: Boolean): string;
2611
var
2612
  I, Count: LongInt;
2613
  FileFormat: TImageFileFormat;
2614
begin
2615
  // -1 because filter indices are in 1..n range
2616
  Index := Index - 1;
2617
  Result := '';
2618
  if OpenFileFilter then
2619
  begin
2620
    if Index > 0 then
2621
      Index := Index - 1;
2622
  end;
2623
2624
  if (Index >= 0) and (Index < ImageFileFormats.Count) then
2625
  begin
2626
    Count := 0;
2627
    for I := 0 to ImageFileFormats.Count - 1 do
2628
    begin
2629
      FileFormat := TObject(ImageFileFormats[I]) as TImageFileFormat;
2630
      if not OpenFileFilter and not FileFormat.CanSave then
2631
        Continue;
2632
      if Index = Count then
2633
      begin
2634
        if FileFormat.Extensions.Count > 0 then
2635
          Result := FileFormat.Extensions[0];
2636
        Exit;
2637
      end;
2638
      Inc(Count);
2639
    end;
2640
  end;
2641
end;
2642
2643
function GetFileNameFilterIndex(const FileName: string; OpenFileFilter: Boolean): LongInt;
2644
var
2645
  I: LongInt;
2646
  FileFormat: TImageFileFormat;
2647
begin
2648
  Result := 0;
2649
  for I := 0 to ImageFileFormats.Count - 1 do
2650
  begin
2651
    FileFormat := TObject(ImageFileFormats[I]) as TImageFileFormat;
2652
    if not OpenFileFilter and not FileFormat.CanSave then
2653
      Continue;
2654
    if FileFormat.TestFileName(FileName) then
2655
    begin
2656
      // +1 because filter indices are in 1..n range
2657
      Inc(Result);
2658
      if OpenFileFilter then
2659
        Inc(Result);
2660
      Exit;
2661
    end;
2662
    Inc(Result);
2663
  end;
2664
  Result := -1;
2665
end;
2666
2667
function GetIO: TIOFunctions;
2668
begin
2669
  Result := IO;
2670
end;
2671
2672
procedure RaiseImaging(const Msg: string; const Args: array of const);
2673
var
2674
  WholeMsg: string;
2675
begin
2676
  WholeMsg := Msg;
2677
  if GetExceptObject <> nil then
2678
    WholeMsg := WholeMsg + ' ' + SExceptMsg + ': ' +
2679
      GetExceptObject.Message;
2680
  raise EImagingError.CreateFmt(WholeMsg, Args);
2681
end;
2682
2683
{ Internal unit functions }
2684
2685
function CheckOptionValue(OptionId, Value: LongInt): LongInt;
2686
begin
2687
  case OptionId of
2688
    ImagingColorReductionMask:
2689
      Result := ClampInt(Value, 0, $FF);
2690
    ImagingLoadOverrideFormat, ImagingSaveOverrideFormat:
2691
      Result := Iff(ImagingFormats.IsImageFormatValid(TImageFormat(Value)),
2692
        Value, LongInt(ifUnknown));
2693
    ImagingMipMapFilter: Result := ClampInt(Value, Ord(Low(TSamplingFilter)),
2694
        Ord(High(TSamplingFilter)));
2695
  else
2696
    Result := Value;
2697
  end;
2698
end;
2699
2700
procedure SetFileIO;
2701
begin
2702
  IO := FileIO;
2703
end;
2704
2705
procedure SetStreamIO;
2706
begin
2707
  IO := StreamIO;
2708
end;
2709
2710
procedure SetMemoryIO;
2711
begin
2712
  IO := MemoryIO;
2713
end;
2714
2715
procedure InitImageFormats;
2716
begin
2717
  ImagingFormats.InitImageFormats(ImageFormatInfos);
2718
end;
2719
2720
procedure FreeImageFileFormats;
2721
var
2722
  I: LongInt;
2723
begin
2724
  if ImageFileFormats <> nil then
2725
    for I := 0 to ImageFileFormats.Count - 1 do
2726
      TImageFileFormat(ImageFileFormats[I]).Free;
2727
  FreeAndNil(ImageFileFormats);
2728
end;
2729
2730
procedure InitOptions;
2731
begin
2732
  SetLength(Options, InitialOptions);
2733
  OptionStack := TOptionStack.Create;
2734
end;
2735
2736
procedure FreeOptions;
2737
begin
2738
  SetLength(Options, 0);
2739
  FreeAndNil(OptionStack);
2740
end;
2741
2742
{
2743
  TImageFileFormat class implementation
2744
}
2745
2746
constructor TImageFileFormat.Create;
2747
begin
2748
  inherited Create;
2749
  FName := SUnknownFormat;
2750
  FExtensions := TStringList.Create;
2751
  FMasks := TStringList.Create;
2752
end;
2753
2754
destructor TImageFileFormat.Destroy;
2755
begin
2756
  FExtensions.Free;
2757
  FMasks.Free;
2758
  inherited Destroy;
2759
end;
2760
2761
function TImageFileFormat.PrepareLoad(Handle: TImagingHandle;
2762
  var Images: TDynImageDataArray; OnlyFirstFrame: Boolean): Boolean;
2763
begin
2764
  FreeImagesInArray(Images);
2765
  SetLength(Images, 0);
2766
  Result := Handle <> nil;
2767
end;
2768
2769
function TImageFileFormat.PostLoadCheck(var Images: TDynImageDataArray;
2770
  LoadResult: Boolean): Boolean;
2771
var
2772
  I: LongInt;
2773
begin
2774
  if not LoadResult then
2775
  begin
2776
    FreeImagesInArray(Images);
2777
    SetLength(Images, 0);
2778
    Result := False;
2779
  end
2780
  else
2781
  begin
2782
    Result := (Length(Images) > 0) and TestImagesInArray(Images);
2783
2784
    if Result then
2785
    begin
2786
      // Convert to overriden format if it is set
2787
      if LoadOverrideFormat <> ifUnknown then
2788
        for I := Low(Images) to High(Images) do
2789
          ConvertImage(Images[I], LoadOverrideFormat);
2790
    end;
2791
  end;
2792
end;
2793
  
2794
function TImageFileFormat.PrepareSave(Handle: TImagingHandle;
2795
  const Images: TDynImageDataArray; var Index: Integer): Boolean;
2796
var
2797
  Len, I: LongInt;
2798
begin
2799
  CheckOptionsValidity;
2800
  Result := False;
2801
  if FCanSave then
2802
  begin
2803
    Len := Length(Images);
2804
    Assert(Len > 0);
2805
2806
    // If there are no images to be saved exit
2807
    if Len = 0 then Exit;
2808
2809
    // Check index of image to be saved (-1 as index means save all images)
2810
    if FIsMultiImageFormat then
2811
    begin
2812
      if (Index >= Len) then
2813
        Index := 0;
2814
2815
      if Index < 0 then
2816
      begin
2817
        Index := 0;
2818
        FFirstIdx := 0;
2819
        FLastIdx := Len - 1;
2820
      end
2821
      else
2822
      begin
2823
        FFirstIdx := Index;
2824
        FLastIdx := Index;
2825
      end;
2826
2827
      for I := FFirstIdx to FLastIdx - 1 do
2828
        if not TestImage(Images[I]) then
2829
          Exit;
2830
    end
2831
    else
2832
    begin
2833
      if (Index >= Len) or (Index < 0) then
2834
        Index := 0;
2835
      if not TestImage(Images[Index]) then
2836
        Exit;
2837
    end;
2838
2839
    Result := True;
2840
  end;
2841
end;
2842
2843
procedure TImageFileFormat.AddMasks(const AMasks: string);
2844
var
2845
  I: LongInt;
2846
  Ext: string;
2847
begin
2848
  FExtensions.Clear;
2849
  FMasks.CommaText := AMasks;
2850
  FMasks.Delimiter := ';';
2851
2852
  for I := 0 to FMasks.Count - 1 do
2853
  begin
2854
    FMasks[I] := Trim(FMasks[I]);
2855
    Ext := GetFileExt(FMasks[I]);
2856
    if (Ext <> '') and (Ext <> '*') then
2857
      FExtensions.Add(Ext);
2858
  end;
2859
end;
2860
2861
function TImageFileFormat.GetFormatInfo(Format: TImageFormat): TImageFormatInfo;
2862
begin
2863
  Result := ImageFormatInfos[Format]^;
2864
end;
2865
2866
function TImageFileFormat.GetSupportedFormats: TImageFormats;
2867
begin
2868
  Result := FSupportedFormats;
2869
end;
2870
2871
function TImageFileFormat.LoadData(Handle: TImagingHandle;
2872
  var Images: TDynImageDataArray; OnlyFirstFrame: Boolean): Boolean;
2873
begin
2874
  Result := False;
2875
  RaiseImaging(SFileFormatCanNotLoad, [FName]);
2876
end;
2877
2878
function TImageFileFormat.SaveData(Handle: TImagingHandle;
2879
  const Images: TDynImageDataArray; Index: LongInt): Boolean;
2880
begin
2881
  Result := False;
2882
  RaiseImaging(SFileFormatCanNotSave, [FName]);
2883
end;
2884
2885
procedure TImageFileFormat.ConvertToSupported(var Image: TImageData;
2886
  const Info: TImageFormatInfo);
2887
begin
2888
end;
2889
2890
function TImageFileFormat.IsSupported(const Image: TImageData): Boolean;
2891
begin
2892
  Result := Image.Format in GetSupportedFormats;
2893
end;
2894
2895
function TImageFileFormat.LoadFromFile(const FileName: string;
2896
  var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
2897
var
2898
  Handle: TImagingHandle;
2899
begin
2900
  Result := False;
2901
  if FCanLoad then
2902
  try
2903
    // Set IO ops to file ops and open given file
2904
    SetFileIO;
2905
    Handle := IO.OpenRead(PChar(FileName));
2906
    try
2907
      // Test if file contains valid image and if so then load it
2908
      if TestFormat(Handle) then
2909
      begin
2910
        Result := PrepareLoad(Handle, Images, OnlyFirstLevel) and
2911
          LoadData(Handle, Images, OnlyFirstlevel);
2912
        Result := Result and PostLoadCheck(Images, Result);
2913
      end
2914
      else
2915
        RaiseImaging(SFileNotValid, [FileName, Name]);
2916
    finally
2917
      IO.Close(Handle);
2918
    end;
2919
  except
2920
    RaiseImaging(SErrorLoadingFile, [FileName, FExtensions[0]]);
2921
  end;
2922
end;
2923
2924
function TImageFileFormat.LoadFromStream(Stream: TStream;
2925
  var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
2926
var
2927
  Handle: TImagingHandle;
2928
  OldPosition: Int64;
2929
begin
2930
  Result := False;
2931
  OldPosition := Stream.Position;
2932
  if FCanLoad then
2933
  try
2934
    // Set IO ops to stream ops and "open" given memory
2935
    SetStreamIO;
2936
    Handle := IO.OpenRead(Pointer(Stream));
2937
    try
2938
      // Test if stream contains valid image and if so then load it
2939
      if TestFormat(Handle) then
2940
      begin
2941
        Result := PrepareLoad(Handle, Images, OnlyFirstLevel) and
2942
          LoadData(Handle, Images, OnlyFirstlevel);
2943
        Result := Result and PostLoadCheck(Images, Result);
2944
      end
2945
      else
2946
        RaiseImaging(SStreamNotValid, [@Stream, Name]);
2947
    finally
2948
      IO.Close(Handle);
2949
    end;
2950
  except
2951
    Stream.Position := OldPosition;
2952
    RaiseImaging(SErrorLoadingStream, [@Stream, FExtensions[0]]);
2953
  end;
2954
end;
2955
2956
function TImageFileFormat.LoadFromMemory(Data: Pointer; Size: LongInt; var
2957
  Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
2958
var
2959
  Handle: TImagingHandle;
2960
  IORec: TMemoryIORec;
2961
begin
2962
  Result := False;
2963
  if FCanLoad then
2964
  try
2965
    // Set IO ops to memory ops and "open" given memory
2966
    SetMemoryIO;
2967
    IORec := PrepareMemIO(Data, Size);
2968
    Handle := IO.OpenRead(@IORec);
2969
    try
2970
      // Test if memory contains valid image and if so then load it
2971
      if TestFormat(Handle) then
2972
      begin
2973
        Result := PrepareLoad(Handle, Images, OnlyFirstLevel) and
2974
          LoadData(Handle, Images, OnlyFirstlevel);
2975
        Result := Result and PostLoadCheck(Images, Result);
2976
      end
2977
      else
2978
        RaiseImaging(SMemoryNotValid, [Data, Size, Name]);
2979
    finally
2980
      IO.Close(Handle);
2981
    end;
2982
  except
2983
    RaiseImaging(SErrorLoadingMemory, [Data, Size, FExtensions[0]]);
2984
  end;
2985
end;
2986
2987
function TImageFileFormat.SaveToFile(const FileName: string;
2988
  const Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
2989
var
2990
  Handle: TImagingHandle;
2991
  Len, Index, I: LongInt;
2992
  Ext, FName: string;
2993
begin
2994
  Result := False;
2995
  if FCanSave and TestImagesInArray(Images) then
2996
  try
2997
    SetFileIO;
2998
    Len := Length(Images);
2999
    if FIsMultiImageFormat or
3000
      (not FIsMultiImageFormat and (OnlyFirstLevel or (Len = 1))) then
3001
    begin
3002
      Handle := IO.OpenWrite(PChar(FileName));
3003
      try
3004
        if OnlyFirstLevel then
3005
          Index := 0
3006
        else
3007
          Index := -1;
3008
        // Write multi image to one file
3009
        Result := PrepareSave(Handle, Images, Index) and SaveData(Handle, Images, Index);
3010
      finally
3011
        IO.Close(Handle);
3012
      end;
3013
    end
3014
    else
3015
    begin
3016
      // Write multi image to file sequence
3017
      Ext := ExtractFileExt(FileName);
3018
      FName := ChangeFileExt(FileName, '');
3019
      Result := True;
3020
      for I := 0 to Len - 1 do
3021
      begin
3022
        Handle := IO.OpenWrite(PChar(Format(FName + '%.3d' + Ext, [I])));
3023
        try
3024
          Index := I;
3025
          Result := Result and PrepareSave(Handle, Images, Index) and
3026
            SaveData(Handle, Images, Index);
3027
          if not Result then
3028
            Break;
3029
        finally
3030
          IO.Close(Handle);
3031
        end;
3032
      end;
3033
    end;
3034
  except
3035
    RaiseImaging(SErrorSavingFile, [FileName, FExtensions[0]]);
3036
  end;
3037
end;
3038
3039
function TImageFileFormat.SaveToStream(Stream: TStream;
3040
  const Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
3041
var
3042
  Handle: TImagingHandle;
3043
  Len, Index, I: LongInt;
3044
  OldPosition: Int64;
3045
begin
3046
  Result := False;
3047
  OldPosition := Stream.Position;
3048
  if FCanSave and TestImagesInArray(Images) then
3049
  try
3050
    SetStreamIO;
3051
    Handle := IO.OpenWrite(PChar(Stream));
3052
    try
3053
      if FIsMultiImageFormat or OnlyFirstLevel then
3054
      begin
3055
        if OnlyFirstLevel then
3056
          Index := 0
3057
        else
3058
          Index := -1;
3059
        // Write multi image in one run
3060
        Result := PrepareSave(Handle, Images, Index) and SaveData(Handle, Images, Index);
3061
      end
3062
      else
3063
      begin
3064
        // Write multi image to sequence
3065
        Result := True;
3066
        Len := Length(Images);
3067
        for I := 0 to Len - 1 do
3068
        begin
3069
          Index := I;
3070
          Result := Result and PrepareSave(Handle, Images, Index) and
3071
            SaveData(Handle, Images, Index);
3072
          if not Result then
3073
            Break;
3074
        end;
3075
      end;
3076
    finally
3077
      IO.Close(Handle);
3078
    end;
3079
  except
3080
    Stream.Position := OldPosition;
3081
    RaiseImaging(SErrorSavingStream, [@Stream, FExtensions[0]]);
3082
  end;
3083
end;
3084
3085
function TImageFileFormat.SaveToMemory(Data: Pointer; var Size: LongInt;
3086
  const Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
3087
var
3088
  Handle: TImagingHandle;
3089
  Len, Index, I: LongInt;
3090
  IORec: TMemoryIORec;
3091
begin
3092
  Result := False;
3093
  if FCanSave and TestImagesInArray(Images) then
3094
  try
3095
    SetMemoryIO;
3096
    IORec := PrepareMemIO(Data, Size);
3097
    Handle := IO.OpenWrite(PChar(@IORec));
3098
    try
3099
      if FIsMultiImageFormat or OnlyFirstLevel then
3100
      begin
3101
        if OnlyFirstLevel then
3102
          Index := 0
3103
        else
3104
          Index := -1;
3105
        // Write multi image in one run
3106
        Result := PrepareSave(Handle, Images, Index) and SaveData(Handle, Images, Index);
3107
      end
3108
      else
3109
      begin
3110
        // Write multi image to sequence
3111
        Result := True;
3112
        Len := Length(Images);
3113
        for I := 0 to Len - 1 do
3114
        begin
3115
          Index := I;
3116
          Result := Result and PrepareSave(Handle, Images, Index) and
3117
            SaveData(Handle, Images, Index);
3118
          if not Result then
3119
            Break;
3120
        end;
3121
      end;
3122
      Size := IORec.Position;
3123
    finally
3124
      IO.Close(Handle);
3125
    end;
3126
  except
3127
    RaiseImaging(SErrorSavingMemory, [Data, Size, FExtensions[0]]);
3128
  end;
3129
end;
3130
3131
function TImageFileFormat.MakeCompatible(const Image: TImageData;
3132
  var Compatible: TImageData; out MustBeFreed: Boolean): Boolean;
3133
begin
3134
  InitImage(Compatible);
3135
3136
  if SaveOverrideFormat <> ifUnknown then
3137
  begin
3138
    // Save format override is active. Clone input and convert it to override format.
3139
    CloneImage(Image, Compatible);
3140
    ConvertImage(Compatible, SaveOverrideFormat);
3141
    // Now check if override format is supported by file format. If it is not
3142
    // then file format specific conversion (virtual method) is called.
3143
    Result := IsSupported(Compatible);
3144
    if not Result then
3145
    begin
3146
      ConvertToSupported(Compatible, GetFormatInfo(Compatible.Format));
3147
      Result := IsSupported(Compatible);
3148
    end;
3149
  end     // Add IsCompatible function! not only checking by Format
3150
  else if IsSupported(Image) then
3151
  begin
3152
    // No save format override and input is in format supported by this
3153
    // file format. Just copy Image's fields to Compatible
3154
    Compatible := Image;
3155
    Result := True;
3156
  end
3157
  else
3158
  begin
3159
    // No override and input's format is not compatible with file format.
3160
    // Clone it and the call file format specific conversion (virtual method).
3161
    CloneImage(Image, Compatible);
3162
    ConvertToSupported(Compatible, GetFormatInfo(Compatible.Format));
3163
    Result := IsSupported(Compatible);
3164
  end;
3165
  // Tell the user that he must free Compatible after he's done with it
3166
  // (if necessary).
3167
  MustBeFreed := Image.Bits <> Compatible.Bits;
3168
end;
3169
3170
function TImageFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
3171
begin
3172
  Result := False;
3173
end;
3174
3175
function TImageFileFormat.TestFileName(const FileName: string): Boolean;
3176
var
3177
  I: LongInt;
3178
  OnlyName: string;
3179
begin
3180
  OnlyName := ExtractFileName(FileName);
3181
  // For each mask test if filename matches it 
3182
  for I := 0 to FMasks.Count - 1 do
3183
    if MatchFileNameMask(OnlyName, FMasks[I], False) then
3184
    begin
3185
      Result := True;
3186
      Exit;
3187
    end;
3188
  Result := False;
3189
end;
3190
3191
procedure TImageFileFormat.CheckOptionsValidity;
3192
begin
3193
end;
3194
3195
{ TOptionStack  class implementation }
3196
3197
constructor TOptionStack.Create;
3198
begin
3199
  inherited Create;
3200
  FPosition := -1;
3201
end;
3202
3203
destructor TOptionStack.Destroy;
3204
var
3205
  I: LongInt;
3206
begin
3207
  for I := 0 to OptionStackDepth - 1 do
3208
    SetLength(FStack[I], 0);
3209
  inherited Destroy;
3210
end;
3211
3212
function TOptionStack.Pop: Boolean;
3213
var
3214
  I: LongInt;
3215
begin
3216
  Result := False;
3217
  if FPosition >= 0  then
3218
  begin
3219
    SetLength(Options, Length(FStack[FPosition]));
3220
    for I := 0 to Length(FStack[FPosition]) - 1 do
3221
      if Options[I] <> nil then
3222
        Options[I]^ := FStack[FPosition, I];
3223
    Dec(FPosition);
3224
    Result := True;
3225
  end;
3226
end;
3227
3228
function TOptionStack.Push: Boolean;
3229
var
3230
  I: LongInt;
3231
begin
3232
  Result := False;
3233
  if FPosition < OptionStackDepth - 1 then
3234
  begin
3235
    Inc(FPosition);
3236
    SetLength(FStack[FPosition], Length(Options));
3237
    for I := 0 to Length(Options) - 1 do
3238
      if Options[I] <> nil then
3239
        FStack[FPosition, I] := Options[I]^;
3240
    Result := True;
3241
  end;
3242
end;
3243
3244
initialization
3245
{$IFDEF MEMCHECK}
3246
  {$IF CompilerVersion >= 18}
3247
    System.ReportMemoryLeaksOnShutdown := True;
3248
  {$IFEND}
3249
{$ENDIF}
3250
  if ImageFileFormats = nil then
3251
    ImageFileFormats := TList.Create;
3252
  InitImageFormats;
3253
  RegisterOption(ImagingColorReductionMask, @ColorReductionMask);
3254
  RegisterOption(ImagingLoadOverrideFormat, @LoadOverrideFormat);
3255
  RegisterOption(ImagingSaveOverrideFormat, @SaveOverrideFormat);
3256
  RegisterOption(ImagingMipMapFilter, @MipMapFilter);
3257
finalization
3258
  FreeOptions;
3259
  FreeImageFileFormats;
3260
3261
{
3262
  File Notes:
3263
3264
  -- TODOS ----------------------------------------------------
3265
    - make searching for the closest color in palette much faster - MapImageToPal
3266
    - investigate CopyPixel and ComparePixels inline problems - line 550
3267
    - add to low level interface function
3268
      CreateImageFromRawData(W, H, Bpp, Data, Align, Flipped, Endian, ...)
3269
      and CreateRawDataFromImage() - use these in BMP loading (align)
3270
      and PNG loading (endian)
3271
    - add loading of multi images from file sequence
3272
    - do not load all frames when only one is required, possible?
3273
      (LoadImageFromFile on MNG/DDS)
3274
3275
  -- 0.23 Changes/Bug Fixes -----------------------------------
3276
    - MakePaletteForImages now works correctly for indexed and special format images
3277
    - Fixed bug in StretchRect: Image was not properly stretched if
3278
      src and dst dimensions differed only in height.
3279
    - ConvertImage now fills new image with zeroes to avoid random data in
3280
      some conversions (RGB->XRGB)
3281
    - Changed RegisterOption procedure to function
3282
    - Changed bunch of palette functions from low level interface to procedure
3283
      (there was no reason for them to be functions).
3284
    - Changed FreeImage and FreeImagesInArray functions to procedures.
3285
    - Added many assertions, come try-finally, other checks, and small code
3286
      and doc changes.
3287
3288
  -- 0.21 Changes/Bug Fixes -----------------------------------
3289
    - GenerateMipMaps threw failed assertion when input was indexed or special,
3290
      fixed.
3291
    - Added CheckOptionsValidity to TImageFileFormat and its decendants.
3292
    - Unit ImagingExtras which registers file formats in Extras package
3293
      is now automatically added to uses clause if LINK_EXTRAS symbol is
3294
      defined in ImagingOptions.inc file.
3295
    - Added EnumFileFormats function to low level interface.
3296
    - Fixed bug in SwapChannels which could cause AV when swapping alpha
3297
      channel of A8R8G8B8 images.
3298
    - Converting loaded images to ImagingOverrideFormat is now done
3299
      in PostLoadCheck method to avoid code duplicity.
3300
    - Added GetFileFormatCount and GetFileFormatAtIndex functions
3301
    - Bug in ConvertImage: if some format was converted to similar format
3302
      only with swapped channels (R16G16B16<>B16G16R16) then channels were
3303
      swapped correctly but new data format (swapped one) was not set.
3304
    - Made TImageFileFormat.MakeCompatible public non-virtual method
3305
      (and modified its function). Created new virtual
3306
      ConvertToSupported which should be overriden by descendants.
3307
      Main reason for doint this is to avoid duplicate code that was in all
3308
      TImageFileFormat's descendants.
3309
    - Changed TImageFileFormat.GetFormatInfo's result type to TImageFormatInfo.
3310
    - Split overloaded FindImageFileFormat functions to
3311
      FindImageFileFormatByClass and FindImageFileFormatByExt and created new
3312
      FindImageFileFormatByName which operates on whole filenames.
3313
    - Function GetExtensionFilterIndex renamed to GetFileNameFilterIndex
3314
      (because it now works with filenames not extensions).
3315
    - DetermineFileFormat now first searches by filename and if not found
3316
      then by data.
3317
    - Added TestFileName method to TImageFileFormat.
3318
    - Updated GetImageFileFormatsFilter to uses Masks instead of Extensions
3319
      property of TImageFileFormat. Also you can now request
3320
      OpenDialog and SaveDialog type filters
3321
    - Added Masks property and AddMasks method to TImageFileFormat.
3322
      AddMasks replaces AddExtensions, it uses filename masks instead
3323
      of sime filename extensions to identify supported files.
3324
    - Changed TImageFileFormat.LoadData procedure to function and
3325
      moved varios duplicate code from its descandats (check index,...)
3326
      here to TImageFileFormat helper methods.
3327
    - Changed TImageFileFormat.SaveData procedure to function and
3328
      moved varios duplicate code from its descandats (check index,...)
3329
      here to TImageFileFormat helper methods.
3330
    - Removed RAISE_EXCEPTIONS define, exceptions are now raised everytime
3331
    - Added MustBeFreed parameter to TImageFileFormat.MakeComptible method
3332
      that indicates that compatible image returned by this method must be
3333
      freed after its usage.
3334
3335
  -- 0.19 Changes/Bug Fixes -----------------------------------
3336
    - fixed bug in NewImage: if given format was ifDefault it wasn't
3337
      replaced with DefaultImageFormat constant which caused problems later
3338
      in other units 
3339
    - fixed bug in RotateImage which caused that rotated special format
3340
      images were whole black
3341
    - LoadImageFromXXX and LoadMultiImageFromXXX now use DetermineXXXFormat
3342
      when choosing proper loader, this eliminated need for Ext parameter
3343
      in stream and memory loading functions
3344
    - added GetVersionStr function
3345
    - fixed bug in ResizeImage which caued indexed images to lose their
3346
      palette during process resulting in whole black image
3347
    - Clipping in ...Rect functions now uses clipping procs from ImagingUtility,
3348
      it also works better
3349
    - FillRect optimization for 8, 16, and 32 bit formats
3350
    - added pixel set/get functions to low level interface:
3351
      GetPixelDirect, SetPixelDirect, GetPixel32, SetPixel32,
3352
      GetPixelFP, SetPixelFP
3353
    - removed GetPixelBytes low level intf function - redundant
3354
      (same data can be obtained by GetImageFormatInfo)
3355
    - made small changes in many parts of library to compile
3356
      on AMD64 CPU (Linux with FPC)
3357
    - changed InitImage to procedure (function was pointless)
3358
    - Method TestFormat of TImageFileFormat class made public
3359
      (was protected)
3360
    - added function IsFileFormatSupported to low level interface
3361
      (contributed by Paul Michell)
3362
    - fixed some missing format arguments from error strings
3363
      which caused Format function to raise exception
3364
    - removed forgotten debug code that disabled filtered resizing of images with
3365
      channel bitcounts > 8
3366
3367
  -- 0.17 Changes/Bug Fixes -----------------------------------
3368
    - changed order of parameters of CopyRect function  
3369
    - GenerateMipMaps now filters mipmap levels
3370
    - ResizeImage functions was extended to allow bilinear and bicubic filtering
3371
    - added StretchRect function to low level interface
3372
    - added functions GetImageFileFormatsFilter, GetFilterIndexExtension,
3373
      and GetExtensionFilterIndex
3374
3375
  -- 0.15 Changes/Bug Fixes -----------------------------------
3376
    - added function RotateImage to low level interface
3377
    - moved TImageFormatInfo record and types required by it to
3378
      ImagingTypes unit, changed GetImageFormatInfo low level
3379
      interface function to return TImageFormatInfo instead of short info
3380
    - added checking of options values validity before they are used
3381
    - fixed possible memory leak in CloneImage
3382
    - added ReplaceColor function to low level interface
3383
    - new function FindImageFileFormat by class added
3384
3385
  -- 0.13 Changes/Bug Fixes -----------------------------------
3386
    - added DetermineFileFormat, DetermineStreamFormat, DetermineMemoryFormat,
3387
      GetPixelsSize functions to low level interface
3388
    - added NewPalette, CopyPalette, FreePalette functions
3389
      to low level interface
3390
    - added MapImageToPalette, FillRect, SplitImage, MakePaletteForImages
3391
      functions to low level interface
3392
    - fixed buggy FillCustomPalette function (possible div by zero and others)
3393
    - added CopyRect function to low level interface
3394
    - Member functions of TImageFormatInfo record implemented for all formats
3395
    - before saving images TestImagesInArray is called now
3396
    - added TestImagesInArray function to low level interface
3397
    - added GenerateMipMaps function to low level interface
3398
    - stream position in load/save from/to stream is now set to position before
3399
      function was called if error occurs
3400
    - when error occured during load/save from/to file file handle
3401
      was not released
3402
    - CloneImage returned always False
3403
3404
}
3405
end.
3406