DEADSOFTWARE

hopefully no more windows
[d2df-editor.git] / src / lib / vampimg / Imaging.pas
1 {
2 Vampyre Imaging Library
3 by Marek Mauder
4 http://imaginglib.sourceforge.net
6 The contents of this file are used with permission, subject to the Mozilla
7 Public License Version 1.1 (the "License"); you may not use this file except
8 in compliance with the License. You may obtain a copy of the License at
9 http://www.mozilla.org/MPL/MPL-1.1.html
11 Software distributed under the License is distributed on an "AS IS" basis,
12 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
13 the specific language governing rights and limitations under the License.
15 Alternatively, the contents of this file may be used under the terms of the
16 GNU Lesser General Public License (the "LGPL License"), in which case the
17 provisions of the LGPL License are applicable instead of those above.
18 If you wish to allow use of your version of this file only under the terms
19 of the LGPL License and not to allow others to use your version of this file
20 under the MPL, indicate your decision by deleting the provisions above and
21 replace them with the notice and other provisions required by the LGPL
22 License. If you do not delete the provisions above, a recipient may use
23 your version of this file under either the MPL or the LGPL License.
25 For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
26 }
28 { This unit is heart of Imaging library. It contains basic functions for
29 manipulating image data as well as various image file format support.}
30 unit Imaging;
32 {$I ImagingOptions.inc}
34 interface
36 uses
37 SysUtils, Classes, Types, ImagingTypes;
39 type
40 { Default Imaging excepton class }
41 EImagingError = class(Exception);
42 { Raised when function receives bad image (not passed TestImage).}
43 EImagingBadImage = class(Exception)
44 public
45 constructor Create;
46 end;
48 { Dynamic array of TImageData records }
49 TDynImageDataArray = array of TImageData;
52 { ------------------------------------------------------------------------
53 Low Level Interface Functions
54 ------------------------------------------------------------------------}
56 { General Functions }
58 { Initializes image (all is set to zeroes). Call this for each image
59 before using it (before calling every other function) to be sure there
60 are no random-filled bytes (which would cause errors later).}
61 procedure InitImage(var Image: TImageData);
62 { Creates empty image of given dimensions and format. Image is filled with
63 transparent black color (A=0, R=0, G=0, B=0).}
64 function NewImage(Width, Height: LongInt; Format: TImageFormat;
65 var Image: TImageData): Boolean;
66 { Returns True if given TImageData record is valid.}
67 function TestImage(const Image: TImageData): Boolean;
68 { Frees given image data. Ater this call image is in the same state
69 as after calling InitImage. If image is not valid (dost not pass TestImage
70 test) it is only zeroed by calling InitImage.}
71 procedure FreeImage(var Image: TImageData);
72 { Call FreeImage() on all images in given dynamic array and sets its
73 length to zero.}
74 procedure FreeImagesInArray(var Images: TDynImageDataArray);
75 { Returns True if all TImageData records in given array are valid. Returns False
76 if at least one is invalid or if array is empty.}
77 function TestImagesInArray(const Images: TDynImageDataArray): Boolean;
78 { Checks given file for every supported image file format and if
79 the file is in one of them returns its string identifier
80 (which can be used in LoadFromStream/LoadFromMem type functions).
81 If file is not in any of the supported formats empty string is returned.}
82 function DetermineFileFormat(const FileName: string): string;
83 { Checks given stream for every supported image file format and if
84 the stream is in one of them returns its string identifier
85 (which can be used in LoadFromStream/LoadFromMem type functions).
86 If stream is not in any of the supported formats empty string is returned.}
87 function DetermineStreamFormat(Stream: TStream): string;
88 { Checks given memory for every supported image file format and if
89 the memory is in one of them returns its string identifier
90 (which can be used in LoadFromStream/LoadFromMem type functions).
91 If memory is not in any of the supported formats empty string is returned.}
92 function DetermineMemoryFormat(Data: Pointer; Size: LongInt): string;
93 { Checks that an apropriate file format is supported purely from inspecting
94 the given file name's extension (not contents of the file itself).
95 The file need not exist.}
96 function IsFileFormatSupported(const FileName: string): Boolean;
97 { Enumerates all registered image file formats. Descriptive name,
98 default extension, masks (like '*.jpg,*.jfif') and some capabilities
99 of each format are returned. To enumerate all formats start with Index at 0 and
100 call EnumFileFormats with given Index in loop until it returns False (Index is
101 automatically increased by 1 in function's body on successful call).}
102 function EnumFileFormats(var Index: LongInt; var Name, DefaultExt, Masks: string;
103 var CanSaveImages, IsMultiImageFormat: Boolean): Boolean;
105 { Loading Functions }
107 { Loads single image from given file.}
108 function LoadImageFromFile(const FileName: string; var Image: TImageData): Boolean;
109 { Loads single image from given stream. If function fails stream position
110 is not changed.}
111 function LoadImageFromStream(Stream: TStream; var Image: TImageData): Boolean;
112 { Loads single image from given memory location.}
113 function LoadImageFromMemory(Data: Pointer; Size: LongInt; var Image: TImageData): Boolean;
114 { Loads multiple images from given file.}
115 function LoadMultiImageFromFile(const FileName: string;
116 var Images: TDynImageDataArray): Boolean;
117 { Loads multiple images from given stream. If function fails stream position
118 is not changed.}
119 function LoadMultiImageFromStream(Stream: TStream;
120 var Images: TDynImageDataArray): Boolean;
121 { Loads multiple images from given memory location.}
122 function LoadMultiImageFromMemory(Data: Pointer; Size: LongInt;
123 var Images: TDynImageDataArray): Boolean;
125 { Saving Functions }
127 { Saves single image to given file.}
128 function SaveImageToFile(const FileName: string; const Image: TImageData): Boolean;
129 { Saves single image to given stream. If function fails stream position
130 is not changed. Ext identifies desired image file format (jpg, png, dds, ...).}
131 function SaveImageToStream(const Ext: string; Stream: TStream;
132 const Image: TImageData): Boolean;
133 { Saves single image to given memory location. Memory must be allocated and its
134 size is passed in Size parameter in which number of written bytes is returned.
135 Ext identifies desired image file format (jpg, png, dds, ...).}
136 function SaveImageToMemory(const Ext: string; Data: Pointer; var Size: LongInt;
137 const Image: TImageData): Boolean;
138 { Saves multiple images to given file. If format supports
139 only single level images and there are multiple images to be saved,
140 they are saved as sequence of files img000.jpg, img001.jpg ....).}
141 function SaveMultiImageToFile(const FileName: string;
142 const Images: TDynImageDataArray): Boolean;
143 { Saves multiple images to given stream. If format supports
144 only single level images and there are multiple images to be saved,
145 they are saved one after another to the stream. If function fails stream
146 position is not changed. Ext identifies desired image file format (jpg, png, dds, ...).}
147 function SaveMultiImageToStream(const Ext: string; Stream: TStream;
148 const Images: TDynImageDataArray): Boolean;
149 { Saves multiple images to given memory location. If format supports
150 only single level images and there are multiple images to be saved,
151 they are saved one after another to the memory. Memory must be allocated and
152 its size is passed in Size parameter in which number of written bytes is returned.
153 Ext identifies desired image file format (jpg, png, dds, ...).}
154 function SaveMultiImageToMemory(const Ext: string; Data: Pointer;
155 var Size: LongInt; const Images: TDynImageDataArray): Boolean;
157 { Manipulation Functions }
159 { Creates identical copy of image data. Clone should be initialized
160 by InitImage or it should be vaild image which will be freed by CloneImage.}
161 function CloneImage(const Image: TImageData; var Clone: TImageData): Boolean;
162 { Converts image to the given format.}
163 function ConvertImage(var Image: TImageData; DestFormat: TImageFormat): Boolean;
164 { Flips given image. Reverses the image along its horizontal axis - the top
165 becomes the bottom and vice versa.}
166 function FlipImage(var Image: TImageData): Boolean;
167 { Mirrors given image. Reverses the image along its vertical axis \97 the left
168 side becomes the right and vice versa.}
169 function MirrorImage(var Image: TImageData): Boolean;
170 { Resizes given image to new dimensions. Nearest, bilinear, or bicubic filtering
171 can be used. Input Image must already be created - use NewImage to create new images.}
172 function ResizeImage(var Image: TImageData; NewWidth, NewHeight: LongInt;
173 Filter: TResizeFilter): Boolean;
174 { Swaps SrcChannel and DstChannel color or alpha channels of image.
175 Use ChannelRed, ChannelBlue, ChannelGreen, ChannelAlpha constants to
176 identify channels.}
177 function SwapChannels(var Image: TImageData; SrcChannel, DstChannel: LongInt): Boolean;
178 { Reduces the number of colors of the Image. Currently MaxColors must be in
179 range <2, 4096>. Color reduction works also for alpha channel. Note that for
180 large images and big number of colors it can be very slow.
181 Output format of the image is the same as input format.}
182 function ReduceColors(var Image: TImageData; MaxColors: LongInt): Boolean;
183 { Generates mipmaps for image. Levels is the number of desired mipmaps levels
184 with zero (or some invalid number) meaning all possible levels.}
185 function GenerateMipMaps(const Image: TImageData; Levels: LongInt;
186 var MipMaps: TDynImageDataArray): Boolean;
187 { Maps image to existing palette producing image in ifIndex8 format.
188 Pal must be allocated to at least Entries * SizeOf(TColor32Rec) bytes.
189 As resulting image is in 8bit indexed format Entries must be lower or
190 equal to 256.}
191 function MapImageToPalette(var Image: TImageData; Pal: PPalette32;
192 Entries: LongInt): Boolean;
193 { Splits image into XChunks x YChunks subimages. Default size of each chunk is
194 ChunkWidth x ChunkHeight. If PreserveSize si True chunks at the edges of
195 the image are also ChunkWidth x ChunkHeight sized and empty space is filled
196 with optional Fill pixels. After calling this function XChunks contains number of
197 chunks along x axis and YChunks along y axis. To access chunk [X, Y] use this
198 index: Chunks[Y * XChunks + X].}
199 function SplitImage(var Image: TImageData; var Chunks: TDynImageDataArray;
200 ChunkWidth, ChunkHeight: LongInt; var XChunks, YChunks: LongInt;
201 PreserveSize: Boolean; Fill: Pointer = nil): Boolean;
202 { Creates palette with MaxColors based on the colors of images in Images array.
203 Use it when you want to convert several images to indexed format using
204 single palette for all of them. If ConvertImages is True images in array
205 are converted to indexed format using resulting palette. if it is False
206 images are left intact and only resulting palatte is returned in Pal.
207 Pal must be allocated to have at least MaxColors entries.}
208 function MakePaletteForImages(var Images: TDynImageDataArray; Pal: PPalette32;
209 MaxColors: LongInt; ConvertImages: Boolean): Boolean;
210 { Rotates image by Angle degrees counterclockwise. All angles are allowed.}
211 procedure RotateImage(var Image: TImageData; Angle: Single);
213 { Drawing/Pixel functions }
215 { Copies rectangular part of SrcImage to DstImage. No blending is performed -
216 alpha is simply copied to destination image. Operates also with
217 negative X and Y coordinates.
218 Note that copying is fastest for images in the same data format
219 (and slowest for images in special formats).}
220 function CopyRect(const SrcImage: TImageData; SrcX, SrcY, Width, Height: LongInt;
221 var DstImage: TImageData; DstX, DstY: LongInt): Boolean;
222 { Fills given rectangle of image with given pixel fill data. Fill should point
223 to the pixel in the same format as the given image is in.}
224 function FillRect(var Image: TImageData; X, Y, Width, Height: LongInt; FillColor: Pointer): Boolean;
225 { Replaces pixels with OldPixel in the given rectangle by NewPixel.
226 OldPixel and NewPixel should point to the pixels in the same format
227 as the given image is in.}
228 function ReplaceColor(var Image: TImageData; X, Y, Width, Height: LongInt;
229 OldColor, NewColor: Pointer): Boolean;
230 { Stretches the contents of the source rectangle to the destination rectangle
231 with optional resampling. No blending is performed - alpha is
232 simply copied/resampled to destination image. Note that stretching is
233 fastest for images in the same data format (and slowest for
234 images in special formats).}
235 function StretchRect(const SrcImage: TImageData; SrcX, SrcY, SrcWidth,
236 SrcHeight: LongInt; var DstImage: TImageData; DstX, DstY, DstWidth,
237 DstHeight: LongInt; Filter: TResizeFilter): Boolean;
238 { Copies pixel of Image at [X, Y] to memory pointed at by Pixel. Doesn't
239 work with special formats.}
240 procedure GetPixelDirect(const Image: TImageData; X, Y: LongInt; Pixel: Pointer);
241 { Copies pixel from memory pointed at by Pixel to Image at position [X, Y].
242 Doesn't work with special formats.}
243 procedure SetPixelDirect(const Image: TImageData; X, Y: LongInt; Pixel: Pointer);
244 { Function for getting pixel colors. Native pixel is read from Image and
245 then translated to 32 bit ARGB. Works for all image formats (except special)
246 so it is not very fast.}
247 function GetPixel32(const Image: TImageData; X, Y: LongInt): TColor32Rec;
248 { Procedure for setting pixel colors. Input 32 bit ARGB color is translated to
249 native format and then written to Image. Works for all image formats (except special)
250 so it is not very fast.}
251 procedure SetPixel32(const Image: TImageData; X, Y: LongInt; const Color: TColor32Rec);
252 { Function for getting pixel colors. Native pixel is read from Image and
253 then translated to FP ARGB. Works for all image formats (except special)
254 so it is not very fast.}
255 function GetPixelFP(const Image: TImageData; X, Y: LongInt): TColorFPRec;
256 { Procedure for setting pixel colors. Input FP ARGB color is translated to
257 native format and then written to Image. Works for all image formats (except special)
258 so it is not very fast.}
259 procedure SetPixelFP(const Image: TImageData; X, Y: LongInt; const Color: TColorFPRec);
261 { Palette Functions }
263 { Allocates new palette with Entries ARGB color entries.}
264 procedure NewPalette(Entries: LongInt; var Pal: PPalette32);
265 { Frees given palette.}
266 procedure FreePalette(var Pal: PPalette32);
267 { Copies Count palette entries from SrcPal starting at index SrcIdx to
268 DstPal at index DstPal.}
269 procedure CopyPalette(SrcPal, DstPal: PPalette32; SrcIdx, DstIdx, Count: LongInt);
270 { Returns index of color in palette or index of nearest color if exact match
271 is not found. Pal must have at least Entries color entries.}
272 function FindColor(Pal: PPalette32; Entries: LongInt; Color: TColor32): LongInt;
273 { Creates grayscale palette where each color channel has the same value.
274 Pal must have at least Entries color entries.}
275 procedure FillGrayscalePalette(Pal: PPalette32; Entries: LongInt);
276 { Creates palette with given bitcount for each channel.
277 2^(RBits + GBits + BBits) should be equl to Entries. Examples:
278 (3, 3, 2) will create palette with all possible colors of R3G3B2 format
279 and (8, 0, 0) will create palette with 256 shades of red.
280 Pal must be allocated to at least Entries * SizeOf(TColor32Rec) bytes.}
281 procedure FillCustomPalette(Pal: PPalette32; Entries: LongInt; RBits, GBits,
282 BBits: Byte; Alpha: Byte = $FF);
283 { Swaps SrcChannel and DstChannel color or alpha channels of palette.
284 Use ChannelRed, ChannelBlue, ChannelGreen, ChannelAlpha constants to
285 identify channels. Pal must be allocated to at least
286 Entries * SizeOf(TColor32Rec) bytes.}
287 procedure SwapChannelsOfPalette(Pal: PPalette32; Entries, SrcChannel,
288 DstChannel: LongInt);
290 { Options Functions }
292 { Sets value of integer option specified by OptionId parameter.
293 Option Ids are constans starting ImagingXXX.}
294 function SetOption(OptionId, Value: LongInt): Boolean;
295 { Returns value of integer option specified by OptionId parameter. If OptionId is
296 invalid, InvalidOption is returned. Option Ids are constans
297 starting ImagingXXX.}
298 function GetOption(OptionId: LongInt): LongInt;
299 { Pushes current values of all options on the stack. Returns True
300 if successfull (max stack depth is 8 now). }
301 function PushOptions: Boolean;
302 { Pops back values of all options from the top of the stack. Returns True
303 if successfull (max stack depth is 8 now). }
304 function PopOptions: Boolean;
306 { Image Format Functions }
308 { Returns short information about given image format.}
309 function GetImageFormatInfo(Format: TImageFormat; out Info: TImageFormatInfo): Boolean;
310 { Returns size in bytes of Width x Height area of pixels. Works for all formats.}
311 function GetPixelsSize(Format: TImageFormat; Width, Height: LongInt): LongInt;
313 { IO Functions }
315 { User can set his own file IO functions used when loading from/saving to
316 files by this function.}
317 procedure SetUserFileIO(OpenProc: TOpenProc; CloseProc: TCloseProc; EofProc: TEofProc; SeekProc:
318 TSeekProc; TellProc: TTellProc; ReadProc: TReadProc; WriteProc: TWriteProc);
319 { Sets file IO functions to Imaging default.}
320 procedure ResetFileIO;
322 { Raw Image IO Functions }
324 procedure ReadRawImageFromFile(const FileName: string; Width, Height: Integer;
325 Format: TImageFormat; var Image: TImageData; Offset: Integer = 0; RowLength: Integer = 0);
326 procedure ReadRawImageFromStream(Stream: TStream; Width, Height: Integer;
327 Format: TImageFormat; var Image: TImageData; Offset: Integer = 0; RowLength: Integer = 0);
328 procedure ReadRawImageFromMemory(Data: Pointer; DataSize: Integer; Width, Height: Integer;
329 Format: TImageFormat; var Image: TImageData; Offset: Integer = 0; RowLength: Integer = 0);
330 procedure ReadRawImageRect(Data: Pointer; Left, Top, Width, Height: Integer;
331 var Image: TImageData; Offset: Integer = 0; RowLength: Integer = 0);
333 procedure WriteRawImageToFile(const FileName: string; const Image: TImageData;
334 Offset: Integer = 0; RowLength: Integer = 0);
335 procedure WriteRawImageToStream(Stream: TStream; const Image: TImageData;
336 Offset: Integer = 0; RowLength: Integer = 0);
337 procedure WriteRawImageToMemory(Data: Pointer; DataSize: Integer; const Image: TImageData;
338 Offset: Integer = 0; RowLength: Integer = 0);
339 procedure WriteRawImageRect(Data: Pointer; Left, Top, Width, Height: Integer;
340 const Image: TImageData; Offset: Integer = 0; RowLength: Integer = 0);
342 { Convenience/helper Functions }
344 procedure ResizeImageToFit(const SrcImage: TImageData; FitWidth, FitHeight: Integer;
345 Filter: TResizeFilter; var DestImage: TImageData);
348 { ------------------------------------------------------------------------
349 Other Imaging Stuff
350 ------------------------------------------------------------------------}
352 type
353 { Set of TImageFormat enum.}
354 TImageFormats = set of TImageFormat;
356 { Record containg set of IO functions internaly used by image loaders/savers.}
357 TIOFunctions = record
358 Open: TOpenProc;
359 Close: TCloseProc;
360 Eof: TEofProc;
361 Seek: TSeekProc;
362 Tell: TTellProc;
363 Read: TReadProc;
364 Write: TWriteProc;
365 end;
366 PIOFunctions = ^TIOFunctions;
368 type
369 TFileFormatFeature = (
370 ffLoad,
371 ffSave,
372 ffMultiImage,
373 ffReadOnSave,
374 ffProgress,
375 ffReadScanlines);
377 TFileFormatFeatures = set of TFileFormatFeature;
379 TMetadata = class;
381 { Base class for various image file format loaders/savers which
382 descend from this class. If you want to add support for new image file
383 format the best way is probably to look at TImageFileFormat descendants'
384 implementations that are already part of Imaging.}
385 {$TYPEINFO ON}
386 TImageFileFormat = class
387 private
388 FExtensions: TStringList;
389 FMasks: TStringList;
390 function GetCanLoad: Boolean;
391 function GetCanSave: Boolean;
392 function GetIsMultiImageFormat: Boolean;
393 { Does various checks and actions before LoadData method is called.}
394 function PrepareLoad(Handle: TImagingHandle; var Images: TDynImageDataArray;
395 OnlyFirstFrame: Boolean): Boolean;
396 { Processes some actions according to result of LoadData.}
397 function PostLoadCheck(var Images: TDynImageDataArray; LoadResult: Boolean): Boolean;
398 { Helper function to be called in SaveData methods of descendants (ensures proper
399 index and sets FFirstIdx and FLastIdx for multi-images).}
400 function PrepareSave(Handle: TImagingHandle; const Images: TDynImageDataArray;
401 var Index: LongInt): Boolean;
402 { Returns file open mode used for saving images. Depends on defined Features.}
403 function GetSaveOpenMode: TOpenMode;
404 protected
405 FName: string;
406 FFeatures: TFileFormatFeatures;
407 FSupportedFormats: TImageFormats;
408 FFirstIdx, FLastIdx: LongInt;
409 FMetadata: TMetadata;
410 { Descendants must override this method and define file format name and
411 capabilities.}
412 procedure Define; virtual;
413 { Defines filename masks for this image file format. AMasks should be
414 in format '*.ext1,*.ext2,umajo.*'.}
415 procedure AddMasks(const AMasks: string);
416 function GetFormatInfo(Format: TImageFormat): TImageFormatInfo;
417 { Returns set of TImageData formats that can be saved in this file format
418 without need for conversion.}
419 function GetSupportedFormats: TImageFormats; virtual;
420 { Method which must be overrided in descendants if they' are be capable
421 of loading images. Images are already freed and length is set to zero
422 whenever this method gets called. Also Handle is assured to be valid
423 and contains data that passed TestFormat method's check.}
424 function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
425 OnlyFirstFrame: Boolean): Boolean; virtual;
426 { Method which must be overriden in descendants if they are be capable
427 of saving images. Images are checked to have length >0 and
428 that they contain valid images. For single-image file formats
429 Index contain valid index to Images array (to image which should be saved).
430 Multi-image formats should use FFirstIdx and FLastIdx fields to
431 to get all images that are to be saved.}
432 function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
433 Index: LongInt): Boolean; virtual;
434 { This method is called internaly by MakeCompatible when input image
435 is in format not supported by this file format. Image is clone of
436 MakeCompatible's input and Info is its extended format info.}
437 procedure ConvertToSupported(var Image: TImageData;
438 const Info: TImageFormatInfo); virtual;
439 { Returns True if given image is supported for saving by this file format.
440 Most file formats don't need to override this method. It checks
441 (in this base class) if Image's format is in SupportedFromats set.
442 But you may override it if you want further checks
443 (proper widht and height for example).}
444 function IsSupported(const Image: TImageData): Boolean; virtual;
445 public
446 constructor Create(AMetadata: TMetadata = nil); virtual;
447 destructor Destroy; override;
449 { Loads images from file source.}
450 function LoadFromFile(const FileName: string; var Images: TDynImageDataArray;
451 OnlyFirstLevel: Boolean = False): Boolean;
452 { Loads images from stream source.}
453 function LoadFromStream(Stream: TStream; var Images: TDynImageDataArray;
454 OnlyFirstLevel: Boolean = False): Boolean;
455 { Loads images from memory source.}
456 function LoadFromMemory(Data: Pointer; Size: LongInt;
457 var Images: TDynImageDataArray; OnlyFirstLevel: Boolean = False): Boolean;
459 { Saves images to file. If format supports only single level images and
460 there are multiple images to be saved, they are saved as sequence of
461 independent images (for example SaveToFile saves sequence of
462 files img000.jpg, img001.jpg ....).}
463 function SaveToFile(const FileName: string; const Images: TDynImageDataArray;
464 OnlyFirstLevel: Boolean = False): Boolean;
465 { Saves images to stream. If format supports only single level images and
466 there are multiple images to be saved, they are saved as sequence of
467 independent images.}
468 function SaveToStream(Stream: TStream; const Images: TDynImageDataArray;
469 OnlyFirstLevel: Boolean = False): Boolean;
470 { Saves images to memory. If format supports only single level images and
471 there are multiple images to be saved, they are saved as sequence of
472 independent images. Data must be already allocated and their size passed
473 as Size parameter, number of written bytes is then returned in the same
474 parameter.}
475 function SaveToMemory(Data: Pointer; var Size: LongInt;
476 const Images: TDynImageDataArray; OnlyFirstLevel: Boolean = False): Boolean;
478 { Makes Image compatible with this file format (that means it is in one
479 of data formats in Supported formats set). If input is already
480 in supported format then Compatible just use value from input
481 (Compatible := Image) so must not free it after you are done with it
482 (image bits pointer points to input image's bits).
483 If input is not in supported format then it is cloned to Compatible
484 and concerted to one of supported formats (which one dependeds on
485 this file format). If image is cloned MustBeFreed is set to True
486 to indicated that you must free Compatible after you are done with it.}
487 function MakeCompatible(const Image: TImageData; var Compatible: TImageData;
488 out MustBeFreed: Boolean): Boolean;
489 { Returns True if data located in source identified by Handle
490 represent valid image in current format.}
491 function TestFormat(Handle: TImagingHandle): Boolean; virtual;
492 { Resturns True if the given FileName matches filter for this file format.
493 For most formats it just checks filename extensions.
494 It uses filename masks in from Masks property so it can recognize
495 filenames like this 'umajoXXXumajo.j0j' if one of themasks is
496 'umajo*umajo.j?j'.}
497 function TestFileName(const FileName: string): Boolean;
498 { Descendants use this method to check if their options (registered with
499 constant Ids for SetOption/GetOption interface or accessible as properties
500 of descendants) have valid values and make necessary changes.}
501 procedure CheckOptionsValidity; virtual;
503 { Description of this format.}
504 property Name: string read FName;
505 { Indicates whether images in this format can be loaded.}
506 property CanLoad: Boolean read GetCanLoad;
507 { Indicates whether images in this format can be saved.}
508 property CanSave: Boolean read GetCanSave;
509 { Indicates whether images in this format can contain multiple image levels.}
510 property IsMultiImageFormat: Boolean read GetIsMultiImageFormat;
511 { List of filename extensions for this format.}
512 property Extensions: TStringList read FExtensions;
513 { List of filename masks that are used to associate filenames
514 with TImageFileFormat descendants. Typical mask looks like
515 '*.bmp' or 'texture.*' (supports file formats which use filename instead
516 of extension to identify image files).}
517 property Masks: TStringList read FMasks;
518 { Set of TImageFormats supported by saving functions of this format. Images
519 can be saved only in one those formats.}
520 property SupportedFormats: TImageFormats read GetSupportedFormats;
521 end;
522 {$TYPEINFO OFF}
524 { Class reference for TImageFileFormat class}
525 TImageFileFormatClass = class of TImageFileFormat;
527 { Physical resolution unit.}
528 TResolutionUnit = (
529 ruSizeInMicroMeters, // value is pixel size in micrometers
530 ruDpi, // value is pixels/dots per inch
531 ruDpm, // value is pixels/dots per meter
532 ruDpcm // value is pixels/dots per centimeter
533 );
535 { Class for storage of single metadata item.}
536 TMetadataItem = class
537 public
538 Id: string;
539 ImageIndex: Integer;
540 Value: Variant;
541 end;
543 { Metadata manager class.}
544 TMetadata = class
545 private
546 FLoadMetaItems: TStringList;
547 FSaveMetaItems: TStringList;
548 procedure AddMetaToList(List: TStringList; const Id: string; const Value: Variant; ImageIndex: Integer);
549 procedure ClearMetaList(List: TStringList);
550 function GetMetaById(const Id: string): Variant;
551 function GetMetaByIdMulti(const Id: string; ImageIndex: Integer): Variant;
552 function GetMetaCount: Integer;
553 function GetMetaByIdx(Index: Integer): TMetadataItem;
554 function GetSaveMetaById(const Id: string): Variant;
555 function GetSaveMetaByIdMulti(const Id: string; ImageIndex: Integer): Variant;
556 procedure TranslateUnits(ResolutionUnit: TResolutionUnit; var XRes, YRes: Single);
557 public
558 constructor Create;
559 destructor Destroy; override;
561 procedure SetMetaItem(const Id: string; const Value: Variant; ImageIndex: Integer = 0);
562 procedure SetMetaItemForSaving(const Id: string; const Value: Variant; ImageIndex: Integer = 0);
563 function HasMetaItem(const Id: string; ImageIndex: Integer = 0): Boolean;
564 function HasMetaItemForSaving(const Id: string; ImageIndex: Integer = 0): Boolean;
566 procedure ClearMetaItems;
567 procedure ClearMetaItemsForSaving;
568 function GetMetaItemName(const Id: string; ImageIndex: Integer): string;
569 { Copies loaded meta items to items-for-save stack. Use this when you want to
570 save metadata that have been just loaded (e.g. resaving image in
571 different file format but keeping the metadata).}
572 procedure CopyLoadedMetaItemsForSaving;
574 function GetPhysicalPixelSize(ResUnit: TResolutionUnit; var XSize,
575 YSize: Single; MetaForSave: Boolean = False; ImageIndex: Integer = 0): Boolean;
576 procedure SetPhysicalPixelSize(ResUnit: TResolutionUnit; XSize, YSize: Single;
577 MetaForSave: Boolean = False; ImageIndex: Integer = 0);
579 property MetaItems[const Id: string]: Variant read GetMetaById;
580 property MetaItemsMulti[const Id: string; ImageIndex: Integer]: Variant read GetMetaByIdMulti;
581 { Number of loaded metadata items.}
582 property MetaItemCount: Integer read GetMetaCount;
583 property MetaItemsByIdx[Index: Integer]: TMetadataItem read GetMetaByIdx;
584 property MetaItemsForSaving[const Id: string]: Variant read GetSaveMetaById;
585 property MetaItemsForSavingMulti[const Id: string; ImageIndex: Integer]: Variant read GetSaveMetaByIdMulti;
586 end;
588 const
589 { Metadata item id constants }
591 { Physical size of one pixel in micrometers. Type of value is Float.}
592 SMetaPhysicalPixelSizeX = 'PhysicalPixelSizeX';
593 SMetaPhysicalPixelSizeY = 'PhysicalPixelSizeY';
594 { Delay for frame of animation (how long it should stay visible) in milliseconds.
595 Type of value is Integer.}
596 SMetaFrameDelay = 'FrameDelay';
597 { Number of times animation should be looped (0 = infinite looping). Type is Int. }
598 SMetaAnimationLoops = 'AnimationLoops';
599 { Gamma correction value. Type is Float.}
600 SMetaGamma = 'Gamma';
601 { Exposure value for HDR etc. Type is Float.}
602 SMetaExposure = 'Exposure';
603 { EXIF image metadata raw blob.}
604 SMetaExifBlob = 'ExifBlob';
605 { XMP image metadata raw blob.}
606 SMetaXmpBlob = 'XmpBlob';
607 { IPTC image metadata raw blob.}
608 SMetaIptcBlob = 'IptcBlob';
610 var
611 GlobalMetadata: TMetadata;
613 { Returns symbolic name of given format.}
614 function GetFormatName(Format: TImageFormat): string;
615 { Returns string with information about given Image.}
616 function ImageToStr(const Image: TImageData): string;
617 { Returns Imaging version string in format 'Major.Minor.Patch'.}
618 function GetVersionStr: string;
619 { If Condition is True then TruePart is retured, otherwise FalsePart is returned.}
620 function IffFormat(Condition: Boolean; const TruePart, FalsePart: TImageFormat): TImageFormat;
622 { Registers new option so it can be used by SetOption and GetOption functions.
623 Returns True if registration was succesful - that is Id is valid and is
624 not already taken by another option.}
625 function RegisterOption(OptionId: LongInt; Variable: PLongInt): Boolean;
627 { Registers new image loader/saver so it can be used by LoadFrom/SaveTo
628 functions.}
629 procedure RegisterImageFileFormat(AClass: TImageFileFormatClass);
630 { Returns image format loader/saver according to given extension
631 or nil if not found.}
632 function FindImageFileFormatByExt(const Ext: string): TImageFileFormat;
633 { Returns image format loader/saver according to given filename
634 or nil if not found.}
635 function FindImageFileFormatByName(const FileName: string): TImageFileFormat;
636 { Returns image format loader/saver based on its class
637 or nil if not found or not registered.}
638 function FindImageFileFormatByClass(AClass: TImageFileFormatClass): TImageFileFormat;
639 { Returns number of registered image file format loaders/saver.}
640 function GetFileFormatCount: LongInt;
641 { Returns image file format loader/saver at given index. Index must be
642 in range [0..GetFileFormatCount - 1] otherwise nil is returned.}
643 function GetFileFormatAtIndex(Index: LongInt): TImageFileFormat;
644 { Returns filter string for usage with open and save picture dialogs
645 which contains all registered image file formats.
646 Set OpenFileFilter to True if you want filter for open dialog
647 and to False if you want save dialog filter (formats that cannot save to files
648 are not added then).
649 For open dialog filter for all known graphic files
650 (like All(*.jpg;*.png;....) is added too at the first index.}
651 function GetImageFileFormatsFilter(OpenFileFilter: Boolean): string;
652 { Returns file extension (without dot) of image format selected
653 by given filter index. Used filter string is defined by GetImageFileFormatsFilter
654 function. This function can be used with save dialogs (with filters created
655 by GetImageFileFormatsFilter) to get the extension of file format selected
656 in dialog quickly. Index is in range 1..N (as FilterIndex property
657 of TOpenDialog/TSaveDialog)}
658 function GetFilterIndexExtension(Index: LongInt; OpenFileFilter: Boolean): string;
659 { Returns filter index of image file format of file specified by FileName. Used filter
660 string is defined by GetImageFileFormatsFilter function.
661 Returned index is in range 1..N (as FilterIndex property of TOpenDialog/TSaveDialog)}
662 function GetFileNameFilterIndex(const FileName: string; OpenFileFilter: Boolean): LongInt;
664 { Returns current IO functions.}
665 function GetIO: TIOFunctions;
666 { Raises EImagingError with given message.}
667 procedure RaiseImaging(const Msg: string; const Args: array of const); overload;
668 procedure RaiseImaging(const Msg: string); overload; {$IFDEF USE_INLINE}inline;{$ENDIF}
670 const
671 SImagingLibTitle = 'Vampyre Imaging Library';
673 implementation
675 uses
676 {$IFNDEF DONT_LINK_BITMAP}
677 ImagingBitmap,
678 {$ENDIF}
679 {$IFNDEF DONT_LINK_JPEG}
680 ImagingJpeg,
681 {$ENDIF}
682 {$IF not Defined(DONT_LINK_PNG) or not Defined(DONT_LINK_MNG) or not Defined(DONT_LINK_JNG)}
683 ImagingNetworkGraphics,
684 {$IFEND}
685 {$IFNDEF DONT_LINK_GIF}
686 ImagingGif,
687 {$ENDIF}
688 {$IFNDEF DONT_LINK_DDS}
689 ImagingDds,
690 {$ENDIF}
691 {$IFNDEF DONT_LINK_TARGA}
692 ImagingTarga,
693 {$ENDIF}
694 {$IFNDEF DONT_LINK_PNM}
695 ImagingPortableMaps,
696 {$ENDIF}
697 {$IFNDEF DONT_LINK_RADHDR}
698 ImagingRadiance,
699 {$ENDIF}
700 {$IFNDEF DONT_LINK_EXTRAS}
701 ImagingExtras,
702 {$ENDIF}
703 //ImagingDebug,
704 ImagingFormats, ImagingUtility, ImagingIO, Variants;
706 resourcestring
707 SExceptMsg = 'Exception Message';
708 SAllFilter = 'All Images';
709 SUnknownFormat = 'Unknown and unsupported format';
711 SErrorFreeImage = 'Error while freeing image. %s';
712 SErrorCloneImage = 'Error while cloning image. %s';
713 SErrorFlipImage = 'Error while flipping image. %s';
714 SErrorMirrorImage = 'Error while mirroring image. %s';
715 SErrorResizeImage = 'Error while resizing image. %s';
716 SErrorSwapImage = 'Error while swapping channels of image. %s';
717 SFileFormatCanNotLoad = 'Image Format "%s" does not support loading images.';
718 SFileFormatCanNotSave = 'Image Format "%s" does not support saving images.';
719 SErrorNewImage = 'Error while creating image data with params: Width=%d ' +
720 'Height=%d Format=%s.';
721 SErrorConvertImage = 'Error while converting image to format "%s". %s';
722 SImageInfo = 'Image @%p info: Width = %dpx, Height = %dpx, ' +
723 'Format = %s, Size = %.0n %s, Bits @%p, Palette @%p.';
724 SImageInfoInvalid = 'Access violation encountered when getting info on ' +
725 'image at address %p.';
726 SFileNotValid = 'File "%s" is not valid image in "%s" format.';
727 SStreamNotValid = 'Stream %p does not contain valid image in "%s" format.';
728 SMemoryNotValid = 'Memory %p (%d Bytes) does not contain valid image ' +
729 'in "%s" format.';
730 SErrorLoadingFile = 'Error while loading images from file "%s" (file format: %s).';
731 SErrorLoadingStream = 'Error while loading images from stream %p (file format: %s).';
732 SErrorLoadingMemory = 'Error while loading images from memory %p (%d Bytes) (file format: %s).';
733 SErrorSavingFile = 'Error while saving images to file "%s" (file format: %s).';
734 SErrorSavingStream = 'Error while saving images to stream %p (file format: %s).';
735 SErrorSavingMemory = 'Error while saving images to memory %p (%d Bytes) (file format: %s).';
736 SErrorFindColor = 'Error while finding color in palette @%p with %d entries.';
737 SErrorGrayscalePalette = 'Error while filling grayscale palette @%p with %d entries.';
738 SErrorCustomPalette = 'Error while filling custom palette @%p with %d entries.';
739 SErrorSwapPalette = 'Error while swapping channels of palette @%p with %d entries.';
740 SErrorReduceColors = 'Error while reducing number of colors of image to %d. %s';
741 SErrorGenerateMipMaps = 'Error while generating %d mipmap levels for image %s';
742 SImagesNotValid = 'One or more images are not valid.';
743 SErrorCopyRect = 'Error while copying rect from image %s to image %s.';
744 SErrorMapImage = 'Error while mapping image %s to palette.';
745 SErrorFillRect = 'Error while filling rectangle X:%d Y:%d W:%d H:%d in image %s';
746 SErrorSplitImage = 'Error while splitting image %s to %dx%d sized chunks.';
747 SErrorMakePaletteForImages = 'Error while making %d color palette for %d images.';
748 SErrorNewPalette = 'Error while creating new palette with %d entries';
749 SErrorFreePalette = 'Error while freeing palette @%p';
750 SErrorCopyPalette = 'Error while copying %d entries from palette @%p to @%p';
751 SErrorReplaceColor = 'Error while replacing colors in rectangle X:%d Y:%d W:%d H:%d of image %s';
752 SErrorRotateImage = 'Error while rotating image %s by %.2n degrees';
753 SErrorStretchRect = 'Error while stretching rect from image %s to image %s.';
754 SErrorEmptyStream = 'Input stream has no data. Check Position property.';
755 SErrorInvalidInputImage = 'Invalid input image.';
757 SErrorBadImage = 'Bad image detected.';
759 const
760 // Initial size of array with options information
761 InitialOptions = 256;
762 // Max depth of the option stack
763 OptionStackDepth = 8;
764 // Do not change the default format now, its too late
765 DefaultImageFormat: TImageFormat = ifA8R8G8B8;
766 // Format used to create metadata IDs for frames loaded form multiimages.
767 SMetaIdForSubImage = '%s/%d';
769 type
770 TOptionArray = array of PLongInt;
771 TOptionValueArray = array of LongInt;
773 TOptionStack = class(TObject)
774 private
775 FStack: array[0..OptionStackDepth - 1] of TOptionValueArray;
776 FPosition: LongInt;
777 public
778 constructor Create;
779 destructor Destroy; override;
780 function Push: Boolean;
781 function Pop: Boolean;
782 end;
784 var
785 // Currently set IO functions
786 IO: TIOFunctions;
787 // List with all registered TImageFileFormat classes
788 ImageFileFormats: TList = nil;
789 // Aarray with registered options (pointers to their values)
790 Options: TOptionArray = nil;
791 // Array containing addional infomation about every image format
792 ImageFormatInfos: TImageFormatInfoArray;
793 // Stack used by PushOptions/PopOtions functions
794 OptionStack: TOptionStack = nil;
795 var
796 // Variable for ImagingColorReduction option
797 ColorReductionMask: LongInt = $FF;
798 // Variable for ImagingLoadOverrideFormat option
799 LoadOverrideFormat: TImageFormat = ifUnknown;
800 // Variable for ImagingSaveOverrideFormat option
801 SaveOverrideFormat: TImageFormat = ifUnknown;
802 // Variable for ImagingSaveOverrideFormat option
803 MipMapFilter: TSamplingFilter = sfLinear;
804 // Variable for ImagingBinaryTreshold option
805 BinaryTreshold: Integer = 128;
807 { Exceptions }
809 constructor EImagingBadImage.Create;
810 begin
811 inherited Create(SErrorBadImage);
812 end;
814 { Internal unit functions }
816 { Modifies option value to be in the allowed range. Works only
817 for options registered in this unit.}
818 function CheckOptionValue(OptionId, Value: LongInt): LongInt; forward;
819 { Sets IO functions to file IO.}
820 procedure SetFileIO; forward;
821 { Sets IO functions to stream IO.}
822 procedure SetStreamIO; forward;
823 { Sets IO functions to memory IO.}
824 procedure SetMemoryIO; forward;
825 { Inits image format infos array.}
826 procedure InitImageFormats; forward;
827 { Freew image format infos array.}
828 procedure FreeImageFileFormats; forward;
829 { Creates options array and stack.}
830 procedure InitOptions; forward;
831 { Frees options array and stack.}
832 procedure FreeOptions; forward;
834 function UpdateExceptMessage(E: Exception; const MsgToPrepend: string; const Args: array of const): Exception;
835 begin
836 Result := E;
837 E.Message := Format(MsgToPrepend, Args) + ' ' + SExceptMsg + ': ' + E.Message
838 end;
840 { ------------------------------------------------------------------------
841 Low Level Interface Functions
842 ------------------------------------------------------------------------}
844 { General Functions }
846 procedure InitImage(var Image: TImageData);
847 begin
848 FillChar(Image, SizeOf(Image), 0);
849 end;
851 function NewImage(Width, Height: LongInt; Format: TImageFormat; var Image:
852 TImageData): Boolean;
853 var
854 FInfo: PImageFormatInfo;
855 begin
856 Assert((Width > 0) and (Height >0));
857 Assert(IsImageFormatValid(Format));
858 Result := False;
859 FreeImage(Image);
860 try
861 Image.Width := Width;
862 Image.Height := Height;
863 // Select default data format if selected
864 if (Format = ifDefault) then
865 Image.Format := DefaultImageFormat
866 else
867 Image.Format := Format;
868 // Get extended format info
869 FInfo := ImageFormatInfos[Image.Format];
870 if FInfo = nil then
871 begin
872 InitImage(Image);
873 Exit;
874 end;
875 // Check image dimensions and calculate its size in bytes
876 FInfo.CheckDimensions(FInfo.Format, Image.Width, Image.Height);
877 Image.Size := FInfo.GetPixelsSize(FInfo.Format, Image.Width, Image.Height);
878 if Image.Size = 0 then
879 begin
880 InitImage(Image);
881 Exit;
882 end;
883 // Image bits are allocated and set to zeroes
884 GetMem(Image.Bits, Image.Size);
885 FillChar(Image.Bits^, Image.Size, 0);
886 // Palette is allocated and set to zeroes
887 if FInfo.PaletteEntries > 0 then
888 begin
889 GetMem(Image.Palette, FInfo.PaletteEntries * SizeOf(TColor32Rec));
890 FillChar(Image.Palette^, FInfo.PaletteEntries * SizeOf(TColor32Rec), 0);
891 end;
892 Result := TestImage(Image);
893 except
894 on E: Exception do
895 begin
896 FreeMem(Image.Bits);
897 FreeMem(Image.Palette);
898 InitImage(Image);
899 raise UpdateExceptMessage(E, SErrorNewImage, [Width, Height, GetFormatName(Format)]);
900 end;
901 end;
902 end;
904 function TestImage(const Image: TImageData): Boolean;
905 begin
906 try
907 Result := (LongInt(Image.Format) >= LongInt(Low(TImageFormat))) and
908 (LongInt(Image.Format) <= LongInt(High(TImageFormat))) and
909 (ImageFormatInfos[Image.Format] <> nil) and
910 (Assigned(ImageFormatInfos[Image.Format].GetPixelsSize) and
911 (ImageFormatInfos[Image.Format].GetPixelsSize(Image.Format,
912 Image.Width, Image.Height) = Image.Size));
913 except
914 // Possible int overflows or other errors
915 Result := False;
916 end;
917 end;
919 procedure FreeImage(var Image: TImageData);
920 begin
921 try
922 if TestImage(Image) then
923 begin
924 FreeMemNil(Image.Bits);
925 FreeMemNil(Image.Palette);
926 end;
927 InitImage(Image);
928 except
929 raise UpdateExceptMessage(GetExceptObject, SErrorFreeImage, [ImageToStr(Image)]);
930 end;
931 end;
933 procedure FreeImagesInArray(var Images: TDynImageDataArray);
934 var
935 I: LongInt;
936 begin
937 if Length(Images) > 0 then
938 begin
939 for I := 0 to Length(Images) - 1 do
940 FreeImage(Images[I]);
941 SetLength(Images, 0);
942 end;
943 end;
945 function TestImagesInArray(const Images: TDynImageDataArray): Boolean;
946 var
947 I: LongInt;
948 begin
949 if Length(Images) > 0 then
950 begin
951 Result := True;
952 for I := 0 to Length(Images) - 1 do
953 begin
954 Result := Result and TestImage(Images[I]);
955 if not Result then
956 Break;
957 end;
958 end
959 else
960 Result := False;
961 end;
963 function DetermineFileFormat(const FileName: string): string;
964 var
965 I: LongInt;
966 Fmt: TImageFileFormat;
967 Handle: TImagingHandle;
968 begin
969 Assert(FileName <> '');
970 Result := '';
971 SetFileIO;
972 Handle := IO.Open(PChar(FileName), omReadOnly);
973 try
974 // First file format according to FileName and test if the data in
975 // file is really in that format
976 for I := 0 to ImageFileFormats.Count - 1 do
977 begin
978 Fmt := TImageFileFormat(ImageFileFormats[I]);
979 if Fmt.TestFileName(FileName) and Fmt.TestFormat(Handle) then
980 begin
981 Result := Fmt.Extensions[0];
982 Exit;
983 end;
984 end;
985 // No file format was found with filename search so try data-based search
986 for I := 0 to ImageFileFormats.Count - 1 do
987 begin
988 Fmt := TImageFileFormat(ImageFileFormats[I]);
989 if Fmt.TestFormat(Handle) then
990 begin
991 Result := Fmt.Extensions[0];
992 Exit;
993 end;
994 end;
995 finally
996 IO.Close(Handle);
997 end;
998 end;
1000 function DetermineStreamFormat(Stream: TStream): string;
1001 var
1002 I: LongInt;
1003 Fmt: TImageFileFormat;
1004 Handle: TImagingHandle;
1005 begin
1006 Assert(Stream <> nil);
1007 Result := '';
1008 SetStreamIO;
1009 Handle := IO.Open(Pointer(Stream), omReadOnly);
1010 try
1011 for I := 0 to ImageFileFormats.Count - 1 do
1012 begin
1013 Fmt := TImageFileFormat(ImageFileFormats[I]);
1014 if Fmt.TestFormat(Handle) then
1015 begin
1016 Result := Fmt.Extensions[0];
1017 Exit;
1018 end;
1019 end;
1020 finally
1021 IO.Close(Handle);
1022 end;
1023 end;
1025 function DetermineMemoryFormat(Data: Pointer; Size: LongInt): string;
1026 var
1027 I: LongInt;
1028 Fmt: TImageFileFormat;
1029 Handle: TImagingHandle;
1030 IORec: TMemoryIORec;
1031 begin
1032 Assert((Data <> nil) and (Size > 0));
1033 Result := '';
1034 SetMemoryIO;
1035 IORec.Data := Data;
1036 IORec.Position := 0;
1037 IORec.Size := Size;
1038 Handle := IO.Open(@IORec, omReadOnly);
1039 try
1040 for I := 0 to ImageFileFormats.Count - 1 do
1041 begin
1042 Fmt := TImageFileFormat(ImageFileFormats[I]);
1043 if Fmt.TestFormat(Handle) then
1044 begin
1045 Result := Fmt.Extensions[0];
1046 Exit;
1047 end;
1048 end;
1049 finally
1050 IO.Close(Handle);
1051 end;
1052 end;
1054 function IsFileFormatSupported(const FileName: string): Boolean;
1055 begin
1056 Result := FindImageFileFormatByName(FileName) <> nil;
1057 end;
1059 function EnumFileFormats(var Index: LongInt; var Name, DefaultExt, Masks: string;
1060 var CanSaveImages, IsMultiImageFormat: Boolean): Boolean;
1061 var
1062 FileFmt: TImageFileFormat;
1063 begin
1064 FileFmt := GetFileFormatAtIndex(Index);
1065 Result := FileFmt <> nil;
1066 if Result then
1067 begin
1068 Name := FileFmt.Name;
1069 DefaultExt := FileFmt.Extensions[0];
1070 Masks := FileFmt.Masks.DelimitedText;
1071 CanSaveImages := FileFmt.CanSave;
1072 IsMultiImageFormat := FileFmt.IsMultiImageFormat;
1073 Inc(Index);
1074 end
1075 else
1076 begin
1077 Name := '';
1078 DefaultExt := '';
1079 Masks := '';
1080 CanSaveImages := False;
1081 IsMultiImageFormat := False;
1082 end;
1083 end;
1085 { Loading Functions }
1087 function LoadImageFromFile(const FileName: string; var Image: TImageData):
1088 Boolean;
1089 var
1090 Format: TImageFileFormat;
1091 IArray: TDynImageDataArray;
1092 I: LongInt;
1093 begin
1094 Assert(FileName <> '');
1095 Result := False;
1096 Format := FindImageFileFormatByExt(DetermineFileFormat(FileName));
1097 if Format <> nil then
1098 begin
1099 FreeImage(Image);
1100 Result := Format.LoadFromFile(FileName, IArray, True);
1101 if Result and (Length(IArray) > 0) then
1102 begin
1103 Image := IArray[0];
1104 for I := 1 to Length(IArray) - 1 do
1105 FreeImage(IArray[I]);
1106 end
1107 else
1108 Result := False;
1109 end;
1110 end;
1112 function LoadImageFromStream(Stream: TStream; var Image: TImageData): Boolean;
1113 var
1114 Format: TImageFileFormat;
1115 IArray: TDynImageDataArray;
1116 I: LongInt;
1117 begin
1118 Assert(Stream <> nil);
1119 if Stream.Size - Stream.Position = 0 then
1120 RaiseImaging(SErrorEmptyStream, []);
1121 Result := False;
1122 Format := FindImageFileFormatByExt(DetermineStreamFormat(Stream));
1123 if Format <> nil then
1124 begin
1125 FreeImage(Image);
1126 Result := Format.LoadFromStream(Stream, IArray, True);
1127 if Result and (Length(IArray) > 0) then
1128 begin
1129 Image := IArray[0];
1130 for I := 1 to Length(IArray) - 1 do
1131 FreeImage(IArray[I]);
1132 end
1133 else
1134 Result := False;
1135 end;
1136 end;
1138 function LoadImageFromMemory(Data: Pointer; Size: LongInt; var Image: TImageData): Boolean;
1139 var
1140 Format: TImageFileFormat;
1141 IArray: TDynImageDataArray;
1142 I: LongInt;
1143 begin
1144 Assert((Data <> nil) and (Size > 0));
1145 Result := False;
1146 Format := FindImageFileFormatByExt(DetermineMemoryFormat(Data, Size));
1147 if Format <> nil then
1148 begin
1149 FreeImage(Image);
1150 Result := Format.LoadFromMemory(Data, Size, IArray, True);
1151 if Result and (Length(IArray) > 0) then
1152 begin
1153 Image := IArray[0];
1154 for I := 1 to Length(IArray) - 1 do
1155 FreeImage(IArray[I]);
1156 end
1157 else
1158 Result := False;
1159 end;
1160 end;
1162 function LoadMultiImageFromFile(const FileName: string; var Images:
1163 TDynImageDataArray): Boolean;
1164 var
1165 Format: TImageFileFormat;
1166 begin
1167 Assert(FileName <> '');
1168 Result := False;
1169 Format := FindImageFileFormatByExt(DetermineFileFormat(FileName));
1170 if Format <> nil then
1171 begin
1172 FreeImagesInArray(Images);
1173 Result := Format.LoadFromFile(FileName, Images);
1174 end;
1175 end;
1177 function LoadMultiImageFromStream(Stream: TStream; var Images: TDynImageDataArray): Boolean;
1178 var
1179 Format: TImageFileFormat;
1180 begin
1181 Assert(Stream <> nil);
1182 if Stream.Size - Stream.Position = 0 then
1183 RaiseImaging(SErrorEmptyStream, []);
1184 Result := False;
1185 Format := FindImageFileFormatByExt(DetermineStreamFormat(Stream));
1186 if Format <> nil then
1187 begin
1188 FreeImagesInArray(Images);
1189 Result := Format.LoadFromStream(Stream, Images);
1190 end;
1191 end;
1193 function LoadMultiImageFromMemory(Data: Pointer; Size: LongInt;
1194 var Images: TDynImageDataArray): Boolean;
1195 var
1196 Format: TImageFileFormat;
1197 begin
1198 Assert((Data <> nil) and (Size > 0));
1199 Result := False;
1200 Format := FindImageFileFormatByExt(DetermineMemoryFormat(Data, Size));
1201 if Format <> nil then
1202 begin
1203 FreeImagesInArray(Images);
1204 Result := Format.LoadFromMemory(Data, Size, Images);
1205 end;
1206 end;
1208 { Saving Functions }
1210 function SaveImageToFile(const FileName: string; const Image: TImageData): Boolean;
1211 var
1212 Format: TImageFileFormat;
1213 IArray: TDynImageDataArray;
1214 begin
1215 Assert(FileName <> '');
1216 Result := False;
1217 Format := FindImageFileFormatByName(FileName);
1218 if Format <> nil then
1219 begin
1220 SetLength(IArray, 1);
1221 IArray[0] := Image;
1222 Result := Format.SaveToFile(FileName, IArray, True);
1223 end;
1224 end;
1226 function SaveImageToStream(const Ext: string; Stream: TStream;
1227 const Image: TImageData): Boolean;
1228 var
1229 Format: TImageFileFormat;
1230 IArray: TDynImageDataArray;
1231 begin
1232 Assert((Ext <> '') and (Stream <> nil));
1233 Result := False;
1234 Format := FindImageFileFormatByExt(Ext);
1235 if Format <> nil then
1236 begin
1237 SetLength(IArray, 1);
1238 IArray[0] := Image;
1239 Result := Format.SaveToStream(Stream, IArray, True);
1240 end;
1241 end;
1243 function SaveImageToMemory(const Ext: string; Data: Pointer; var Size: LongInt;
1244 const Image: TImageData): Boolean;
1245 var
1246 Format: TImageFileFormat;
1247 IArray: TDynImageDataArray;
1248 begin
1249 Assert((Ext <> '') and (Data <> nil) and (Size > 0));
1250 Result := False;
1251 Format := FindImageFileFormatByExt(Ext);
1252 if Format <> nil then
1253 begin
1254 SetLength(IArray, 1);
1255 IArray[0] := Image;
1256 Result := Format.SaveToMemory(Data, Size, IArray, True);
1257 end;
1258 end;
1260 function SaveMultiImageToFile(const FileName: string;
1261 const Images: TDynImageDataArray): Boolean;
1262 var
1263 Format: TImageFileFormat;
1264 begin
1265 Assert(FileName <> '');
1266 Result := False;
1267 Format := FindImageFileFormatByName(FileName);
1268 if Format <> nil then
1269 Result := Format.SaveToFile(FileName, Images);
1270 end;
1272 function SaveMultiImageToStream(const Ext: string; Stream: TStream;
1273 const Images: TDynImageDataArray): Boolean;
1274 var
1275 Format: TImageFileFormat;
1276 begin
1277 Assert((Ext <> '') and (Stream <> nil));
1278 Result := False;
1279 Format := FindImageFileFormatByExt(Ext);
1280 if Format <> nil then
1281 Result := Format.SaveToStream(Stream, Images);
1282 end;
1284 function SaveMultiImageToMemory(const Ext: string; Data: Pointer;
1285 var Size: LongInt; const Images: TDynImageDataArray): Boolean;
1286 var
1287 Format: TImageFileFormat;
1288 begin
1289 Assert((Ext <> '') and (Data <> nil) and (Size > 0));
1290 Result := False;
1291 Format := FindImageFileFormatByExt(Ext);
1292 if Format <> nil then
1293 Result := Format.SaveToMemory(Data, Size, Images);
1294 end;
1296 { Manipulation Functions }
1298 function CloneImage(const Image: TImageData; var Clone: TImageData): Boolean;
1299 var
1300 Info: PImageFormatInfo;
1301 begin
1302 Result := False;
1303 if TestImage(Image) then
1304 try
1305 if TestImage(Clone) and (Image.Bits <> Clone.Bits) then
1306 FreeImage(Clone)
1307 else
1308 InitImage(Clone);
1310 Info := ImageFormatInfos[Image.Format];
1311 Clone.Width := Image.Width;
1312 Clone.Height := Image.Height;
1313 Clone.Format := Image.Format;
1314 Clone.Size := Image.Size;
1316 if Info.PaletteEntries > 0 then
1317 begin
1318 GetMem(Clone.Palette, Info.PaletteEntries * SizeOf(TColor32Rec));
1319 Move(Image.Palette^, Clone.Palette^, Info.PaletteEntries *
1320 SizeOf(TColor32Rec));
1321 end;
1323 GetMem(Clone.Bits, Clone.Size);
1324 Move(Image.Bits^, Clone.Bits^, Clone.Size);
1325 Result := True;
1326 except
1327 raise UpdateExceptMessage(GetExceptObject, SErrorCloneImage, [ImageToStr(Image)]);
1328 end;
1329 end;
1331 function ConvertImage(var Image: TImageData; DestFormat: TImageFormat): Boolean;
1332 var
1333 NewData: Pointer;
1334 NewPal: PPalette32;
1335 NewSize, NumPixels: LongInt;
1336 SrcInfo, DstInfo: PImageFormatInfo;
1337 begin
1338 Assert(IsImageFormatValid(DestFormat));
1339 Result := False;
1340 if TestImage(Image) then
1341 with Image do
1342 try
1343 // If default format is set we use DefaultImageFormat
1344 if DestFormat = ifDefault then
1345 DestFormat := DefaultImageFormat;
1346 SrcInfo := ImageFormatInfos[Format];
1347 DstInfo := ImageFormatInfos[DestFormat];
1348 if SrcInfo = DstInfo then
1349 begin
1350 // There is nothing to convert - src is alredy in dest format
1351 Result := True;
1352 Exit;
1353 end;
1354 // Exit Src or Dest format is invalid
1355 if (SrcInfo = nil) or (DstInfo = nil) then Exit;
1356 // If dest format is just src with swapped channels we call
1357 // SwapChannels instead
1358 if (SrcInfo.RBSwapFormat = DestFormat) and
1359 (DstInfo.RBSwapFormat = SrcInfo.Format) then
1360 begin
1361 Result := SwapChannels(Image, ChannelRed, ChannelBlue);
1362 Image.Format := SrcInfo.RBSwapFormat;
1363 Exit;
1364 end;
1366 if (not SrcInfo.IsSpecial) and (not DstInfo.IsSpecial) then
1367 begin
1368 NumPixels := Width * Height;
1369 NewSize := NumPixels * DstInfo.BytesPerPixel;
1370 GetMem(NewData, NewSize);
1371 FillChar(NewData^, NewSize, 0);
1372 GetMem(NewPal, DstInfo.PaletteEntries * SizeOf(TColor32Rec));
1373 FillChar(NewPal^, DstInfo.PaletteEntries * SizeOf(TColor32Rec), 0);
1375 if SrcInfo.IsIndexed then
1376 begin
1377 // Source: indexed format
1378 if DstInfo.IsIndexed then
1379 IndexToIndex(NumPixels, Bits, NewData, SrcInfo, DstInfo, Palette, NewPal)
1380 else if DstInfo.HasGrayChannel then
1381 IndexToGray(NumPixels, Bits, NewData, SrcInfo, DstInfo, Palette)
1382 else if DstInfo.IsFloatingPoint then
1383 IndexToFloat(NumPixels, Bits, NewData, SrcInfo, DstInfo, Palette)
1384 else
1385 IndexToChannel(NumPixels, Bits, NewData, SrcInfo, DstInfo, Palette);
1386 end
1387 else if SrcInfo.HasGrayChannel then
1388 begin
1389 // Source: grayscale format
1390 if DstInfo.IsIndexed then
1391 GrayToIndex(NumPixels, Bits, NewData, SrcInfo, DstInfo, NewPal)
1392 else if DstInfo.HasGrayChannel then
1393 GrayToGray(NumPixels, Bits, NewData, SrcInfo, DstInfo)
1394 else if DstInfo.IsFloatingPoint then
1395 GrayToFloat(NumPixels, Bits, NewData, SrcInfo, DstInfo)
1396 else
1397 GrayToChannel(NumPixels, Bits, NewData, SrcInfo, DstInfo);
1398 end
1399 else if SrcInfo.IsFloatingPoint then
1400 begin
1401 // Source: floating point format
1402 if DstInfo.IsIndexed then
1403 FloatToIndex(NumPixels, Bits, NewData, SrcInfo, DstInfo, NewPal)
1404 else if DstInfo.HasGrayChannel then
1405 FloatToGray(NumPixels, Bits, NewData, SrcInfo, DstInfo)
1406 else if DstInfo.IsFloatingPoint then
1407 FloatToFloat(NumPixels, Bits, NewData, SrcInfo, DstInfo)
1408 else
1409 FloatToChannel(NumPixels, Bits, NewData, SrcInfo, DstInfo);
1410 end
1411 else
1412 begin
1413 // Source: standard multi channel image
1414 if DstInfo.IsIndexed then
1415 ChannelToIndex(NumPixels, Bits, NewData, SrcInfo, DstInfo, NewPal)
1416 else if DstInfo.HasGrayChannel then
1417 ChannelToGray(NumPixels, Bits, NewData, SrcInfo, DstInfo)
1418 else if DstInfo.IsFloatingPoint then
1419 ChannelToFloat(NumPixels, Bits, NewData, SrcInfo, DstInfo)
1420 else
1421 ChannelToChannel(NumPixels, Bits, NewData, SrcInfo, DstInfo);
1422 end;
1424 FreeMemNil(Bits);
1425 FreeMemNil(Palette);
1426 Format := DestFormat;
1427 Bits := NewData;
1428 Size := NewSize;
1429 Palette := NewPal;
1430 end
1431 else
1432 ConvertSpecial(Image, SrcInfo, DstInfo);
1434 Assert(SrcInfo.Format <> Image.Format);
1436 Result := True;
1437 except
1438 raise UpdateExceptMessage(GetExceptObject, SErrorConvertImage, [GetFormatName(DestFormat), ImageToStr(Image)]);
1439 end;
1440 end;
1442 function FlipImage(var Image: TImageData): Boolean;
1443 var
1444 P1, P2, Buff: Pointer;
1445 WidthBytes, I: LongInt;
1446 OldFmt: TImageFormat;
1447 begin
1448 Result := False;
1449 OldFmt := Image.Format;
1450 if TestImage(Image) then
1451 with Image do
1452 try
1453 if ImageFormatInfos[OldFmt].IsSpecial then
1454 ConvertImage(Image, ifDefault);
1456 WidthBytes := Width * ImageFormatInfos[Format].BytesPerPixel;
1457 GetMem(Buff, WidthBytes);
1458 try
1459 // Swap all scanlines of image
1460 for I := 0 to Height div 2 - 1 do
1461 begin
1462 P1 := @PByteArray(Bits)[I * WidthBytes];
1463 P2 := @PByteArray(Bits)[(Height - I - 1) * WidthBytes];
1464 Move(P1^, Buff^, WidthBytes);
1465 Move(P2^, P1^, WidthBytes);
1466 Move(Buff^, P2^, WidthBytes);
1467 end;
1468 finally
1469 FreeMemNil(Buff);
1470 end;
1472 if OldFmt <> Format then
1473 ConvertImage(Image, OldFmt);
1475 Result := True;
1476 except
1477 RaiseImaging(SErrorFlipImage, [ImageToStr(Image)]);
1478 end;
1479 end;
1481 function MirrorImage(var Image: TImageData): Boolean;
1482 var
1483 Scanline: PByte;
1484 Buff: TColorFPRec;
1485 Bpp, Y, X, WidthDiv2, WidthBytes, XLeft, XRight: LongInt;
1486 OldFmt: TImageFormat;
1487 begin
1488 Result := False;
1489 OldFmt := Image.Format;
1490 if TestImage(Image) then
1491 with Image do
1492 try
1493 if ImageFormatInfos[OldFmt].IsSpecial then
1494 ConvertImage(Image, ifDefault);
1496 Bpp := ImageFormatInfos[Format].BytesPerPixel;
1497 WidthDiv2 := Width div 2;
1498 WidthBytes := Width * Bpp;
1499 // Mirror all pixels on each scanline of image
1500 for Y := 0 to Height - 1 do
1501 begin
1502 Scanline := @PByteArray(Bits)[Y * WidthBytes];
1503 XLeft := 0;
1504 XRight := (Width - 1) * Bpp;
1505 for X := 0 to WidthDiv2 - 1 do
1506 begin
1507 CopyPixel(@PByteArray(Scanline)[XLeft], @Buff, Bpp);
1508 CopyPixel(@PByteArray(Scanline)[XRight],
1509 @PByteArray(Scanline)[XLeft], Bpp);
1510 CopyPixel(@Buff, @PByteArray(Scanline)[XRight], Bpp);
1511 Inc(XLeft, Bpp);
1512 Dec(XRight, Bpp);
1513 end;
1514 end;
1516 if OldFmt <> Format then
1517 ConvertImage(Image, OldFmt);
1519 Result := True;
1520 except
1521 RaiseImaging(SErrorMirrorImage, [ImageToStr(Image)]);
1522 end;
1523 end;
1525 function ResizeImage(var Image: TImageData; NewWidth, NewHeight: LongInt;
1526 Filter: TResizeFilter): Boolean;
1527 var
1528 WorkImage: TImageData;
1529 begin
1530 Assert((NewWidth > 0) and (NewHeight > 0), 'New width or height is zero.');
1531 Result := False;
1532 if TestImage(Image) and ((Image.Width <> NewWidth) or (Image.Height <> NewHeight)) then
1533 try
1534 InitImage(WorkImage);
1535 // Create new image with desired dimensions
1536 NewImage(NewWidth, NewHeight, Image.Format, WorkImage);
1537 // Stretch pixels from old image to new one
1538 StretchRect(Image, 0, 0, Image.Width, Image.Height,
1539 WorkImage, 0, 0, WorkImage.Width, WorkImage.Height, Filter);
1540 // Free old image and assign new image to it
1541 FreeMemNil(Image.Bits);
1542 if Image.Palette <> nil then
1543 begin
1544 FreeMem(WorkImage.Palette);
1545 WorkImage.Palette := Image.Palette;
1546 end;
1547 Image := WorkImage;
1548 Result := True;
1549 except
1550 raise UpdateExceptMessage(GetExceptObject, SErrorResizeImage, [ImageToStr(Image)]);
1551 end;
1552 end;
1554 function SwapChannels(var Image: TImageData; SrcChannel, DstChannel: LongInt): Boolean;
1555 var
1556 I, NumPixels: LongInt;
1557 Info: PImageFormatInfo;
1558 Swap, Alpha: Word;
1559 Data: PByte;
1560 Pix64: TColor64Rec;
1561 PixF: TColorFPRec;
1562 SwapF: Single;
1563 begin
1564 Assert((SrcChannel in [0..3]) and (DstChannel in [0..3]));
1565 Result := False;
1566 if TestImage(Image) and (SrcChannel <> DstChannel) then
1567 with Image do
1568 try
1569 NumPixels := Width * Height;
1570 Info := ImageFormatInfos[Format];
1571 Data := Bits;
1573 if (Info.Format = ifR8G8B8) or ((Info.Format = ifA8R8G8B8) and
1574 (SrcChannel <> ChannelAlpha) and (DstChannel <> ChannelAlpha)) then
1575 begin
1576 // Swap channels of most common formats R8G8B8 and A8R8G8B8 (no alpha)
1577 for I := 0 to NumPixels - 1 do
1578 with PColor24Rec(Data)^ do
1579 begin
1580 Swap := Channels[SrcChannel];
1581 Channels[SrcChannel] := Channels[DstChannel];
1582 Channels[DstChannel] := Swap;
1583 Inc(Data, Info.BytesPerPixel);
1584 end;
1585 end
1586 else if Info.IsIndexed then
1587 begin
1588 // Swap palette channels of indexed images
1589 SwapChannelsOfPalette(Palette, Info.PaletteEntries, SrcChannel, DstChannel)
1590 end
1591 else if Info.IsFloatingPoint then
1592 begin
1593 // Swap channels of floating point images
1594 for I := 0 to NumPixels - 1 do
1595 begin
1596 FloatGetSrcPixel(Data, Info, PixF);
1597 with PixF do
1598 begin
1599 SwapF := Channels[SrcChannel];
1600 Channels[SrcChannel] := Channels[DstChannel];
1601 Channels[DstChannel] := SwapF;
1602 end;
1603 FloatSetDstPixel(Data, Info, PixF);
1604 Inc(Data, Info.BytesPerPixel);
1605 end;
1606 end
1607 else if Info.IsSpecial then
1608 begin
1609 // Swap channels of special format images
1610 ConvertImage(Image, ifDefault);
1611 SwapChannels(Image, SrcChannel, DstChannel);
1612 ConvertImage(Image, Info.Format);
1613 end
1614 else if Info.HasGrayChannel and Info.HasAlphaChannel and
1615 ((SrcChannel = ChannelAlpha) or (DstChannel = ChannelAlpha)) then
1616 begin
1617 for I := 0 to NumPixels - 1 do
1618 begin
1619 // If we have grayscale image with alpha and alpha is channel
1620 // to be swapped, we swap it. No other alternative for gray images,
1621 // just alpha and something
1622 GrayGetSrcPixel(Data, Info, Pix64, Alpha);
1623 Swap := Alpha;
1624 Alpha := Pix64.A;
1625 Pix64.A := Swap;
1626 GraySetDstPixel(Data, Info, Pix64, Alpha);
1627 Inc(Data, Info.BytesPerPixel);
1628 end;
1629 end
1630 else
1631 begin
1632 // Then do general swap on other channel image formats
1633 for I := 0 to NumPixels - 1 do
1634 begin
1635 ChannelGetSrcPixel(Data, Info, Pix64);
1636 with Pix64 do
1637 begin
1638 Swap := Channels[SrcChannel];
1639 Channels[SrcChannel] := Channels[DstChannel];
1640 Channels[DstChannel] := Swap;
1641 end;
1642 ChannelSetDstPixel(Data, Info, Pix64);
1643 Inc(Data, Info.BytesPerPixel);
1644 end;
1645 end;
1647 Result := True;
1648 except
1649 RaiseImaging(SErrorSwapImage, [ImageToStr(Image)]);
1650 end;
1651 end;
1653 function ReduceColors(var Image: TImageData; MaxColors: LongInt): Boolean;
1654 var
1655 TmpInfo: TImageFormatInfo;
1656 Data, Index: PWord;
1657 I, NumPixels: LongInt;
1658 Pal: PPalette32;
1659 Col:PColor32Rec;
1660 OldFmt: TImageFormat;
1661 begin
1662 Result := False;
1663 if TestImage(Image) then
1664 with Image do
1665 try
1666 // First create temp image info and allocate output bits and palette
1667 MaxColors := ClampInt(MaxColors, 2, High(Word));
1668 OldFmt := Format;
1669 FillChar(TmpInfo, SizeOf(TmpInfo), 0);
1670 TmpInfo.PaletteEntries := MaxColors;
1671 TmpInfo.BytesPerPixel := 2;
1672 NumPixels := Width * Height;
1673 GetMem(Data, NumPixels * TmpInfo.BytesPerPixel);
1674 GetMem(Pal, MaxColors * SizeOf(TColor32Rec));
1675 ConvertImage(Image, ifA8R8G8B8);
1676 // We use median cut algorithm to create reduced palette and to
1677 // fill Data with indices to this palette
1678 ReduceColorsMedianCut(NumPixels, Bits, PByte(Data),
1679 ImageFormatInfos[Format], @TmpInfo, MaxColors, ColorReductionMask, Pal);
1680 Col := Bits;
1681 Index := Data;
1682 // Then we write reduced colors to the input image
1683 for I := 0 to NumPixels - 1 do
1684 begin
1685 Col.Color := Pal[Index^].Color;
1686 Inc(Col);
1687 Inc(Index);
1688 end;
1689 FreeMemNil(Data);
1690 FreeMemNil(Pal);
1691 // And convert it to its original format
1692 ConvertImage(Image, OldFmt);
1693 Result := True;
1694 except
1695 RaiseImaging(SErrorReduceColors, [MaxColors, ImageToStr(Image)]);
1696 end;
1697 end;
1699 function GenerateMipMaps(const Image: TImageData; Levels: LongInt;
1700 var MipMaps: TDynImageDataArray): Boolean;
1701 var
1702 Width, Height, I, Count: LongInt;
1703 Info: TImageFormatInfo;
1704 CompatibleCopy: TImageData;
1705 begin
1706 Result := False;
1707 if TestImage(Image) then
1708 try
1709 Width := Image.Width;
1710 Height := Image.Height;
1711 // We compute number of possible mipmap levels and if
1712 // the given levels are invalid or zero we use this value
1713 Count := GetNumMipMapLevels(Width, Height);
1714 if (Levels <= 0) or (Levels > Count) then
1715 Levels := Count;
1717 // If we have special format image we create copy to allow pixel access.
1718 // This is also done in FillMipMapLevel which is called for each level
1719 // but then the main big image would be converted to compatible
1720 // for every level.
1721 GetImageFormatInfo(Image.Format, Info);
1722 if Info.IsSpecial then
1723 begin
1724 InitImage(CompatibleCopy);
1725 CloneImage(Image, CompatibleCopy);
1726 ConvertImage(CompatibleCopy, ifDefault);
1727 end
1728 else
1729 CompatibleCopy := Image;
1731 FreeImagesInArray(MipMaps);
1732 SetLength(MipMaps, Levels);
1733 CloneImage(Image, MipMaps[0]);
1735 for I := 1 to Levels - 1 do
1736 begin
1737 Width := Width shr 1;
1738 Height := Height shr 1;
1739 if Width < 1 then Width := 1;
1740 if Height < 1 then Height := 1;
1741 FillMipMapLevel(CompatibleCopy, Width, Height, MipMaps[I]);
1742 end;
1744 if CompatibleCopy.Format <> MipMaps[0].Format then
1745 begin
1746 // Must convert smaller levels to proper format
1747 for I := 1 to High(MipMaps) do
1748 ConvertImage(MipMaps[I], MipMaps[0].Format);
1749 FreeImage(CompatibleCopy);
1750 end;
1752 Result := True;
1753 except
1754 RaiseImaging(SErrorGenerateMipMaps, [Levels, ImageToStr(Image)]);
1755 end;
1756 end;
1758 function MapImageToPalette(var Image: TImageData; Pal: PPalette32;
1759 Entries: LongInt): Boolean;
1761 function FindNearestColor(Pal: PPalette32; Entries: LongInt; Col: TColor32Rec): LongInt;
1762 var
1763 I, MinDif, Dif: LongInt;
1764 begin
1765 Result := 0;
1766 MinDif := 1020;
1767 for I := 0 to Entries - 1 do
1768 with Pal[I] do
1769 begin
1770 Dif := Abs(R - Col.R);
1771 if Dif > MinDif then Continue;
1772 Dif := Dif + Abs(G - Col.G);
1773 if Dif > MinDif then Continue;
1774 Dif := Dif + Abs(B - Col.B);
1775 if Dif > MinDif then Continue;
1776 Dif := Dif + Abs(A - Col.A);
1777 if Dif < MinDif then
1778 begin
1779 MinDif := Dif;
1780 Result := I;
1781 end;
1782 end;
1783 end;
1785 var
1786 I, MaxEntries: LongInt;
1787 PIndex: PByte;
1788 PColor: PColor32Rec;
1789 CloneARGB: TImageData;
1790 Info: PImageFormatInfo;
1791 begin
1792 Assert((Entries >= 2) and (Entries <= 256));
1793 Result := False;
1795 if TestImage(Image) then
1796 try
1797 // We create clone of source image in A8R8G8B8 and
1798 // then recreate source image in ifIndex8 format
1799 // with palette taken from Pal parameter
1800 InitImage(CloneARGB);
1801 CloneImage(Image, CloneARGB);
1802 ConvertImage(CloneARGB, ifA8R8G8B8);
1803 FreeImage(Image);
1804 NewImage(CloneARGB.Width, CloneARGB.Height, ifIndex8, Image);
1806 Info := ImageFormatInfos[Image.Format];
1807 MaxEntries := Min(Info.PaletteEntries, Entries);
1808 Move(Pal^, Image.Palette^, MaxEntries * SizeOf(TColor32Rec));
1809 PIndex := Image.Bits;
1810 PColor := CloneARGB.Bits;
1812 // For every pixel of ARGB clone we find closest color in
1813 // given palette and assign its index to resulting image's pixel
1814 // procedure used here is very slow but simple and memory usage friendly
1815 // (contrary to other methods)
1816 for I := 0 to Image.Width * Image.Height - 1 do
1817 begin
1818 PIndex^ := Byte(FindNearestColor(Image.Palette, MaxEntries, PColor^));
1819 Inc(PIndex);
1820 Inc(PColor);
1821 end;
1823 FreeImage(CloneARGB);
1824 Result := True;
1825 except
1826 raise UpdateExceptMessage(GetExceptObject, SErrorMapImage, [ImageToStr(Image)]);
1827 end;
1828 end;
1830 function SplitImage(var Image: TImageData; var Chunks: TDynImageDataArray;
1831 ChunkWidth, ChunkHeight: LongInt; var XChunks, YChunks: LongInt;
1832 PreserveSize: Boolean; Fill: Pointer): Boolean;
1833 var
1834 X, Y, XTrunc, YTrunc: LongInt;
1835 NotOnEdge: Boolean;
1836 Info: PImageFormatInfo;
1837 OldFmt: TImageFormat;
1838 begin
1839 Assert((ChunkWidth > 0) and (ChunkHeight > 0));
1840 Result := False;
1841 OldFmt := Image.Format;
1842 FreeImagesInArray(Chunks);
1844 if TestImage(Image) then
1845 try
1846 Info := ImageFormatInfos[Image.Format];
1847 if Info.IsSpecial then
1848 ConvertImage(Image, ifDefault);
1850 // We compute make sure that chunks are not larger than source image or negative
1851 ChunkWidth := ClampInt(ChunkWidth, 0, Image.Width);
1852 ChunkHeight := ClampInt(ChunkHeight, 0, Image.Height);
1853 // Number of chunks along X and Y axes is computed
1854 XChunks := Trunc(Ceil(Image.Width / ChunkWidth));
1855 YChunks := Trunc(Ceil(Image.Height / ChunkHeight));
1856 SetLength(Chunks, XChunks * YChunks);
1858 // For every chunk we create new image and copy a portion of
1859 // the source image to it. If chunk is on the edge of the source image
1860 // we fill enpty space with Fill pixel data if PreserveSize is set or
1861 // make the chunk smaller if it is not set
1862 for Y := 0 to YChunks - 1 do
1863 for X := 0 to XChunks - 1 do
1864 begin
1865 // Determine if current chunk is on the edge of original image
1866 NotOnEdge := ((X < XChunks - 1) and (Y < YChunks - 1)) or
1867 ((Image.Width mod ChunkWidth = 0) and (Image.Height mod ChunkHeight = 0));
1869 if PreserveSize or NotOnEdge then
1870 begin
1871 // We should preserve chunk sizes or we are somewhere inside original image
1872 NewImage(ChunkWidth, ChunkHeight, Image.Format, Chunks[Y * XChunks + X]);
1873 if (not NotOnEdge) and (Fill <> nil) then
1874 FillRect(Chunks[Y * XChunks + X], 0, 0, ChunkWidth, ChunkHeight, Fill);
1875 CopyRect(Image, X * ChunkWidth, Y * ChunkHeight, ChunkWidth, ChunkHeight,
1876 Chunks[Y * XChunks + X], 0, 0);
1877 end
1878 else
1879 begin
1880 // Create smaller edge chunk
1881 XTrunc := Image.Width - X * ChunkWidth;
1882 YTrunc := Image.Height - Y * ChunkHeight;
1883 NewImage(XTrunc, YTrunc, Image.Format, Chunks[Y * XChunks + X]);
1884 CopyRect(Image, X * ChunkWidth, Y * ChunkHeight, XTrunc, YTrunc,
1885 Chunks[Y * XChunks + X], 0, 0);
1886 end;
1888 // If source image is in indexed format we copy its palette to chunk
1889 if Info.IsIndexed then
1890 begin
1891 Move(Image.Palette^, Chunks[Y * XChunks + X].Palette^,
1892 Info.PaletteEntries * SizeOf(TColor32Rec));
1893 end;
1894 end;
1896 if OldFmt <> Image.Format then
1897 begin
1898 ConvertImage(Image, OldFmt);
1899 for X := 0 to Length(Chunks) - 1 do
1900 ConvertImage(Chunks[X], OldFmt);
1901 end;
1903 Result := True;
1904 except
1905 raise UpdateExceptMessage(GetExceptObject, SErrorSplitImage,
1906 [ImageToStr(Image), ChunkWidth, ChunkHeight]);
1907 end;
1908 end;
1910 function MakePaletteForImages(var Images: TDynImageDataArray; Pal: PPalette32;
1911 MaxColors: LongInt; ConvertImages: Boolean): Boolean;
1912 var
1913 I: Integer;
1914 SrcInfo, DstInfo: PImageFormatInfo;
1915 Target, TempImage: TImageData;
1916 DstFormat: TImageFormat;
1917 begin
1918 Assert((Pal <> nil) and (MaxColors > 0));
1919 Result := False;
1920 InitImage(TempImage);
1922 if TestImagesInArray(Images) then
1923 try
1924 // Null the color histogram
1925 ReduceColorsMedianCut(0, nil, nil, nil, nil, 0, 0, nil, [raCreateHistogram]);
1926 for I := 0 to Length(Images) - 1 do
1927 begin
1928 SrcInfo := ImageFormatInfos[Images[I].Format];
1929 if SrcInfo.IsIndexed or SrcInfo.IsSpecial then
1930 begin
1931 // create temp image in supported format for updating histogram
1932 CloneImage(Images[I], TempImage);
1933 ConvertImage(TempImage, ifA8R8G8B8);
1934 SrcInfo := ImageFormatInfos[TempImage.Format];
1935 end
1936 else
1937 TempImage := Images[I];
1939 // Update histogram with colors of each input image
1940 ReduceColorsMedianCut(TempImage.Width * TempImage.Height, TempImage.Bits,
1941 nil, SrcInfo, nil, MaxColors, ColorReductionMask, nil, [raUpdateHistogram]);
1943 if Images[I].Bits <> TempImage.Bits then
1944 FreeImage(TempImage);
1945 end;
1946 // Construct reduced color map from the histogram
1947 ReduceColorsMedianCut(0, nil, nil, nil, nil, MaxColors, ColorReductionMask,
1948 Pal, [raMakeColorMap]);
1950 if ConvertImages then
1951 begin
1952 DstFormat := ifIndex8;
1953 DstInfo := ImageFormatInfos[DstFormat];
1954 MaxColors := Min(DstInfo.PaletteEntries, MaxColors);
1956 for I := 0 to Length(Images) - 1 do
1957 begin
1958 SrcInfo := ImageFormatInfos[Images[I].Format];
1959 if SrcInfo.IsIndexed or SrcInfo.IsSpecial then
1960 begin
1961 // If source image is in format not supported by ReduceColorsMedianCut
1962 // we convert it
1963 ConvertImage(Images[I], ifA8R8G8B8);
1964 SrcInfo := ImageFormatInfos[Images[I].Format];
1965 end;
1967 InitImage(Target);
1968 NewImage(Images[I].Width, Images[I].Height, DstFormat, Target);
1969 // We map each input image to reduced palette and replace
1970 // image in array with mapped image
1971 ReduceColorsMedianCut(Images[I].Width * Images[I].Height, Images[I].Bits,
1972 Target.Bits, SrcInfo, DstInfo, MaxColors, 0, nil, [raMapImage]);
1973 Move(Pal^, Target.Palette^, MaxColors * SizeOf(TColor32Rec));
1975 FreeImage(Images[I]);
1976 Images[I] := Target;
1977 end;
1978 end;
1979 Result := True;
1980 except
1981 RaiseImaging(SErrorMakePaletteForImages, [MaxColors, Length(Images)]);
1982 end;
1983 end;
1985 procedure RotateImage(var Image: TImageData; Angle: Single);
1986 var
1987 OldFmt: TImageFormat;
1989 procedure XShear(var Src, Dst: TImageData; Row, Offset, Weight, Bpp: Integer);
1990 var
1991 I, J, XPos: Integer;
1992 PixSrc, PixLeft, PixOldLeft: TColor32Rec;
1993 LineDst: PByteArray;
1994 SrcPtr: PColor32;
1995 begin
1996 SrcPtr := @PByteArray(Src.Bits)[Row * Src.Width * Bpp];
1997 LineDst := @PByteArray(Dst.Bits)[Row * Dst.Width * Bpp];
1998 PixOldLeft.Color := 0;
2000 for I := 0 to Src.Width - 1 do
2001 begin
2002 CopyPixel(SrcPtr, @PixSrc, Bpp);
2003 for J := 0 to Bpp - 1 do
2004 PixLeft.Channels[J] := MulDiv(PixSrc.Channels[J], Weight, 256);
2006 XPos := I + Offset;
2007 if (XPos >= 0) and (XPos < Dst.Width) then
2008 begin
2009 for J := 0 to Bpp - 1 do
2010 PixSrc.Channels[J] := ClampToByte(PixSrc.Channels[J] - (PixLeft.Channels[J] - PixOldLeft.Channels[J]));
2011 CopyPixel(@PixSrc, @LineDst[XPos * Bpp], Bpp);
2012 end;
2013 PixOldLeft := PixLeft;
2014 Inc(PByte(SrcPtr), Bpp);
2015 end;
2017 XPos := Src.Width + Offset;
2018 if XPos < Dst.Width then
2019 CopyPixel(@PixOldLeft, @LineDst[XPos * Bpp], Bpp);
2020 end;
2022 procedure YShear(var Src, Dst: TImageData; Col, Offset, Weight, Bpp: Integer);
2023 var
2024 I, J, YPos: Integer;
2025 PixSrc, PixLeft, PixOldLeft: TColor32Rec;
2026 SrcPtr: PByte;
2027 begin
2028 SrcPtr := @PByteArray(Src.Bits)[Col * Bpp];
2029 PixOldLeft.Color := 0;
2031 for I := 0 to Src.Height - 1 do
2032 begin
2033 CopyPixel(SrcPtr, @PixSrc, Bpp);
2034 for J := 0 to Bpp - 1 do
2035 PixLeft.Channels[J] := MulDiv(PixSrc.Channels[J], Weight, 256);
2037 YPos := I + Offset;
2038 if (YPos >= 0) and (YPos < Dst.Height) then
2039 begin
2040 for J := 0 to Bpp - 1 do
2041 PixSrc.Channels[J] := ClampToByte(PixSrc.Channels[J] - (PixLeft.Channels[J] - PixOldLeft.Channels[J]));
2042 CopyPixel(@PixSrc, @PByteArray(Dst.Bits)[(YPos * Dst.Width + Col) * Bpp], Bpp);
2043 end;
2044 PixOldLeft := PixLeft;
2045 Inc(SrcPtr, Src.Width * Bpp);
2046 end;
2048 YPos := Src.Height + Offset;
2049 if YPos < Dst.Height then
2050 CopyPixel(@PixOldLeft, @PByteArray(Dst.Bits)[(YPos * Dst.Width + Col) * Bpp], Bpp);
2051 end;
2053 procedure Rotate45(var Image: TImageData; Angle: Single);
2054 var
2055 TempImage1, TempImage2: TImageData;
2056 AngleRad, AngleTan, AngleSin, AngleCos, Shear: Single;
2057 I, DstWidth, DstHeight, SrcWidth, SrcHeight, Bpp: Integer;
2058 SrcFmt, TempFormat: TImageFormat;
2059 Info: TImageFormatInfo;
2060 begin
2061 AngleRad := Angle * Pi / 180;
2062 AngleSin := Sin(AngleRad);
2063 AngleCos := Cos(AngleRad);
2064 AngleTan := Sin(AngleRad / 2) / Cos(AngleRad / 2);
2065 SrcWidth := Image.Width;
2066 SrcHeight := Image.Height;
2067 SrcFmt := Image.Format;
2069 if not (SrcFmt in [ifR8G8B8..ifX8R8G8B8, ifGray8..ifGray32, ifA16Gray16]) then
2070 ConvertImage(Image, ifA8R8G8B8);
2072 TempFormat := Image.Format;
2073 GetImageFormatInfo(TempFormat, Info);
2074 Bpp := Info.BytesPerPixel;
2076 // 1st shear (horizontal)
2077 DstWidth := Trunc(SrcWidth + SrcHeight * Abs(AngleTan) + 0.5);
2078 DstHeight := SrcHeight;
2079 InitImage(TempImage1);
2080 NewImage(DstWidth, DstHeight, TempFormat, TempImage1);
2082 for I := 0 to DstHeight - 1 do
2083 begin
2084 if AngleTan >= 0 then
2085 Shear := (I + 0.5) * AngleTan
2086 else
2087 Shear := (I - DstHeight + 0.5) * AngleTan;
2088 XShear(Image, TempImage1, I, Floor(Shear), Trunc(255 * (Shear - Floor(Shear)) + 1), Bpp);
2089 end;
2091 // 2nd shear (vertical)
2092 FreeImage(Image);
2093 DstHeight := Trunc(SrcWidth * Abs(AngleSin) + SrcHeight * AngleCos + 0.5) + 1;
2094 InitImage(TempImage2);
2095 NewImage(DstWidth, DstHeight, TempFormat, TempImage2);
2097 if AngleSin >= 0 then
2098 Shear := (SrcWidth - 1) * AngleSin
2099 else
2100 Shear := (SrcWidth - DstWidth) * -AngleSin;
2102 for I := 0 to DstWidth - 1 do
2103 begin
2104 YShear(TempImage1, TempImage2, I, Floor(Shear), Trunc(255 * (Shear - Floor(Shear)) + 1), Bpp);
2105 Shear := Shear - AngleSin;
2106 end;
2108 // 3rd shear (horizontal)
2109 FreeImage(TempImage1);
2110 DstWidth := Trunc(SrcHeight * Abs(AngleSin) + SrcWidth * AngleCos + 0.5) + 1;
2111 NewImage(DstWidth, DstHeight, TempFormat, Image);
2113 if AngleSin >= 0 then
2114 Shear := (SrcWidth - 1) * AngleSin * -AngleTan
2115 else
2116 Shear := ((SrcWidth - 1) * -AngleSin + (1 - DstHeight)) * AngleTan;
2118 for I := 0 to DstHeight - 1 do
2119 begin
2120 XShear(TempImage2, Image, I, Floor(Shear), Trunc(255 * (Shear - Floor(Shear)) + 1), Bpp);
2121 Shear := Shear + AngleTan;
2122 end;
2124 FreeImage(TempImage2);
2125 if Image.Format <> SrcFmt then
2126 ConvertImage(Image, SrcFmt);
2127 end;
2129 procedure RotateMul90(var Image: TImageData; Angle: Integer);
2130 var
2131 RotImage: TImageData;
2132 X, Y, BytesPerPixel: Integer;
2133 RotPix, Pix: PByte;
2134 begin
2135 InitImage(RotImage);
2136 BytesPerPixel := ImageFormatInfos[Image.Format].BytesPerPixel;
2138 if ((Angle = 90) or (Angle = 270)) and (Image.Width <> Image.Height) then
2139 NewImage(Image.Height, Image.Width, Image.Format, RotImage)
2140 else
2141 NewImage(Image.Width, Image.Height, Image.Format, RotImage);
2143 RotPix := RotImage.Bits;
2144 case Angle of
2145 90:
2146 begin
2147 for Y := 0 to RotImage.Height - 1 do
2148 begin
2149 Pix := @PByteArray(Image.Bits)[(Image.Width - Y - 1) * BytesPerPixel];
2150 for X := 0 to RotImage.Width - 1 do
2151 begin
2152 CopyPixel(Pix, RotPix, BytesPerPixel);
2153 Inc(RotPix, BytesPerPixel);
2154 Inc(Pix, Image.Width * BytesPerPixel);
2155 end;
2156 end;
2157 end;
2158 180:
2159 begin
2160 Pix := @PByteArray(Image.Bits)[((Image.Height - 1) * Image.Width +
2161 (Image.Width - 1)) * BytesPerPixel];
2162 for Y := 0 to RotImage.Height - 1 do
2163 for X := 0 to RotImage.Width - 1 do
2164 begin
2165 CopyPixel(Pix, RotPix, BytesPerPixel);
2166 Inc(RotPix, BytesPerPixel);
2167 Dec(Pix, BytesPerPixel);
2168 end;
2169 end;
2170 270:
2171 begin
2172 for Y := 0 to RotImage.Height - 1 do
2173 begin
2174 Pix := @PByteArray(Image.Bits)[((Image.Height - 1) * Image.Width + Y) * BytesPerPixel];
2175 for X := 0 to RotImage.Width - 1 do
2176 begin
2177 CopyPixel(Pix, RotPix, BytesPerPixel);
2178 Inc(RotPix, BytesPerPixel);
2179 Dec(Pix, Image.Width * BytesPerPixel);
2180 end;
2181 end;
2182 end;
2183 end;
2185 FreeMemNil(Image.Bits);
2186 RotImage.Palette := Image.Palette;
2187 Image := RotImage;
2188 end;
2190 begin
2191 if TestImage(Image) then
2192 try
2193 while Angle >= 360 do
2194 Angle := Angle - 360;
2195 while Angle < 0 do
2196 Angle := Angle + 360;
2198 if (Angle = 0) or (Abs(Angle) = 360) then
2199 Exit;
2201 OldFmt := Image.Format;
2202 if ImageFormatInfos[Image.Format].IsSpecial then
2203 ConvertImage(Image, ifDefault);
2205 if (Angle > 45) and (Angle <= 135) then
2206 begin
2207 RotateMul90(Image, 90);
2208 Angle := Angle - 90;
2209 end
2210 else if (Angle > 135) and (Angle <= 225) then
2211 begin
2212 RotateMul90(Image, 180);
2213 Angle := Angle - 180;
2214 end
2215 else if (Angle > 225) and (Angle <= 315) then
2216 begin
2217 RotateMul90(Image, 270);
2218 Angle := Angle - 270;
2219 end;
2221 if Angle <> 0 then
2222 Rotate45(Image, Angle);
2224 if OldFmt <> Image.Format then
2225 ConvertImage(Image, OldFmt);
2227 except
2228 raise UpdateExceptMessage(GetExceptObject, SErrorRotateImage, [ImageToStr(Image), Angle]);
2229 end;
2230 end;
2232 { Drawing/Pixel functions }
2234 function CopyRect(const SrcImage: TImageData; SrcX, SrcY, Width, Height: LongInt;
2235 var DstImage: TImageData; DstX, DstY: LongInt): Boolean;
2236 var
2237 Info: PImageFormatInfo;
2238 I, SrcWidthBytes, DstWidthBytes, MoveBytes: LongInt;
2239 SrcPointer, DstPointer: PByte;
2240 WorkImage: TImageData;
2241 OldFormat: TImageFormat;
2242 begin
2243 Result := False;
2244 OldFormat := ifUnknown;
2245 if TestImage(SrcImage) and TestImage(DstImage) then
2246 try
2247 // Make sure we are still copying image to image, not invalid pointer to protected memory
2248 ClipCopyBounds(SrcX, SrcY, Width, Height, DstX, DstY, SrcImage.Width, SrcImage.Height,
2249 Rect(0, 0, DstImage.Width, DstImage.Height));
2251 if (Width > 0) and (Height > 0) then
2252 begin
2253 Info := ImageFormatInfos[DstImage.Format];
2254 if Info.IsSpecial then
2255 begin
2256 // If dest image is in special format we convert it to default
2257 OldFormat := Info.Format;
2258 ConvertImage(DstImage, ifDefault);
2259 Info := ImageFormatInfos[DstImage.Format];
2260 end;
2261 if SrcImage.Format <> DstImage.Format then
2262 begin
2263 // If images are in different format source is converted to dest's format
2264 InitImage(WorkImage);
2265 CloneImage(SrcImage, WorkImage);
2266 ConvertImage(WorkImage, DstImage.Format);
2267 end
2268 else
2269 WorkImage := SrcImage;
2271 MoveBytes := Width * Info.BytesPerPixel;
2272 DstWidthBytes := DstImage.Width * Info.BytesPerPixel;
2273 DstPointer := @PByteArray(DstImage.Bits)[DstY * DstWidthBytes +
2274 DstX * Info.BytesPerPixel];
2275 SrcWidthBytes := WorkImage.Width * Info.BytesPerPixel;
2276 SrcPointer := @PByteArray(WorkImage.Bits)[SrcY * SrcWidthBytes +
2277 SrcX * Info.BytesPerPixel];
2279 for I := 0 to Height - 1 do
2280 begin
2281 Move(SrcPointer^, DstPointer^, MoveBytes);
2282 Inc(SrcPointer, SrcWidthBytes);
2283 Inc(DstPointer, DstWidthBytes);
2284 end;
2285 // If dest image was in special format we convert it back
2286 if OldFormat <> ifUnknown then
2287 ConvertImage(DstImage, OldFormat);
2288 // Working image must be freed if it is not the same as source image
2289 if WorkImage.Bits <> SrcImage.Bits then
2290 FreeImage(WorkImage);
2292 Result := True;
2293 end;
2294 except
2295 RaiseImaging(SErrorCopyRect, [ImageToStr(SrcImage), ImageToStr(DstImage)]);
2296 end;
2297 end;
2299 function FillRect(var Image: TImageData; X, Y, Width, Height: LongInt;
2300 FillColor: Pointer): Boolean;
2301 var
2302 Info: PImageFormatInfo;
2303 I, J, ImageWidthBytes, RectWidthBytes, Bpp: Longint;
2304 LinePointer, PixPointer: PByte;
2305 OldFmt: TImageFormat;
2306 begin
2307 Result := False;
2308 if TestImage(Image) then
2309 try
2310 ClipRectBounds(X, Y, Width, Height, Rect(0, 0, Image.Width, Image.Height));
2312 if (Width > 0) and (Height > 0) then
2313 begin
2314 OldFmt := Image.Format;
2315 if ImageFormatInfos[OldFmt].IsSpecial then
2316 ConvertImage(Image, ifDefault);
2318 Info := ImageFormatInfos[Image.Format];
2319 Bpp := Info.BytesPerPixel;
2320 ImageWidthBytes := Image.Width * Bpp;
2321 RectWidthBytes := Width * Bpp;
2322 LinePointer := @PByteArray(Image.Bits)[Y * ImageWidthBytes + X * Bpp];
2324 for I := 0 to Height - 1 do
2325 begin
2326 case Bpp of
2327 1: FillMemoryByte(LinePointer, RectWidthBytes, PByte(FillColor)^);
2328 2: FillMemoryWord(LinePointer, RectWidthBytes, PWord(FillColor)^);
2329 4: FillMemoryLongWord(LinePointer, RectWidthBytes, PLongWord(FillColor)^);
2330 else
2331 PixPointer := LinePointer;
2332 for J := 0 to Width - 1 do
2333 begin
2334 CopyPixel(FillColor, PixPointer, Bpp);
2335 Inc(PixPointer, Bpp);
2336 end;
2337 end;
2338 Inc(LinePointer, ImageWidthBytes);
2339 end;
2341 if OldFmt <> Image.Format then
2342 ConvertImage(Image, OldFmt);
2343 end;
2345 Result := True;
2346 except
2347 RaiseImaging(SErrorFillRect, [X, Y, Width, Height, ImageToStr(Image)]);
2348 end;
2349 end;
2351 function ReplaceColor(var Image: TImageData; X, Y, Width, Height: LongInt;
2352 OldColor, NewColor: Pointer): Boolean;
2353 var
2354 Info: PImageFormatInfo;
2355 I, J, WidthBytes, Bpp: Longint;
2356 LinePointer, PixPointer: PByte;
2357 OldFmt: TImageFormat;
2358 begin
2359 Assert((OldColor <> nil) and (NewColor <> nil));
2360 Result := False;
2361 if TestImage(Image) then
2362 try
2363 ClipRectBounds(X, Y, Width, Height, Rect(0, 0, Image.Width, Image.Height));
2365 if (Width > 0) and (Height > 0) then
2366 begin
2367 OldFmt := Image.Format;
2368 if ImageFormatInfos[OldFmt].IsSpecial then
2369 ConvertImage(Image, ifDefault);
2371 Info := ImageFormatInfos[Image.Format];
2372 Bpp := Info.BytesPerPixel;
2373 WidthBytes := Image.Width * Bpp;
2374 LinePointer := @PByteArray(Image.Bits)[Y * WidthBytes + X * Bpp];
2376 for I := 0 to Height - 1 do
2377 begin
2378 PixPointer := LinePointer;
2379 for J := 0 to Width - 1 do
2380 begin
2381 if ComparePixels(PixPointer, OldColor, Bpp) then
2382 CopyPixel(NewColor, PixPointer, Bpp);
2383 Inc(PixPointer, Bpp);
2384 end;
2385 Inc(LinePointer, WidthBytes);
2386 end;
2388 if OldFmt <> Image.Format then
2389 ConvertImage(Image, OldFmt);
2390 end;
2392 Result := True;
2393 except
2394 RaiseImaging(SErrorReplaceColor, [X, Y, Width, Height, ImageToStr(Image)]);
2395 end;
2396 end;
2398 function StretchRect(const SrcImage: TImageData; SrcX, SrcY, SrcWidth,
2399 SrcHeight: LongInt; var DstImage: TImageData; DstX, DstY, DstWidth,
2400 DstHeight: LongInt; Filter: TResizeFilter): Boolean;
2401 var
2402 Info: PImageFormatInfo;
2403 WorkImage: TImageData;
2404 OldFormat: TImageFormat;
2405 Resampling: TSamplingFilter;
2406 begin
2407 Result := False;
2408 OldFormat := ifUnknown;
2409 if TestImage(SrcImage) and TestImage(DstImage) then
2410 try
2411 // Make sure we are still copying image to image, not invalid pointer to protected memory
2412 ClipStretchBounds(SrcX, SrcY, SrcWidth, SrcHeight, DstX, DstY, DstWidth, DstHeight,
2413 SrcImage.Width, SrcImage.Height, Rect(0, 0, DstImage.Width, DstImage.Height));
2415 if (SrcWidth = DstWidth) and (SrcHeight = DstHeight) then
2416 begin
2417 // If source and dest rectangles have the same size call CopyRect
2418 Result := CopyRect(SrcImage, SrcX, SrcY, SrcWidth, SrcHeight, DstImage, DstX, DstY);
2419 end
2420 else if (SrcWidth > 0) and (SrcHeight > 0) and (DstWidth > 0) and (DstHeight > 0) then
2421 begin
2422 // If source and dest rectangles don't have the same size we do stretch
2423 Info := ImageFormatInfos[DstImage.Format];
2425 if Info.IsSpecial then
2426 begin
2427 // If dest image is in special format we convert it to default
2428 OldFormat := Info.Format;
2429 ConvertImage(DstImage, ifDefault);
2430 Info := ImageFormatInfos[DstImage.Format];
2431 end;
2433 if SrcImage.Format <> DstImage.Format then
2434 begin
2435 // If images are in different format source is converted to dest's format
2436 InitImage(WorkImage);
2437 CloneImage(SrcImage, WorkImage);
2438 ConvertImage(WorkImage, DstImage.Format);
2439 end
2440 else
2441 WorkImage := SrcImage;
2443 // Only pixel resize is supported for indexed images
2444 if Info.IsIndexed then
2445 Filter := rfNearest;
2447 if Filter = rfNearest then
2448 begin
2449 StretchNearest(WorkImage, SrcX, SrcY, SrcWidth, SrcHeight,
2450 DstImage, DstX, DstY, DstWidth, DstHeight);
2451 end
2452 else
2453 begin
2454 Resampling := sfNearest;
2455 case Filter of
2456 rfBilinear: Resampling := sfLinear;
2457 rfBicubic: Resampling := DefaultCubicFilter;
2458 rfLanczos: Resampling := sfLanczos;
2459 end;
2460 StretchResample(WorkImage, SrcX, SrcY, SrcWidth, SrcHeight,
2461 DstImage, DstX, DstY, DstWidth, DstHeight, Resampling);
2462 end;
2464 // If dest image was in special format we convert it back
2465 if OldFormat <> ifUnknown then
2466 ConvertImage(DstImage, OldFormat);
2467 // Working image must be freed if it is not the same as source image
2468 if WorkImage.Bits <> SrcImage.Bits then
2469 FreeImage(WorkImage);
2471 Result := True;
2472 end;
2473 except
2474 RaiseImaging(SErrorStretchRect, [ImageToStr(SrcImage), ImageToStr(DstImage)]);
2475 end;
2476 end;
2478 procedure GetPixelDirect(const Image: TImageData; X, Y: LongInt; Pixel: Pointer);
2479 var
2480 BytesPerPixel: LongInt;
2481 begin
2482 Assert(Pixel <> nil);
2483 BytesPerPixel := ImageFormatInfos[Image.Format].BytesPerPixel;
2484 CopyPixel(@PByteArray(Image.Bits)[(Y * Image.Width + X) * BytesPerPixel],
2485 Pixel, BytesPerPixel);
2486 end;
2488 procedure SetPixelDirect(const Image: TImageData; X, Y: LongInt; Pixel: Pointer);
2489 var
2490 BytesPerPixel: LongInt;
2491 begin
2492 Assert(Pixel <> nil);
2493 BytesPerPixel := ImageFormatInfos[Image.Format].BytesPerPixel;
2494 CopyPixel(Pixel, @PByteArray(Image.Bits)[(Y * Image.Width + X) * BytesPerPixel],
2495 BytesPerPixel);
2496 end;
2498 function GetPixel32(const Image: TImageData; X, Y: LongInt): TColor32Rec;
2499 var
2500 Info: PImageFormatInfo;
2501 Data: PByte;
2502 begin
2503 Info := ImageFormatInfos[Image.Format];
2504 Data := @PByteArray(Image.Bits)[(Y * Image.Width + X) * Info.BytesPerPixel];
2505 Result := GetPixel32Generic(Data, Info, Image.Palette);
2506 end;
2508 procedure SetPixel32(const Image: TImageData; X, Y: LongInt; const Color: TColor32Rec);
2509 var
2510 Info: PImageFormatInfo;
2511 Data: PByte;
2512 begin
2513 Info := ImageFormatInfos[Image.Format];
2514 Data := @PByteArray(Image.Bits)[(Y * Image.Width + X) * Info.BytesPerPixel];
2515 SetPixel32Generic(Data, Info, Image.Palette, Color);
2516 end;
2518 function GetPixelFP(const Image: TImageData; X, Y: LongInt): TColorFPRec;
2519 var
2520 Info: PImageFormatInfo;
2521 Data: PByte;
2522 begin
2523 Info := ImageFormatInfos[Image.Format];
2524 Data := @PByteArray(Image.Bits)[(Y * Image.Width + X) * Info.BytesPerPixel];
2525 Result := GetPixelFPGeneric(Data, Info, Image.Palette);
2526 end;
2528 procedure SetPixelFP(const Image: TImageData; X, Y: LongInt; const Color: TColorFPRec);
2529 var
2530 Info: PImageFormatInfo;
2531 Data: PByte;
2532 begin
2533 Info := ImageFormatInfos[Image.Format];
2534 Data := @PByteArray(Image.Bits)[(Y * Image.Width + X) * Info.BytesPerPixel];
2535 SetPixelFPGeneric(Data, Info, Image.Palette, Color);
2536 end;
2538 { Palette Functions }
2540 procedure NewPalette(Entries: LongInt; var Pal: PPalette32);
2541 begin
2542 Assert((Entries > 2) and (Entries <= 65535));
2543 try
2544 GetMem(Pal, Entries * SizeOf(TColor32Rec));
2545 FillChar(Pal^, Entries * SizeOf(TColor32Rec), $FF);
2546 except
2547 RaiseImaging(SErrorNewPalette, [Entries]);
2548 end;
2549 end;
2551 procedure FreePalette(var Pal: PPalette32);
2552 begin
2553 try
2554 FreeMemNil(Pal);
2555 except
2556 RaiseImaging(SErrorFreePalette, [Pal]);
2557 end;
2558 end;
2560 procedure CopyPalette(SrcPal, DstPal: PPalette32; SrcIdx, DstIdx, Count: LongInt);
2561 begin
2562 Assert((SrcPal <> nil) and (DstPal <> nil));
2563 Assert((SrcIdx >= 0) and (DstIdx >= 0) and (Count >= 0));
2564 try
2565 Move(SrcPal[SrcIdx], DstPal[DstIdx], Count * SizeOf(TColor32Rec));
2566 except
2567 RaiseImaging(SErrorCopyPalette, [Count, SrcPal, DstPal]);
2568 end;
2569 end;
2571 function FindColor(Pal: PPalette32; Entries: LongInt; Color: TColor32):
2572 LongInt;
2573 var
2574 Col: TColor32Rec;
2575 I, MinDif, Dif: LongInt;
2576 begin
2577 Assert(Pal <> nil);
2578 Result := -1;
2579 Col.Color := Color;
2580 try
2581 // First try to find exact match
2582 for I := 0 to Entries - 1 do
2583 with Pal[I] do
2584 begin
2585 if (A = Col.A) and (R = Col.R) and
2586 (G = Col.G) and (B = Col.B) then
2587 begin
2588 Result := I;
2589 Exit;
2590 end;
2591 end;
2593 // If exact match was not found, find nearest color
2594 MinDif := 1020;
2595 for I := 0 to Entries - 1 do
2596 with Pal[I] do
2597 begin
2598 Dif := Abs(R - Col.R);
2599 if Dif > MinDif then Continue;
2600 Dif := Dif + Abs(G - Col.G);
2601 if Dif > MinDif then Continue;
2602 Dif := Dif + Abs(B - Col.B);
2603 if Dif > MinDif then Continue;
2604 Dif := Dif + Abs(A - Col.A);
2605 if Dif < MinDif then
2606 begin
2607 MinDif := Dif;
2608 Result := I;
2609 end;
2610 end;
2611 except
2612 RaiseImaging(SErrorFindColor, [Pal, Entries]);
2613 end;
2614 end;
2616 procedure FillGrayscalePalette(Pal: PPalette32; Entries: LongInt);
2617 var
2618 I: LongInt;
2619 begin
2620 Assert(Pal <> nil);
2621 try
2622 for I := 0 to Entries - 1 do
2623 with Pal[I] do
2624 begin
2625 A := $FF;
2626 R := Byte(I);
2627 G := Byte(I);
2628 B := Byte(I);
2629 end;
2630 except
2631 RaiseImaging(SErrorGrayscalePalette, [Pal, Entries]);
2632 end;
2633 end;
2635 procedure FillCustomPalette(Pal: PPalette32; Entries: LongInt; RBits, GBits,
2636 BBits: Byte; Alpha: Byte = $FF);
2637 var
2638 I, TotalBits, MaxEntries: LongInt;
2639 begin
2640 Assert(Pal <> nil);
2641 TotalBits := RBits + GBits + BBits;
2642 MaxEntries := Min(Pow2Int(TotalBits), Entries);
2643 FillChar(Pal^, Entries * SizeOf(TColor32Rec), 0);
2644 try
2645 for I := 0 to MaxEntries - 1 do
2646 with Pal[I] do
2647 begin
2648 A := Alpha;
2649 if RBits > 0 then
2650 R := ((I shr Max(0, GBits + BBits - 1)) and (1 shl RBits - 1)) * 255 div (1 shl RBits - 1);
2651 if GBits > 0 then
2652 G := ((I shr Max(0, BBits - 1)) and (1 shl GBits - 1)) * 255 div (1 shl GBits - 1);
2653 if BBits > 0 then
2654 B := ((I shr 0) and (1 shl BBits - 1)) * 255 div (1 shl BBits - 1);
2655 end;
2656 except
2657 RaiseImaging(SErrorCustomPalette, [Pal, Entries]);
2658 end;
2659 end;
2661 procedure SwapChannelsOfPalette(Pal: PPalette32; Entries, SrcChannel,
2662 DstChannel: LongInt);
2663 var
2664 I: LongInt;
2665 Swap: Byte;
2666 begin
2667 Assert(Pal <> nil);
2668 Assert((SrcChannel in [0..3]) and (DstChannel in [0..3]));
2669 try
2670 for I := 0 to Entries - 1 do
2671 with Pal[I] do
2672 begin
2673 Swap := Channels[SrcChannel];
2674 Channels[SrcChannel] := Channels[DstChannel];
2675 Channels[DstChannel] := Swap;
2676 end;
2677 except
2678 RaiseImaging(SErrorSwapPalette, [Pal, Entries]);
2679 end;
2680 end;
2682 { Options Functions }
2684 function SetOption(OptionId, Value: LongInt): Boolean;
2685 begin
2686 Result := False;
2687 if (OptionId >= 0) and (OptionId < Length(Options)) and
2688 (Options[OptionID] <> nil) then
2689 begin
2690 Options[OptionID]^ := CheckOptionValue(OptionId, Value);
2691 Result := True;
2692 end;
2693 end;
2695 function GetOption(OptionId: LongInt): LongInt;
2696 begin
2697 Result := InvalidOption;
2698 if (OptionId >= 0) and (OptionId < Length(Options)) and
2699 (Options[OptionID] <> nil) then
2700 begin
2701 Result := Options[OptionID]^;
2702 end;
2703 end;
2705 function PushOptions: Boolean;
2706 begin
2707 Result := OptionStack.Push;
2708 end;
2710 function PopOptions: Boolean;
2711 begin
2712 Result := OptionStack.Pop;
2713 end;
2715 { Image Format Functions }
2717 function GetImageFormatInfo(Format: TImageFormat; out Info: TImageFormatInfo): Boolean;
2718 begin
2719 FillChar(Info, SizeOf(Info), 0);
2720 if ImageFormatInfos[Format] <> nil then
2721 begin
2722 Info := ImageFormatInfos[Format]^;
2723 Result := True;
2724 end
2725 else
2726 Result := False;
2727 end;
2729 function GetPixelsSize(Format: TImageFormat; Width, Height: LongInt): LongInt;
2730 begin
2731 if ImageFormatInfos[Format] <> nil then
2732 Result := ImageFormatInfos[Format].GetPixelsSize(Format, Width, Height)
2733 else
2734 Result := 0;
2735 end;
2737 { IO Functions }
2739 procedure SetUserFileIO(OpenProc: TOpenProc;
2740 CloseProc: TCloseProc; EofProc: TEofProc; SeekProc: TSeekProc; TellProc:
2741 TTellProc; ReadProc: TReadProc; WriteProc: TWriteProc);
2742 begin
2743 FileIO.Open := OpenProc;
2744 FileIO.Close := CloseProc;
2745 FileIO.Eof := EofProc;
2746 FileIO.Seek := SeekProc;
2747 FileIO.Tell := TellProc;
2748 FileIO.Read := ReadProc;
2749 FileIO.Write := WriteProc;
2750 end;
2752 procedure ResetFileIO;
2753 begin
2754 FileIO := OriginalFileIO;
2755 end;
2757 { Raw Image IO Functions }
2759 procedure ReadRawImage(Handle: TImagingHandle; Width, Height: Integer;
2760 Format: TImageFormat; out Image: TImageData; Offset, RowLength: Integer);
2761 var
2762 WidthBytes, I: Integer;
2763 Info: PImageFormatInfo;
2764 begin
2765 Info := ImageFormatInfos[Format];
2766 // Calc scanline size
2767 WidthBytes := Info.GetPixelsSize(Format, Width, 1);
2768 if RowLength = 0 then
2769 RowLength := WidthBytes;
2770 // Create new image if needed - don't need to allocate new one if there is already
2771 // one with desired size and format
2772 if (Image.Width <> Width) or (Image.Height <> Height) or (Image.Format <> Format) then
2773 NewImage(Width, Height, Format, Image);
2774 // Move past the header
2775 IO.Seek(Handle, Offset, smFromCurrent);
2776 // Read scanlines from input
2777 for I := 0 to Height - 1 do
2778 begin
2779 IO.Read(Handle, @PByteArray(Image.Bits)[I * WidthBytes], WidthBytes);
2780 IO.Seek(Handle, RowLength - WidthBytes, smFromCurrent);
2781 end;
2782 end;
2784 procedure ReadRawImageFromFile(const FileName: string; Width, Height: Integer;
2785 Format: TImageFormat; var Image: TImageData; Offset, RowLength: Integer);
2786 var
2787 Handle: TImagingHandle;
2788 begin
2789 Assert(FileName <> '');
2790 // Set IO ops to file ops and open given file
2791 SetFileIO;
2792 Handle := IO.Open(PChar(FileName), omReadOnly);
2793 try
2794 ReadRawImage(Handle, Width, Height, Format, Image, Offset, RowLength);
2795 finally
2796 IO.Close(Handle);
2797 end;
2798 end;
2800 procedure ReadRawImageFromStream(Stream: TStream; Width, Height: Integer;
2801 Format: TImageFormat; var Image: TImageData; Offset, RowLength: Integer);
2802 var
2803 Handle: TImagingHandle;
2804 begin
2805 Assert(Stream <> nil);
2806 if Stream.Size - Stream.Position = 0 then
2807 RaiseImaging(SErrorEmptyStream, []);
2808 // Set IO ops to stream ops and open given stream
2809 SetStreamIO;
2810 Handle := IO.Open(Pointer(Stream), omReadOnly);
2811 try
2812 ReadRawImage(Handle, Width, Height, Format, Image, Offset, RowLength);
2813 finally
2814 IO.Close(Handle);
2815 end;
2816 end;
2818 procedure ReadRawImageFromMemory(Data: Pointer; DataSize: Integer; Width, Height: Integer;
2819 Format: TImageFormat; var Image: TImageData; Offset, RowLength: Integer);
2820 var
2821 Handle: TImagingHandle;
2822 MemRec: TMemoryIORec;
2823 begin
2824 Assert((Data <> nil) and (DataSize > 0));
2825 // Set IO ops to memory ops and open given stream
2826 SetMemoryIO;
2827 MemRec := PrepareMemIO(Data, DataSize);
2828 Handle := IO.Open(@MemRec, omReadOnly);
2829 try
2830 ReadRawImage(Handle, Width, Height, Format, Image, Offset, RowLength);
2831 finally
2832 IO.Close(Handle);
2833 end;
2834 end;
2836 procedure ReadRawImageRect(Data: Pointer; Left, Top, Width, Height: Integer;
2837 var Image: TImageData; Offset, RowLength: Integer);
2838 var
2839 DestScanBytes, RectBytes, I: Integer;
2840 Info: PImageFormatInfo;
2841 Src, Dest: PByte;
2842 begin
2843 Assert(Data <> nil);
2844 Assert((Left + Width <= Image.Width) and (Top + Height <= Image.Height));
2845 Info := ImageFormatInfos[Image.Format];
2847 // Calc scanline size
2848 DestScanBytes := Info.GetPixelsSize(Info.Format, Image.Width, 1);
2849 RectBytes := Info.GetPixelsSize(Info.Format, Width, 1);
2850 if RowLength = 0 then
2851 RowLength := RectBytes;
2853 Src := Data;
2854 Dest := @PByteArray(Image.Bits)[Top * DestScanBytes + Info.GetPixelsSize(Info.Format, Left, 1)];
2855 // Move past the header
2856 Inc(Src, Offset);
2858 // Read lines into rect in the existing image
2859 for I := 0 to Height - 1 do
2860 begin
2861 Move(Src^, Dest^, RectBytes);
2862 Inc(Src, RowLength);
2863 Inc(Dest, DestScanBytes);
2864 end;
2865 end;
2867 procedure WriteRawImage(Handle: TImagingHandle; const Image: TImageData;
2868 Offset, RowLength: Integer);
2869 var
2870 WidthBytes, I: Integer;
2871 Info: PImageFormatInfo;
2872 begin
2873 Info := ImageFormatInfos[Image.Format];
2874 // Calc scanline size
2875 WidthBytes := Info.GetPixelsSize(Image.Format, Image.Width, 1);
2876 if RowLength = 0 then
2877 RowLength := WidthBytes;
2878 // Move past the header
2879 IO.Seek(Handle, Offset, smFromCurrent);
2880 // Write scanlines to output
2881 for I := 0 to Image.Height - 1 do
2882 begin
2883 IO.Write(Handle, @PByteArray(Image.Bits)[I * WidthBytes], WidthBytes);
2884 IO.Seek(Handle, RowLength - WidthBytes, smFromCurrent);
2885 end;
2886 end;
2888 procedure WriteRawImageToFile(const FileName: string; const Image: TImageData;
2889 Offset, RowLength: Integer);
2890 var
2891 Handle: TImagingHandle;
2892 begin
2893 Assert(FileName <> '');
2894 // Set IO ops to file ops and open given file
2895 SetFileIO;
2896 Handle := IO.Open(PChar(FileName), omCreate);
2897 try
2898 WriteRawImage(Handle, Image, Offset, RowLength);
2899 finally
2900 IO.Close(Handle);
2901 end;
2902 end;
2904 procedure WriteRawImageToStream(Stream: TStream; const Image: TImageData;
2905 Offset, RowLength: Integer);
2906 var
2907 Handle: TImagingHandle;
2908 begin
2909 Assert(Stream <> nil);
2910 // Set IO ops to stream ops and open given stream
2911 SetStreamIO;
2912 Handle := IO.Open(Pointer(Stream), omCreate);
2913 try
2914 WriteRawImage(Handle, Image, Offset, RowLength);
2915 finally
2916 IO.Close(Handle);
2917 end;
2918 end;
2920 procedure WriteRawImageToMemory(Data: Pointer; DataSize: Integer; const Image: TImageData;
2921 Offset, RowLength: Integer);
2922 var
2923 Handle: TImagingHandle;
2924 MemRec: TMemoryIORec;
2925 begin
2926 Assert((Data <> nil) and (DataSize > 0));
2927 // Set IO ops to memory ops and open given stream
2928 SetMemoryIO;
2929 MemRec := PrepareMemIO(Data, DataSize);
2930 Handle := IO.Open(@MemRec, omCreate);
2931 try
2932 WriteRawImage(Handle, Image, Offset, RowLength);
2933 finally
2934 IO.Close(Handle);
2935 end;
2936 end;
2938 procedure WriteRawImageRect(Data: Pointer; Left, Top, Width, Height: Integer;
2939 const Image: TImageData; Offset, RowLength: Integer);
2940 var
2941 SrcScanBytes, RectBytes, I: Integer;
2942 Info: PImageFormatInfo;
2943 Src, Dest: PByte;
2944 begin
2945 Assert(Data <> nil);
2946 Assert((Left + Width <= Image.Width) and (Top + Height <= Image.Height));
2947 Info := ImageFormatInfos[Image.Format];
2949 // Calc scanline size
2950 SrcScanBytes := Info.GetPixelsSize(Info.Format, Image.Width, 1);
2951 RectBytes := Info.GetPixelsSize(Info.Format, Width, 1);
2952 if RowLength = 0 then
2953 RowLength := RectBytes;
2955 Src := @PByteArray(Image.Bits)[Top * SrcScanBytes + Info.GetPixelsSize(Info.Format, Left, 1)];
2956 Dest := Data;
2957 // Move past the header
2958 Inc(Dest, Offset);
2960 // Write lines from rect of the existing image
2961 for I := 0 to Height - 1 do
2962 begin
2963 Move(Src^, Dest^, RectBytes);
2964 Inc(Dest, RowLength);
2965 Inc(Src, SrcScanBytes);
2966 end;
2967 end;
2969 { Convenience/helper Functions }
2971 procedure ResizeImageToFit(const SrcImage: TImageData; FitWidth, FitHeight: Integer;
2972 Filter: TResizeFilter; var DestImage: TImageData);
2973 var
2974 CurSize, FitSize, DestSize: TSize;
2975 begin
2976 if not TestImage(SrcImage) then
2977 raise EImagingError.Create(SErrorInvalidInputImage);
2979 FitSize.CX := FitWidth;
2980 FitSize.CY := FitHeight;
2981 CurSize.CX := SrcImage.Width;
2982 CurSize.CY := SrcImage.Height;
2983 DestSize := ImagingUtility.ScaleSizeToFit(CurSize, FitSize);
2985 NewImage(Max(DestSize.CX, 1), Max(DestSize.CY, 1), SrcImage.Format, DestImage);
2986 if SrcImage.Palette <> nil then
2987 CopyPalette(SrcImage.Palette, DestImage.Palette, 0, 0, ImageFormatInfos[SrcImage.Format].PaletteEntries);
2989 StretchRect(SrcImage, 0, 0, CurSize.CX, CurSize.CY, DestImage, 0, 0,
2990 DestSize.CX, DestSize.CY, Filter);
2991 end;
2993 { ------------------------------------------------------------------------
2994 Other Imaging Stuff
2995 ------------------------------------------------------------------------}
2997 function GetFormatName(Format: TImageFormat): string;
2998 begin
2999 if ImageFormatInfos[Format] <> nil then
3000 Result := ImageFormatInfos[Format].Name
3001 else
3002 Result := SUnknownFormat;
3003 end;
3005 function ImageToStr(const Image: TImageData): string;
3006 var
3007 ImgSize: Integer;
3008 begin
3009 if TestImage(Image) then
3010 with Image do
3011 begin
3012 ImgSize := Size;
3013 if ImgSize > 8192 then
3014 ImgSize := ImgSize div 1024;
3015 Result := SysUtils.Format(SImageInfo, [@Image, Width, Height,
3016 GetFormatName(Format), ImgSize + 0.0, Iff(ImgSize = Size, 'B', 'KiB'), Bits,
3017 Palette]);
3018 end
3019 else
3020 Result := SysUtils.Format(SImageInfoInvalid, [@Image]);
3021 end;
3023 function GetVersionStr: string;
3024 begin
3025 Result := Format('%.1d.%.2d.%.1d', [ImagingVersionMajor,
3026 ImagingVersionMinor, ImagingVersionPatch]);
3027 end;
3029 function IffFormat(Condition: Boolean; const TruePart, FalsePart: TImageFormat): TImageFormat;
3030 begin
3031 if Condition then
3032 Result := TruePart
3033 else
3034 Result := FalsePart;
3035 end;
3037 procedure RegisterImageFileFormat(AClass: TImageFileFormatClass);
3038 begin
3039 Assert(AClass <> nil);
3040 if ImageFileFormats = nil then
3041 ImageFileFormats := TList.Create;
3042 if GlobalMetadata = nil then
3043 GlobalMetadata := TMetadata.Create;
3044 if ImageFileFormats <> nil then
3045 ImageFileFormats.Add(AClass.Create);
3046 end;
3048 function RegisterOption(OptionId: LongInt; Variable: PLongInt): Boolean;
3049 begin
3050 Result := False;
3051 if Options = nil then
3052 InitOptions;
3054 Assert(Variable <> nil);
3056 if OptionId >= Length(Options) then
3057 SetLength(Options, OptionId + InitialOptions);
3058 if (OptionId >= 0) and (OptionId < Length(Options)) {and (Options[OptionId] = nil) - must be able to override existing } then
3059 begin
3060 Options[OptionId] := Variable;
3061 Result := True;
3062 end;
3063 end;
3065 function FindImageFileFormatByExt(const Ext: string): TImageFileFormat;
3066 var
3067 I: LongInt;
3068 begin
3069 Result := nil;
3070 for I := ImageFileFormats.Count - 1 downto 0 do
3071 if TImageFileFormat(ImageFileFormats[I]).Extensions.IndexOf(Ext) >= 0 then
3072 begin
3073 Result := TImageFileFormat(ImageFileFormats[I]);
3074 Exit;
3075 end;
3076 end;
3078 function FindImageFileFormatByName(const FileName: string): TImageFileFormat;
3079 var
3080 I: LongInt;
3081 begin
3082 Result := nil;
3083 for I := ImageFileFormats.Count - 1 downto 0 do
3084 if TImageFileFormat(ImageFileFormats[I]).TestFileName(FileName) then
3085 begin
3086 Result := TImageFileFormat(ImageFileFormats[I]);
3087 Exit;
3088 end;
3089 end;
3091 function FindImageFileFormatByClass(AClass: TImageFileFormatClass): TImageFileFormat;
3092 var
3093 I: LongInt;
3094 begin
3095 Result := nil;
3096 for I := 0 to ImageFileFormats.Count - 1 do
3097 if TImageFileFormat(ImageFileFormats[I]) is AClass then
3098 begin
3099 Result := TObject(ImageFileFormats[I]) as TImageFileFormat;
3100 Break;
3101 end;
3102 end;
3104 function GetFileFormatCount: LongInt;
3105 begin
3106 Result := ImageFileFormats.Count;
3107 end;
3109 function GetFileFormatAtIndex(Index: LongInt): TImageFileFormat;
3110 begin
3111 if (Index >= 0) and (Index < ImageFileFormats.Count) then
3112 Result := TImageFileFormat(ImageFileFormats[Index])
3113 else
3114 Result := nil;
3115 end;
3117 function GetImageFileFormatsFilter(OpenFileFilter: Boolean): string;
3118 var
3119 I, J, Count: LongInt;
3120 Descriptions: string;
3121 Filters, CurFilter: string;
3122 FileFormat: TImageFileFormat;
3123 begin
3124 Descriptions := '';
3125 Filters := '';
3126 Count := 0;
3128 for I := 0 to ImageFileFormats.Count - 1 do
3129 begin
3130 FileFormat := TObject(ImageFileFormats[I]) as TImageFileFormat;
3132 // If we are creating filter for save dialog and this format cannot save
3133 // files the we skip it
3134 if not OpenFileFilter and not FileFormat.CanSave then
3135 Continue;
3137 CurFilter := '';
3138 for J := 0 to FileFormat.Masks.Count - 1 do
3139 begin
3140 CurFilter := CurFilter + FileFormat.Masks[J];
3141 if J < FileFormat.Masks.Count - 1 then
3142 CurFilter := CurFilter + ';';
3143 end;
3145 FmtStr(Descriptions, '%s%s (%s)|%2:s', [Descriptions, FileFormat.Name, CurFilter]);
3146 if Filters <> '' then
3147 FmtStr(Filters, '%s;%s', [Filters, CurFilter])
3148 else
3149 Filters := CurFilter;
3151 if I < ImageFileFormats.Count - 1 then
3152 Descriptions := Descriptions + '|';
3154 Inc(Count);
3155 end;
3157 if (Count > 1) and OpenFileFilter then
3158 FmtStr(Descriptions, '%s (%s)|%1:s|%s', [SAllFilter, Filters, Descriptions]);
3160 Result := Descriptions;
3161 end;
3163 function GetFilterIndexExtension(Index: LongInt; OpenFileFilter: Boolean): string;
3164 var
3165 I, Count: LongInt;
3166 FileFormat: TImageFileFormat;
3167 begin
3168 // -1 because filter indices are in 1..n range
3169 Index := Index - 1;
3170 Result := '';
3171 if OpenFileFilter then
3172 begin
3173 if Index > 0 then
3174 Index := Index - 1;
3175 end;
3177 if (Index >= 0) and (Index < ImageFileFormats.Count) then
3178 begin
3179 Count := 0;
3180 for I := 0 to ImageFileFormats.Count - 1 do
3181 begin
3182 FileFormat := TObject(ImageFileFormats[I]) as TImageFileFormat;
3183 if not OpenFileFilter and not FileFormat.CanSave then
3184 Continue;
3185 if Index = Count then
3186 begin
3187 if FileFormat.Extensions.Count > 0 then
3188 Result := FileFormat.Extensions[0];
3189 Exit;
3190 end;
3191 Inc(Count);
3192 end;
3193 end;
3194 end;
3196 function GetFileNameFilterIndex(const FileName: string; OpenFileFilter: Boolean): LongInt;
3197 var
3198 I: LongInt;
3199 FileFormat: TImageFileFormat;
3200 begin
3201 Result := 0;
3202 for I := 0 to ImageFileFormats.Count - 1 do
3203 begin
3204 FileFormat := TObject(ImageFileFormats[I]) as TImageFileFormat;
3205 if not OpenFileFilter and not FileFormat.CanSave then
3206 Continue;
3207 if FileFormat.TestFileName(FileName) then
3208 begin
3209 // +1 because filter indices are in 1..n range
3210 Inc(Result);
3211 if OpenFileFilter then
3212 Inc(Result);
3213 Exit;
3214 end;
3215 Inc(Result);
3216 end;
3217 Result := -1;
3218 end;
3220 function GetIO: TIOFunctions;
3221 begin
3222 Result := IO;
3223 end;
3225 procedure RaiseImaging(const Msg: string; const Args: array of const);
3226 var
3227 WholeMsg: string;
3228 begin
3229 WholeMsg := Msg;
3230 if GetExceptObject <> nil then
3231 begin
3232 WholeMsg := WholeMsg + ' ' + SExceptMsg + ': ' +
3233 GetExceptObject.Message;
3234 end;
3235 raise EImagingError.CreateFmt(WholeMsg, Args);
3236 end;
3238 procedure RaiseImaging(const Msg: string);
3239 begin
3240 RaiseImaging(Msg, []);
3241 end;
3243 { Internal unit functions }
3245 function CheckOptionValue(OptionId, Value: LongInt): LongInt;
3246 begin
3247 case OptionId of
3248 ImagingColorReductionMask:
3249 Result := ClampInt(Value, 0, $FF);
3250 ImagingLoadOverrideFormat, ImagingSaveOverrideFormat:
3251 Result := Iff(ImagingFormats.IsImageFormatValid(TImageFormat(Value)),
3252 Value, LongInt(ifUnknown));
3253 ImagingMipMapFilter: Result := ClampInt(Value, Ord(Low(TSamplingFilter)),
3254 Ord(High(TSamplingFilter)));
3255 else
3256 Result := Value;
3257 end;
3258 end;
3260 procedure SetFileIO;
3261 begin
3262 IO := FileIO;
3263 end;
3265 procedure SetStreamIO;
3266 begin
3267 IO := StreamIO;
3268 end;
3270 procedure SetMemoryIO;
3271 begin
3272 IO := MemoryIO;
3273 end;
3275 procedure InitImageFormats;
3276 begin
3277 ImagingFormats.InitImageFormats(ImageFormatInfos);
3278 end;
3280 procedure FreeImageFileFormats;
3281 var
3282 I: LongInt;
3283 begin
3284 if ImageFileFormats <> nil then
3285 for I := 0 to ImageFileFormats.Count - 1 do
3286 TImageFileFormat(ImageFileFormats[I]).Free;
3287 FreeAndNil(ImageFileFormats);
3288 end;
3290 procedure InitOptions;
3291 begin
3292 SetLength(Options, InitialOptions);
3293 OptionStack := TOptionStack.Create;
3294 end;
3296 procedure FreeOptions;
3297 begin
3298 SetLength(Options, 0);
3299 FreeAndNil(OptionStack);
3300 end;
3303 TImageFileFormat class implementation
3306 constructor TImageFileFormat.Create(AMetadata: TMetadata);
3307 begin
3308 inherited Create;
3309 FName := SUnknownFormat;
3310 FExtensions := TStringList.Create;
3311 FMasks := TStringList.Create;
3312 if AMetadata = nil then
3313 FMetadata := GlobalMetadata
3314 else
3315 FMetadata := AMetadata;
3316 Define;
3317 end;
3319 destructor TImageFileFormat.Destroy;
3320 begin
3321 FExtensions.Free;
3322 FMasks.Free;
3323 inherited Destroy;
3324 end;
3326 procedure TImageFileFormat.Define;
3327 begin
3328 end;
3330 function TImageFileFormat.PrepareLoad(Handle: TImagingHandle;
3331 var Images: TDynImageDataArray; OnlyFirstFrame: Boolean): Boolean;
3332 begin
3333 FMetadata.ClearMetaItems; // Clear old metadata
3334 FreeImagesInArray(Images);
3335 SetLength(Images, 0);
3336 Result := Handle <> nil;
3337 end;
3339 function TImageFileFormat.PostLoadCheck(var Images: TDynImageDataArray;
3340 LoadResult: Boolean): Boolean;
3341 var
3342 I: LongInt;
3343 begin
3344 if not LoadResult then
3345 begin
3346 FreeImagesInArray(Images);
3347 SetLength(Images, 0);
3348 Result := False;
3349 end
3350 else
3351 begin
3352 Result := (Length(Images) > 0) and TestImagesInArray(Images);
3354 if Result then
3355 begin
3356 // Convert to overriden format if it is set
3357 if LoadOverrideFormat <> ifUnknown then
3358 for I := Low(Images) to High(Images) do
3359 ConvertImage(Images[I], LoadOverrideFormat);
3360 end;
3361 end;
3362 end;
3364 function TImageFileFormat.PrepareSave(Handle: TImagingHandle;
3365 const Images: TDynImageDataArray; var Index: Integer): Boolean;
3366 var
3367 Len, I: LongInt;
3368 begin
3369 CheckOptionsValidity;
3370 Result := False;
3371 if CanSave then
3372 begin
3373 Len := Length(Images);
3374 Assert(Len > 0);
3376 // If there are no images to be saved exit
3377 if Len = 0 then Exit;
3379 // Check index of image to be saved (-1 as index means save all images)
3380 if IsMultiImageFormat then
3381 begin
3382 if (Index >= Len) then
3383 Index := 0;
3385 if Index < 0 then
3386 begin
3387 Index := 0;
3388 FFirstIdx := 0;
3389 FLastIdx := Len - 1;
3390 end
3391 else
3392 begin
3393 FFirstIdx := Index;
3394 FLastIdx := Index;
3395 end;
3397 for I := FFirstIdx to FLastIdx - 1 do
3398 begin
3399 if not TestImage(Images[I]) then
3400 Exit;
3401 end;
3402 end
3403 else
3404 begin
3405 if (Index >= Len) or (Index < 0) then
3406 Index := 0;
3407 if not TestImage(Images[Index]) then
3408 Exit;
3409 end;
3411 Result := True;
3412 end;
3413 end;
3415 procedure TImageFileFormat.AddMasks(const AMasks: string);
3416 var
3417 I: LongInt;
3418 Ext: string;
3419 begin
3420 FExtensions.Clear;
3421 FMasks.CommaText := AMasks;
3422 FMasks.Delimiter := ';';
3424 for I := 0 to FMasks.Count - 1 do
3425 begin
3426 FMasks[I] := Trim(FMasks[I]);
3427 Ext := GetFileExt(FMasks[I]);
3428 if (Ext <> '') and (Ext <> '*') then
3429 FExtensions.Add(Ext);
3430 end;
3431 end;
3433 function TImageFileFormat.GetFormatInfo(Format: TImageFormat): TImageFormatInfo;
3434 begin
3435 Result := ImageFormatInfos[Format]^;
3436 end;
3438 function TImageFileFormat.GetSupportedFormats: TImageFormats;
3439 begin
3440 Result := FSupportedFormats;
3441 end;
3443 function TImageFileFormat.LoadData(Handle: TImagingHandle;
3444 var Images: TDynImageDataArray; OnlyFirstFrame: Boolean): Boolean;
3445 begin
3446 Result := False;
3447 RaiseImaging(SFileFormatCanNotLoad, [FName]);
3448 end;
3450 function TImageFileFormat.SaveData(Handle: TImagingHandle;
3451 const Images: TDynImageDataArray; Index: LongInt): Boolean;
3452 begin
3453 Result := False;
3454 RaiseImaging(SFileFormatCanNotSave, [FName]);
3455 end;
3457 procedure TImageFileFormat.ConvertToSupported(var Image: TImageData;
3458 const Info: TImageFormatInfo);
3459 begin
3460 end;
3462 function TImageFileFormat.IsSupported(const Image: TImageData): Boolean;
3463 begin
3464 Result := Image.Format in GetSupportedFormats;
3465 end;
3467 function TImageFileFormat.LoadFromFile(const FileName: string;
3468 var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
3469 var
3470 Handle: TImagingHandle;
3471 begin
3472 Result := False;
3473 if CanLoad then
3474 try
3475 // Set IO ops to file ops and open given file
3476 SetFileIO;
3477 Handle := IO.Open(PChar(FileName), omReadOnly);
3478 try
3479 // Test if file contains valid image and if so then load it
3480 if TestFormat(Handle) then
3481 begin
3482 Result := PrepareLoad(Handle, Images, OnlyFirstLevel) and
3483 LoadData(Handle, Images, OnlyFirstlevel);
3484 Result := PostLoadCheck(Images, Result);
3485 end
3486 else
3487 RaiseImaging(SFileNotValid, [FileName, Name]);
3488 finally
3489 IO.Close(Handle);
3490 end;
3491 except
3492 RaiseImaging(SErrorLoadingFile, [FileName, FExtensions[0]]);
3493 end;
3494 end;
3496 function TImageFileFormat.LoadFromStream(Stream: TStream;
3497 var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
3498 var
3499 Handle: TImagingHandle;
3500 OldPosition: Int64;
3501 begin
3502 Result := False;
3503 OldPosition := Stream.Position;
3504 if CanLoad then
3505 try
3506 // Set IO ops to stream ops and "open" given memory
3507 SetStreamIO;
3508 Handle := IO.Open(Pointer(Stream), omReadOnly);
3509 try
3510 // Test if stream contains valid image and if so then load it
3511 if TestFormat(Handle) then
3512 begin
3513 Result := PrepareLoad(Handle, Images, OnlyFirstLevel) and
3514 LoadData(Handle, Images, OnlyFirstlevel);
3515 Result := PostLoadCheck(Images, Result);
3516 end
3517 else
3518 RaiseImaging(SStreamNotValid, [@Stream, Name]);
3519 finally
3520 IO.Close(Handle);
3521 end;
3522 except
3523 Stream.Position := OldPosition;
3524 FreeImagesInArray(Images);
3525 RaiseImaging(SErrorLoadingStream, [@Stream, FExtensions[0]]);
3526 end;
3527 end;
3529 function TImageFileFormat.LoadFromMemory(Data: Pointer; Size: LongInt; var
3530 Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
3531 var
3532 Handle: TImagingHandle;
3533 IORec: TMemoryIORec;
3534 begin
3535 Result := False;
3536 if CanLoad then
3537 try
3538 // Set IO ops to memory ops and "open" given memory
3539 SetMemoryIO;
3540 IORec := PrepareMemIO(Data, Size);
3541 Handle := IO.Open(@IORec,omReadOnly);
3542 try
3543 // Test if memory contains valid image and if so then load it
3544 if TestFormat(Handle) then
3545 begin
3546 Result := PrepareLoad(Handle, Images, OnlyFirstLevel) and
3547 LoadData(Handle, Images, OnlyFirstlevel);
3548 Result := PostLoadCheck(Images, Result);
3549 end
3550 else
3551 RaiseImaging(SMemoryNotValid, [Data, Size, Name]);
3552 finally
3553 IO.Close(Handle);
3554 end;
3555 except
3556 RaiseImaging(SErrorLoadingMemory, [Data, Size, FExtensions[0]]);
3557 end;
3558 end;
3560 function TImageFileFormat.SaveToFile(const FileName: string;
3561 const Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
3562 var
3563 Handle: TImagingHandle;
3564 Len, Index, I: LongInt;
3565 Ext, FName: string;
3566 begin
3567 Result := False;
3568 if CanSave and TestImagesInArray(Images) then
3569 try
3570 SetFileIO;
3571 Len := Length(Images);
3572 if IsMultiImageFormat or
3573 (not IsMultiImageFormat and (OnlyFirstLevel or (Len = 1))) then
3574 begin
3575 Handle := IO.Open(PChar(FileName), GetSaveOpenMode);
3576 try
3577 if OnlyFirstLevel then
3578 Index := 0
3579 else
3580 Index := -1;
3581 // Write multi image to one file
3582 Result := PrepareSave(Handle, Images, Index) and SaveData(Handle, Images, Index);
3583 finally
3584 IO.Close(Handle);
3585 end;
3586 end
3587 else
3588 begin
3589 // Write multi image to file sequence
3590 Ext := ExtractFileExt(FileName);
3591 FName := ChangeFileExt(FileName, '');
3592 Result := True;
3593 for I := 0 to Len - 1 do
3594 begin
3595 Handle := IO.Open(PChar(Format(FName + '%.3d' + Ext, [I])), GetSaveOpenMode);
3596 try
3597 Index := I;
3598 Result := Result and PrepareSave(Handle, Images, Index) and
3599 SaveData(Handle, Images, Index);
3600 if not Result then
3601 Break;
3602 finally
3603 IO.Close(Handle);
3604 end;
3605 end;
3606 end;
3607 except
3608 raise UpdateExceptMessage(GetExceptObject, SErrorSavingFile, [FileName, FExtensions[0]]);
3609 end;
3610 end;
3612 function TImageFileFormat.SaveToStream(Stream: TStream;
3613 const Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
3614 var
3615 Handle: TImagingHandle;
3616 Len, Index, I: LongInt;
3617 OldPosition: Int64;
3618 begin
3619 Result := False;
3620 OldPosition := Stream.Position;
3621 if CanSave and TestImagesInArray(Images) then
3622 try
3623 SetStreamIO;
3624 Handle := IO.Open(PChar(Stream), GetSaveOpenMode);
3625 try
3626 if IsMultiImageFormat or OnlyFirstLevel then
3627 begin
3628 if OnlyFirstLevel then
3629 Index := 0
3630 else
3631 Index := -1;
3632 // Write multi image in one run
3633 Result := PrepareSave(Handle, Images, Index) and SaveData(Handle, Images, Index);
3634 end
3635 else
3636 begin
3637 // Write multi image to sequence
3638 Result := True;
3639 Len := Length(Images);
3640 for I := 0 to Len - 1 do
3641 begin
3642 Index := I;
3643 Result := Result and PrepareSave(Handle, Images, Index) and
3644 SaveData(Handle, Images, Index);
3645 if not Result then
3646 Break;
3647 end;
3648 end;
3649 finally
3650 IO.Close(Handle);
3651 end;
3652 except
3653 Stream.Position := OldPosition;
3654 raise UpdateExceptMessage(GetExceptObject, SErrorSavingStream, [@Stream, FExtensions[0]]);
3655 end;
3656 end;
3658 function TImageFileFormat.SaveToMemory(Data: Pointer; var Size: LongInt;
3659 const Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
3660 var
3661 Handle: TImagingHandle;
3662 Len, Index, I: LongInt;
3663 IORec: TMemoryIORec;
3664 begin
3665 Result := False;
3666 if CanSave and TestImagesInArray(Images) then
3667 try
3668 SetMemoryIO;
3669 IORec := PrepareMemIO(Data, Size);
3670 Handle := IO.Open(PChar(@IORec), GetSaveOpenMode);
3671 try
3672 if IsMultiImageFormat or OnlyFirstLevel then
3673 begin
3674 if OnlyFirstLevel then
3675 Index := 0
3676 else
3677 Index := -1;
3678 // Write multi image in one run
3679 Result := PrepareSave(Handle, Images, Index) and SaveData(Handle, Images, Index);
3680 end
3681 else
3682 begin
3683 // Write multi image to sequence
3684 Result := True;
3685 Len := Length(Images);
3686 for I := 0 to Len - 1 do
3687 begin
3688 Index := I;
3689 Result := Result and PrepareSave(Handle, Images, Index) and
3690 SaveData(Handle, Images, Index);
3691 if not Result then
3692 Break;
3693 end;
3694 end;
3695 Size := IORec.Position;
3696 finally
3697 IO.Close(Handle);
3698 end;
3699 except
3700 raise UpdateExceptMessage(GetExceptObject, SErrorSavingMemory, [Data, Size, FExtensions[0]]);
3701 end;
3702 end;
3704 function TImageFileFormat.MakeCompatible(const Image: TImageData;
3705 var Compatible: TImageData; out MustBeFreed: Boolean): Boolean;
3706 begin
3707 InitImage(Compatible);
3709 if SaveOverrideFormat <> ifUnknown then
3710 begin
3711 // Save format override is active. Clone input and convert it to override format.
3712 CloneImage(Image, Compatible);
3713 ConvertImage(Compatible, SaveOverrideFormat);
3714 // Now check if override format is supported by file format. If it is not
3715 // then file format specific conversion (virtual method) is called.
3716 Result := IsSupported(Compatible);
3717 if not Result then
3718 begin
3719 ConvertToSupported(Compatible, GetFormatInfo(Compatible.Format));
3720 Result := IsSupported(Compatible);
3721 end;
3722 end // Add IsCompatible function! not only checking by Format
3723 else if IsSupported(Image) then
3724 begin
3725 // No save format override and input is in format supported by this
3726 // file format. Just copy Image's fields to Compatible
3727 Compatible := Image;
3728 Result := True;
3729 end
3730 else
3731 begin
3732 // No override and input's format is not compatible with file format.
3733 // Clone it and the call file format specific conversion (virtual method).
3734 CloneImage(Image, Compatible);
3735 ConvertToSupported(Compatible, GetFormatInfo(Compatible.Format));
3736 Result := IsSupported(Compatible);
3737 end;
3738 // Tell the user that he must free Compatible after he's done with it
3739 // (if necessary).
3740 MustBeFreed := Image.Bits <> Compatible.Bits;
3741 end;
3743 function TImageFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
3744 begin
3745 Result := False;
3746 end;
3748 function TImageFileFormat.TestFileName(const FileName: string): Boolean;
3749 var
3750 I: LongInt;
3751 OnlyName: string;
3752 begin
3753 OnlyName := ExtractFileName(FileName);
3754 // For each mask test if filename matches it
3755 for I := 0 to FMasks.Count - 1 do
3756 if StrMaskMatch(OnlyName, FMasks[I], False) then
3757 begin
3758 Result := True;
3759 Exit;
3760 end;
3761 Result := False;
3762 end;
3764 procedure TImageFileFormat.CheckOptionsValidity;
3765 begin
3766 end;
3768 function TImageFileFormat.GetCanLoad: Boolean;
3769 begin
3770 Result := ffLoad in FFeatures;
3771 end;
3773 function TImageFileFormat.GetCanSave: Boolean;
3774 begin
3775 Result := ffSave in FFeatures;
3776 end;
3778 function TImageFileFormat.GetIsMultiImageFormat: Boolean;
3779 begin
3780 Result := ffMultiImage in FFeatures;
3781 end;
3783 function TImageFileFormat.GetSaveOpenMode: TOpenMode;
3784 begin
3785 // TODO: fix
3786 //if ffReadOnSave in FFeatures then
3787 // Result := omReadWrite
3788 //else
3789 Result := omCreate;
3790 end;
3792 { TOptionStack class implementation }
3794 constructor TOptionStack.Create;
3795 begin
3796 inherited Create;
3797 FPosition := -1;
3798 end;
3800 destructor TOptionStack.Destroy;
3801 var
3802 I: LongInt;
3803 begin
3804 for I := 0 to OptionStackDepth - 1 do
3805 SetLength(FStack[I], 0);
3806 inherited Destroy;
3807 end;
3809 function TOptionStack.Pop: Boolean;
3810 var
3811 I: LongInt;
3812 begin
3813 Result := False;
3814 if FPosition >= 0 then
3815 begin
3816 SetLength(Options, Length(FStack[FPosition]));
3817 for I := 0 to Length(FStack[FPosition]) - 1 do
3818 if Options[I] <> nil then
3819 Options[I]^ := FStack[FPosition, I];
3820 Dec(FPosition);
3821 Result := True;
3822 end;
3823 end;
3825 function TOptionStack.Push: Boolean;
3826 var
3827 I: LongInt;
3828 begin
3829 Result := False;
3830 if FPosition < OptionStackDepth - 1 then
3831 begin
3832 Inc(FPosition);
3833 SetLength(FStack[FPosition], Length(Options));
3834 for I := 0 to Length(Options) - 1 do
3835 if Options[I] <> nil then
3836 FStack[FPosition, I] := Options[I]^;
3837 Result := True;
3838 end;
3839 end;
3841 { TMetadata }
3843 procedure TMetadata.SetMetaItem(const Id: string; const Value: Variant;
3844 ImageIndex: Integer);
3845 begin
3846 AddMetaToList(FLoadMetaItems, Id, Value, ImageIndex);
3847 end;
3849 procedure TMetadata.SetMetaItemForSaving(const Id: string; const Value: Variant;
3850 ImageIndex: Integer);
3851 begin
3852 AddMetaToList(FSaveMetaItems, Id, Value, ImageIndex);
3853 end;
3855 procedure TMetadata.AddMetaToList(List: TStringList; const Id: string;
3856 const Value: Variant; ImageIndex: Integer);
3857 var
3858 Item: TMetadataItem;
3859 Idx: Integer;
3860 FullId: string;
3861 begin
3862 FullId := GetMetaItemName(Id, ImageIndex);
3863 if List.Find(FullId, Idx) then
3864 (List.Objects[Idx] as TMetadataItem).Value := Value
3865 else
3866 begin
3867 Item := TMetadataItem.Create;
3868 Item.Id := Id;
3869 Item.ImageIndex := ImageIndex;
3870 Item.Value := Value;
3871 List.AddObject(FullId, Item);
3872 end;
3873 end;
3875 procedure TMetadata.ClearMetaItems;
3876 begin
3877 ClearMetaList(FLoadMetaItems);
3878 end;
3880 procedure TMetadata.ClearMetaItemsForSaving;
3881 begin
3882 ClearMetaList(FSaveMetaItems);
3883 end;
3885 procedure TMetadata.ClearMetaList(List: TStringList);
3886 var
3887 I: Integer;
3888 begin
3889 for I := 0 to List.Count - 1 do
3890 List.Objects[I].Free;
3891 List.Clear;
3892 end;
3894 procedure TMetadata.CopyLoadedMetaItemsForSaving;
3895 var
3896 I: Integer;
3897 Copy, Orig: TMetadataItem;
3898 begin
3899 ClearMetaItemsForSaving;
3900 for I := 0 to FLoadMetaItems.Count - 1 do
3901 begin
3902 Orig := TMetadataItem(FLoadMetaItems.Objects[I]);
3903 Copy := TMetadataItem.Create;
3904 Copy.Id := Orig.Id;
3905 Copy.ImageIndex := Orig.ImageIndex;
3906 Copy.Value := Orig.Value;
3907 FSaveMetaItems.AddObject(GetMetaItemName(Copy.Id, Copy.ImageIndex), Copy);
3908 end;
3909 end;
3911 constructor TMetadata.Create;
3912 begin
3913 inherited;
3914 FLoadMetaItems := TStringList.Create;
3915 FLoadMetaItems.Sorted := True;
3916 FSaveMetaItems := TStringList.Create;
3917 FSaveMetaItems.Sorted := True;
3918 end;
3920 destructor TMetadata.Destroy;
3921 begin
3922 ClearMetaItems;
3923 ClearMetaItemsForSaving;
3924 FLoadMetaItems.Free;
3925 FSaveMetaItems.Free;
3926 inherited;
3927 end;
3929 function TMetadata.GetMetaById(const Id: string): Variant;
3930 var
3931 Idx: Integer;
3932 begin
3933 if FLoadMetaItems.Find(Id, Idx) then
3934 Result := (FLoadMetaItems.Objects[Idx] as TMetadataItem).Value
3935 else
3936 Result := Variants.Null;
3937 end;
3939 function TMetadata.GetMetaByIdMulti(const Id: string; ImageIndex: Integer): Variant;
3940 begin
3941 Result := GetMetaById(GetMetaItemName(Id, ImageIndex));
3942 end;
3944 function TMetadata.GetSaveMetaById(const Id: string): Variant;
3945 var
3946 Idx: Integer;
3947 begin
3948 if FSaveMetaItems.Find(Id, Idx) then
3949 Result := (FSaveMetaItems.Objects[Idx] as TMetadataItem).Value
3950 else
3951 Result := Variants.Null;
3952 end;
3954 function TMetadata.GetSaveMetaByIdMulti(const Id: string;
3955 ImageIndex: Integer): Variant;
3956 begin
3957 Result := GetSaveMetaById(GetMetaItemName(Id, ImageIndex));
3958 end;
3960 function TMetadata.GetMetaByIdx(Index: Integer): TMetadataItem;
3961 begin
3962 Result := FLoadMetaItems.Objects[Index] as TMetadataItem;
3963 end;
3965 function TMetadata.GetMetaCount: Integer;
3966 begin
3967 Result := FLoadMetaItems.Count;
3968 end;
3970 function TMetadata.GetMetaItemName(const Id: string;
3971 ImageIndex: Integer): string;
3972 begin
3973 Result := Iff(ImageIndex = 0, Id, Format(SMetaIdForSubImage, [Id, ImageIndex]));
3974 end;
3976 function TMetadata.GetPhysicalPixelSize(ResUnit: TResolutionUnit; var XSize,
3977 YSize: Single; MetaForSave: Boolean; ImageIndex: Integer): Boolean;
3978 type
3979 TGetter = function(const Id: string; ImageIndex: Integer): Variant of object;
3980 var
3981 Getter: TGetter;
3982 XMeta, YMeta: Variant;
3983 begin
3984 if MetaForSave then
3985 Getter := GetSaveMetaByIdMulti
3986 else
3987 Getter := GetMetaByIdMulti;
3989 XMeta := Getter(SMetaPhysicalPixelSizeX, ImageIndex);
3990 YMeta := Getter(SMetaPhysicalPixelSizeY, ImageIndex);
3991 XSize := -1;
3992 YSize := -1;
3994 Result := not VarIsNull(XMeta) or not VarIsNull(YMeta);
3996 if not Result then
3997 Exit;
3999 if not VarIsNull(XMeta) then
4000 XSize := XMeta;
4001 if not VarIsNull(YMeta) then
4002 YSize := YMeta;
4004 if XSize < 0 then
4005 XSize := YSize;
4006 if YSize < 0 then
4007 YSize := XSize;
4009 TranslateUnits(ResUnit, XSize, YSize);
4010 end;
4012 procedure TMetadata.SetPhysicalPixelSize(ResUnit: TResolutionUnit; XSize,
4013 YSize: Single; MetaForSave: Boolean; ImageIndex: Integer);
4014 type
4015 TAdder = procedure(const Id: string; const Value: Variant; ImageIndex: Integer) of object;
4016 var
4017 Adder: TAdder;
4018 begin
4019 TranslateUnits(ResUnit, XSize, YSize);
4021 if MetaForSave then
4022 Adder := SetMetaItemForSaving
4023 else
4024 Adder := SetMetaItem;
4026 Adder(SMetaPhysicalPixelSizeX, XSize, ImageIndex);
4027 Adder(SMetaPhysicalPixelSizeY, YSize, ImageIndex);
4028 end;
4030 procedure TMetadata.TranslateUnits(ResolutionUnit: TResolutionUnit; var XRes,
4031 YRes: Single);
4032 var
4033 UnitSize: Single;
4034 begin
4035 case ResolutionUnit of
4036 ruDpi: UnitSize := 25400;
4037 ruDpm: UnitSize := 1e06;
4038 ruDpcm: UnitSize := 1e04;
4039 else
4040 UnitSize := 1;
4041 end;
4042 if ResolutionUnit <> ruSizeInMicroMeters then
4043 begin
4044 XRes := UnitSize / XRes;
4045 YRes := UnitSize / YRes;
4046 end;
4047 end;
4049 function TMetadata.HasMetaItem(const Id: string; ImageIndex: Integer): Boolean;
4050 begin
4051 Result := GetMetaByIdMulti(Id, ImageIndex) <> Variants.Null;
4052 end;
4054 function TMetadata.HasMetaItemForSaving(const Id: string; ImageIndex: Integer): Boolean;
4055 begin
4056 Result := GetSaveMetaByIdMulti(Id, ImageIndex) <> Variants.Null;
4057 end;
4059 initialization
4060 {$IFDEF MEMCHECK}
4061 {$IF CompilerVersion >= 18}
4062 System.ReportMemoryLeaksOnShutdown := True;
4063 {$IFEND}
4064 {$ENDIF}
4065 if GlobalMetadata = nil then
4066 GlobalMetadata := TMetadata.Create;
4067 if ImageFileFormats = nil then
4068 ImageFileFormats := TList.Create;
4069 InitImageFormats;
4070 RegisterOption(ImagingColorReductionMask, @ColorReductionMask);
4071 RegisterOption(ImagingLoadOverrideFormat, @LoadOverrideFormat);
4072 RegisterOption(ImagingSaveOverrideFormat, @SaveOverrideFormat);
4073 RegisterOption(ImagingMipMapFilter, @MipMapFilter);
4074 RegisterOption(ImagingBinaryTreshold, @BinaryTreshold);
4075 finalization
4076 FreeOptions;
4077 FreeImageFileFormats;
4078 GlobalMetadata.Free;
4081 File Notes:
4083 -- TODOS ----------------------------------------------------
4084 - nothing now
4086 -- 0.77.1 ---------------------------------------------------
4087 - Updated IO Open functions according to changes in ImagingTypes.
4088 - Fixed bug in SplitImage that could cause wrong size of edge chunks.
4089 - Metadata support fixes and extensions (frame delays, animation loops).
4091 -- 0.26.5 Changes/Bug Fixes ---------------------------------
4092 - Started reworking exception raising to keep the original class type
4093 (e.g. in NewImage EOutOfMemory could be raised but was hidden
4094 by EImagingError raised afterwards in NewImage try/except).
4095 - Fixed possible AV in Rotate45 subproc of RotateImage.
4096 - Added ReadRawXXX and WriteRawXXX functions for raw image bits IO.
4097 - Implemented ImagingBinaryTreshold option.
4098 - Added support for simple image metadata loading/saving.
4099 - Moved file format definition (name, exts, caps, ...) from
4100 constructor to new Define method.
4101 - Fixed some memory leaks caused by failures during image loading.
4103 -- 0.26.3 Changes/Bug Fixes ---------------------------------
4104 - Extended RotateImage to allow arbitrary angle rotations.
4105 - Reversed the order file formats list is searched so
4106 if you register a new one it will be found sooner than
4107 built in formats.
4108 - Fixed memory leak in ResizeImage ocurring when resizing
4109 indexed images.
4111 -- 0.26.1 Changes/Bug Fixes ---------------------------------
4112 - Added position/size checks to LoadFromStream functions.
4113 - Changed conditional compilation in impl. uses section to reflect changes
4114 in LINK symbols.
4116 -- 0.24.3 Changes/Bug Fixes ---------------------------------
4117 - GenerateMipMaps now generates all smaller levels from
4118 original big image (better results when using more advanced filters).
4119 Also conversion to compatible image format is now done here not
4120 in FillMipMapLevel (that is called for every mipmap level).
4122 -- 0.23 Changes/Bug Fixes -----------------------------------
4123 - MakePaletteForImages now works correctly for indexed and special format images
4124 - Fixed bug in StretchRect: Image was not properly stretched if
4125 src and dst dimensions differed only in height.
4126 - ConvertImage now fills new image with zeroes to avoid random data in
4127 some conversions (RGB->XRGB)
4128 - Changed RegisterOption procedure to function
4129 - Changed bunch of palette functions from low level interface to procedure
4130 (there was no reason for them to be functions).
4131 - Changed FreeImage and FreeImagesInArray functions to procedures.
4132 - Added many assertions, come try-finally, other checks, and small code
4133 and doc changes.
4135 -- 0.21 Changes/Bug Fixes -----------------------------------
4136 - GenerateMipMaps threw failed assertion when input was indexed or special,
4137 fixed.
4138 - Added CheckOptionsValidity to TImageFileFormat and its decendants.
4139 - Unit ImagingExtras which registers file formats in Extras package
4140 is now automatically added to uses clause if LINK_EXTRAS symbol is
4141 defined in ImagingOptions.inc file.
4142 - Added EnumFileFormats function to low level interface.
4143 - Fixed bug in SwapChannels which could cause AV when swapping alpha
4144 channel of A8R8G8B8 images.
4145 - Converting loaded images to ImagingOverrideFormat is now done
4146 in PostLoadCheck method to avoid code duplicity.
4147 - Added GetFileFormatCount and GetFileFormatAtIndex functions
4148 - Bug in ConvertImage: if some format was converted to similar format
4149 only with swapped channels (R16G16B16<>B16G16R16) then channels were
4150 swapped correctly but new data format (swapped one) was not set.
4151 - Made TImageFileFormat.MakeCompatible public non-virtual method
4152 (and modified its function). Created new virtual
4153 ConvertToSupported which should be overriden by descendants.
4154 Main reason for doint this is to avoid duplicate code that was in all
4155 TImageFileFormat's descendants.
4156 - Changed TImageFileFormat.GetFormatInfo's result type to TImageFormatInfo.
4157 - Split overloaded FindImageFileFormat functions to
4158 FindImageFileFormatByClass and FindImageFileFormatByExt and created new
4159 FindImageFileFormatByName which operates on whole filenames.
4160 - Function GetExtensionFilterIndex renamed to GetFileNameFilterIndex
4161 (because it now works with filenames not extensions).
4162 - DetermineFileFormat now first searches by filename and if not found
4163 then by data.
4164 - Added TestFileName method to TImageFileFormat.
4165 - Updated GetImageFileFormatsFilter to uses Masks instead of Extensions
4166 property of TImageFileFormat. Also you can now request
4167 OpenDialog and SaveDialog type filters
4168 - Added Masks property and AddMasks method to TImageFileFormat.
4169 AddMasks replaces AddExtensions, it uses filename masks instead
4170 of sime filename extensions to identify supported files.
4171 - Changed TImageFileFormat.LoadData procedure to function and
4172 moved varios duplicate code from its descandats (check index,...)
4173 here to TImageFileFormat helper methods.
4174 - Changed TImageFileFormat.SaveData procedure to function and
4175 moved varios duplicate code from its descandats (check index,...)
4176 here to TImageFileFormat helper methods.
4177 - Removed RAISE_EXCEPTIONS define, exceptions are now raised everytime
4178 - Added MustBeFreed parameter to TImageFileFormat.MakeComptible method
4179 that indicates that compatible image returned by this method must be
4180 freed after its usage.
4182 -- 0.19 Changes/Bug Fixes -----------------------------------
4183 - fixed bug in NewImage: if given format was ifDefault it wasn't
4184 replaced with DefaultImageFormat constant which caused problems later
4185 in other units
4186 - fixed bug in RotateImage which caused that rotated special format
4187 images were whole black
4188 - LoadImageFromXXX and LoadMultiImageFromXXX now use DetermineXXXFormat
4189 when choosing proper loader, this eliminated need for Ext parameter
4190 in stream and memory loading functions
4191 - added GetVersionStr function
4192 - fixed bug in ResizeImage which caued indexed images to lose their
4193 palette during process resulting in whole black image
4194 - Clipping in ...Rect functions now uses clipping procs from ImagingUtility,
4195 it also works better
4196 - FillRect optimization for 8, 16, and 32 bit formats
4197 - added pixel set/get functions to low level interface:
4198 GetPixelDirect, SetPixelDirect, GetPixel32, SetPixel32,
4199 GetPixelFP, SetPixelFP
4200 - removed GetPixelBytes low level intf function - redundant
4201 (same data can be obtained by GetImageFormatInfo)
4202 - made small changes in many parts of library to compile
4203 on AMD64 CPU (Linux with FPC)
4204 - changed InitImage to procedure (function was pointless)
4205 - Method TestFormat of TImageFileFormat class made public
4206 (was protected)
4207 - added function IsFileFormatSupported to low level interface
4208 (contributed by Paul Michell)
4209 - fixed some missing format arguments from error strings
4210 which caused Format function to raise exception
4211 - removed forgotten debug code that disabled filtered resizing of images with
4212 channel bitcounts > 8
4214 -- 0.17 Changes/Bug Fixes -----------------------------------
4215 - changed order of parameters of CopyRect function
4216 - GenerateMipMaps now filters mipmap levels
4217 - ResizeImage functions was extended to allow bilinear and bicubic filtering
4218 - added StretchRect function to low level interface
4219 - added functions GetImageFileFormatsFilter, GetFilterIndexExtension,
4220 and GetExtensionFilterIndex
4222 -- 0.15 Changes/Bug Fixes -----------------------------------
4223 - added function RotateImage to low level interface
4224 - moved TImageFormatInfo record and types required by it to
4225 ImagingTypes unit, changed GetImageFormatInfo low level
4226 interface function to return TImageFormatInfo instead of short info
4227 - added checking of options values validity before they are used
4228 - fixed possible memory leak in CloneImage
4229 - added ReplaceColor function to low level interface
4230 - new function FindImageFileFormat by class added
4232 -- 0.13 Changes/Bug Fixes -----------------------------------
4233 - added DetermineFileFormat, DetermineStreamFormat, DetermineMemoryFormat,
4234 GetPixelsSize functions to low level interface
4235 - added NewPalette, CopyPalette, FreePalette functions
4236 to low level interface
4237 - added MapImageToPalette, FillRect, SplitImage, MakePaletteForImages
4238 functions to low level interface
4239 - fixed buggy FillCustomPalette function (possible div by zero and others)
4240 - added CopyRect function to low level interface
4241 - Member functions of TImageFormatInfo record implemented for all formats
4242 - before saving images TestImagesInArray is called now
4243 - added TestImagesInArray function to low level interface
4244 - added GenerateMipMaps function to low level interface
4245 - stream position in load/save from/to stream is now set to position before
4246 function was called if error occurs
4247 - when error occured during load/save from/to file file handle
4248 was not released
4249 - CloneImage returned always False
4252 end.