{ $Id: ImagingFormats.pas 174 2009-09-08 09:37:59Z galfar $ Vampyre Imaging Library by Marek Mauder http://imaginglib.sourceforge.net The contents of this file are used with permission, subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/MPL-1.1.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. Alternatively, the contents of this file may be used under the terms of the GNU Lesser General Public License (the "LGPL License"), in which case the provisions of the LGPL License are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of the LGPL License and not to allow others to use your version of this file under the MPL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the LGPL License. If you do not delete the provisions above, a recipient may use your version of this file under either the MPL or the LGPL License. For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html } { This unit manages information about all image data formats and contains low level format conversion, manipulation, and other related functions.} unit ImagingFormats; {$I ImagingOptions.inc} interface uses ImagingTypes, Imaging, ImagingUtility; type TImageFormatInfoArray = array[TImageFormat] of PImageFormatInfo; PImageFormatInfoArray = ^TImageFormatInfoArray; { Additional image manipulation functions (usually used internally by Imaging unit) } type { Color reduction operations.} TReduceColorsAction = (raCreateHistogram, raUpdateHistogram, raMakeColorMap, raMapImage); TReduceColorsActions = set of TReduceColorsAction; const AllReduceColorsActions = [raCreateHistogram, raUpdateHistogram, raMakeColorMap, raMapImage]; { Reduces the number of colors of source. Src is bits of source image (ARGB or floating point) and Dst is in some indexed format. MaxColors is the number of colors to which reduce and DstPal is palette to which the resulting colors are written and it must be allocated to at least MaxColors entries. ChannelMask is 'anded' with every pixel's channel value when creating color histogram. If $FF is used all 8bits of color channels are used which can be slow for large images with many colors so you can use lower masks to speed it up.} procedure ReduceColorsMedianCut(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo; MaxColors: LongInt; ChannelMask: Byte; DstPal: PPalette32; Actions: TReduceColorsActions = AllReduceColorsActions); { Stretches rectangle in source image to rectangle in destination image using nearest neighbor filtering. It is fast but results look blocky because there is no interpolation used. SrcImage and DstImage must be in the same data format. Works for all data formats except special formats.} procedure StretchNearest(const SrcImage: TImageData; SrcX, SrcY, SrcWidth, SrcHeight: LongInt; var DstImage: TImageData; DstX, DstY, DstWidth, DstHeight: LongInt); type { Built-in sampling filters.} TSamplingFilter = (sfNearest, sfLinear, sfCosine, sfHermite, sfQuadratic, sfGaussian, sfSpline, sfLanczos, sfMitchell, sfCatmullRom); { Type of custom sampling function} TFilterFunction = function(Value: Single): Single; const { Default resampling filter used for bicubic resizing.} DefaultCubicFilter = sfCatmullRom; var { Built-in filter functions.} SamplingFilterFunctions: array[TSamplingFilter] of TFilterFunction; { Default radii of built-in filter functions.} SamplingFilterRadii: array[TSamplingFilter] of Single; { Stretches rectangle in source image to rectangle in destination image with resampling. One of built-in resampling filters defined by Filter is used. Set WrapEdges to True for seamlessly tileable images. SrcImage and DstImage must be in the same data format. Works for all data formats except special and indexed formats.} procedure StretchResample(const SrcImage: TImageData; SrcX, SrcY, SrcWidth, SrcHeight: LongInt; var DstImage: TImageData; DstX, DstY, DstWidth, DstHeight: LongInt; Filter: TSamplingFilter; WrapEdges: Boolean = False); overload; { Stretches rectangle in source image to rectangle in destination image with resampling. You can use custom sampling function and filter radius. Set WrapEdges to True for seamlessly tileable images. SrcImage and DstImage must be in the same data format. Works for all data formats except special and indexed formats.} procedure StretchResample(const SrcImage: TImageData; SrcX, SrcY, SrcWidth, SrcHeight: LongInt; var DstImage: TImageData; DstX, DstY, DstWidth, DstHeight: LongInt; Filter: TFilterFunction; Radius: Single; WrapEdges: Boolean = False); overload; { Helper for functions that create mipmap levels. BiggerLevel is valid image and SmallerLevel is empty zeroed image. SmallerLevel is created with Width and Height dimensions and it is filled with pixels of BiggerLevel using resampling filter specified by ImagingMipMapFilter option. Uses StretchNearest and StretchResample internally so the same image data format limitations apply.} procedure FillMipMapLevel(const BiggerLevel: TImageData; Width, Height: LongInt; var SmallerLevel: TImageData); { Various helper & support functions } { Copies Src pixel to Dest pixel. It is faster than System.Move procedure.} procedure CopyPixel(Src, Dest: Pointer; BytesPerPixel: LongInt); {$IFDEF USE_INLINE}inline;{$ENDIF} { Compares Src pixel and Dest pixel. It is faster than SysUtils.CompareMem function.} function ComparePixels(PixelA, PixelB: Pointer; BytesPerPixel: LongInt): Boolean; {$IFDEF USE_INLINE}inline;{$ENDIF} { Translates pixel color in SrcFormat to DstFormat.} procedure TranslatePixel(SrcPixel, DstPixel: Pointer; SrcFormat, DstFormat: TImageFormat; SrcPalette, DstPalette: PPalette32); { Clamps floating point pixel channel values to [0.0, 1.0] range.} procedure ClampFloatPixel(var PixF: TColorFPRec); {$IFDEF USE_INLINE}inline;{$ENDIF} { Adds padding bytes at the ends of scanlines. Bpp is the number of bytes per pixel of source and WidthBytes is the number of bytes per scanlines of dest.} procedure AddPadBytes(DataIn: Pointer; DataOut: Pointer; Width, Height, Bpp, WidthBytes: LongInt); { Removes padding from image with scanlines that have aligned sizes. Bpp is the number of bytes per pixel of dest and WidthBytes is the number of bytes per scanlines of source.} procedure RemovePadBytes(DataIn: Pointer; DataOut: Pointer; Width, Height, Bpp, WidthBytes: LongInt); { Converts 1bit image data to 8bit (without scaling). Used by file loaders for formats supporting 1bit images.} procedure Convert1To8(DataIn, DataOut: Pointer; Width, Height, WidthBytes: LongInt); { Converts 2bit image data to 8bit (without scaling). Used by file loaders for formats supporting 2bit images.} procedure Convert2To8(DataIn, DataOut: Pointer; Width, Height, WidthBytes: LongInt); { Converts 4bit image data to 8bit (without scaling). Used by file loaders for formats supporting 4bit images.} procedure Convert4To8(DataIn, DataOut: Pointer; Width, Height, WidthBytes: LongInt); { Helper function for image file loaders. Some 15 bit images (targas, bitmaps) may contain 1 bit alpha but there is no indication of it. This function checks all 16 bit(should be X1R5G5B5 or A1R5G5B5 format) pixels and some of them have alpha bit set it returns True, otherwise False.} function Has16BitImageAlpha(NumPixels: LongInt; Data: PWord): Boolean; { Helper function for image file loaders. This function checks is similar to Has16BitImageAlpha but works with A8R8G8B8 format.} function Has32BitImageAlpha(NumPixels: LongInt; Data: PLongWord): Boolean; { Provides indexed access to each line of pixels. Does not work with special format images.} function GetScanLine(ImageBits: Pointer; const FormatInfo: TImageFormatInfo; LineWidth, Index: LongInt): Pointer; {$IFDEF USE_INLINE}inline;{$ENDIF} { Returns True if Format is valid image data format identifier.} function IsImageFormatValid(Format: TImageFormat): Boolean; { Converts 16bit half floating point value to 32bit Single.} function HalfToFloat(Half: THalfFloat): Single; { Converts 32bit Single to 16bit half floating point.} function FloatToHalf(Float: Single): THalfFloat; { Converts half float color value to single-precision floating point color.} function ColorHalfToFloat(ColorHF: TColorHFRec): TColorFPRec; {$IFDEF USE_INLINE}inline;{$ENDIF} { Converts single-precision floating point color to half float color.} function ColorFloatToHalf(ColorFP: TColorFPRec): TColorHFRec; {$IFDEF USE_INLINE}inline;{$ENDIF} { Makes image PalEntries x 1 big where each pixel has color of one pal entry.} procedure VisualizePalette(Pal: PPalette32; Entries: Integer; out PalImage: TImageData); type TPointRec = record Pos: LongInt; Weight: Single; end; TCluster = array of TPointRec; TMappingTable = array of TCluster; { Helper function for resampling.} function BuildMappingTable(DstLow, DstHigh, SrcLow, SrcHigh, SrcImageWidth: LongInt; Filter: TFilterFunction; Radius: Single; WrapEdges: Boolean): TMappingTable; { Helper function for resampling.} procedure FindExtremes(const Map: TMappingTable; var MinPos, MaxPos: LongInt); { Pixel readers/writers for different image formats } { Returns pixel of image in any ARGB format. Channel values are scaled to 16 bits.} procedure ChannelGetSrcPixel(Src: PByte; SrcInfo: PImageFormatInfo; var Pix: TColor64Rec); { Sets pixel of image in any ARGB format. Channel values must be scaled to 16 bits.} procedure ChannelSetDstPixel(Dst: PByte; DstInfo: PImageFormatInfo; const Pix: TColor64Rec); { Returns pixel of image in any grayscale format. Gray value is scaled to 64 bits and alpha to 16 bits.} procedure GrayGetSrcPixel(Src: PByte; SrcInfo: PImageFormatInfo; var Gray: TColor64Rec; var Alpha: Word); { Sets pixel of image in any grayscale format. Gray value must be scaled to 64 bits and alpha to 16 bits.} procedure GraySetDstPixel(Dst: PByte; DstInfo: PImageFormatInfo; const Gray: TColor64Rec; Alpha: Word); { Returns pixel of image in any floating point format. Channel values are in range <0.0, 1.0>.} procedure FloatGetSrcPixel(Src: PByte; SrcInfo: PImageFormatInfo; var Pix: TColorFPRec); { Sets pixel of image in any floating point format. Channel values must be in range <0.0, 1.0>.} procedure FloatSetDstPixel(Dst: PByte; DstInfo: PImageFormatInfo; const Pix: TColorFPRec); { Returns pixel of image in any indexed format. Returned value is index to the palette.} procedure IndexGetSrcPixel(Src: PByte; SrcInfo: PImageFormatInfo; var Index: LongWord); { Sets pixel of image in any indexed format. Index is index to the palette.} procedure IndexSetDstPixel(Dst: PByte; DstInfo: PImageFormatInfo; Index: LongWord); { Pixel readers/writers for 32bit and FP colors} { Function for getting pixel colors. Native pixel is read from Image and then translated to 32 bit ARGB.} function GetPixel32Generic(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32): TColor32Rec; { Procedure for setting pixel colors. Input 32 bit ARGB color is translated to native format and then written to Image.} procedure SetPixel32Generic(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32; const Color: TColor32Rec); { Function for getting pixel colors. Native pixel is read from Image and then translated to FP ARGB.} function GetPixelFPGeneric(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32): TColorFPRec; { Procedure for setting pixel colors. Input FP ARGB color is translated to native format and then written to Image.} procedure SetPixelFPGeneric(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32; const Color: TColorFPRec); { Image format conversion functions } { Converts any ARGB format to any ARGB format.} procedure ChannelToChannel(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo); { Converts any ARGB format to any grayscale format.} procedure ChannelToGray(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo); { Converts any ARGB format to any floating point format.} procedure ChannelToFloat(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo); { Converts any ARGB format to any indexed format.} procedure ChannelToIndex(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo; DstPal: PPalette32); { Converts any grayscale format to any grayscale format.} procedure GrayToGray(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo); { Converts any grayscale format to any ARGB format.} procedure GrayToChannel(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo); { Converts any grayscale format to any floating point format.} procedure GrayToFloat(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo); { Converts any grayscale format to any indexed format.} procedure GrayToIndex(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo; DstPal: PPalette32); { Converts any floating point format to any floating point format.} procedure FloatToFloat(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo); { Converts any floating point format to any ARGB format.} procedure FloatToChannel(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo); { Converts any floating point format to any grayscale format.} procedure FloatToGray(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo); { Converts any floating point format to any indexed format.} procedure FloatToIndex(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo; DstPal: PPalette32); { Converts any indexed format to any indexed format.} procedure IndexToIndex(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo; SrcPal, DstPal: PPalette32); { Converts any indexed format to any ARGB format.} procedure IndexToChannel(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo; SrcPal: PPalette32); { Converts any indexed format to any grayscale format.} procedure IndexToGray(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo; SrcPal: PPalette32); { Converts any indexed format to any floating point format.} procedure IndexToFloat(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo; SrcPal: PPalette32); { Color constructor functions } { Constructs TColor24Rec color.} function Color24(R, G, B: Byte): TColor24Rec; {$IFDEF USE_INLINE}inline;{$ENDIF} { Constructs TColor32Rec color.} function Color32(A, R, G, B: Byte): TColor32Rec; {$IFDEF USE_INLINE}inline;{$ENDIF} { Constructs TColor48Rec color.} function Color48(R, G, B: Word): TColor48Rec; {$IFDEF USE_INLINE}inline;{$ENDIF} { Constructs TColor64Rec color.} function Color64(A, R, G, B: Word): TColor64Rec; {$IFDEF USE_INLINE}inline;{$ENDIF} { Constructs TColorFPRec color.} function ColorFP(A, R, G, B: Single): TColorFPRec; {$IFDEF USE_INLINE}inline;{$ENDIF} { Constructs TColorHFRec color.} function ColorHF(A, R, G, B: THalfFloat): TColorHFRec; {$IFDEF USE_INLINE}inline;{$ENDIF} { Special formats conversion functions } { Converts image to/from/between special image formats (dxtc, ...).} procedure ConvertSpecial(var Image: TImageData; SrcInfo, DstInfo: PImageFormatInfo); { Inits all image format information. Called internally on startup.} procedure InitImageFormats(var Infos: TImageFormatInfoArray); const // Grayscale conversion channel weights GrayConv: TColorFPRec = (B: 0.114; G: 0.587; R: 0.299; A: 0.0); // Contants for converting integer colors to floating point OneDiv8Bit: Single = 1.0 / 255.0; OneDiv16Bit: Single = 1.0 / 65535.0; implementation { TImageFormatInfo member functions } { Returns size in bytes of image in given standard format where Size = Width * Height * Bpp.} function GetStdPixelsSize(Format: TImageFormat; Width, Height: LongInt): LongInt; forward; { Checks if Width and Height are valid for given standard format.} procedure CheckStdDimensions(Format: TImageFormat; var Width, Height: LongInt); forward; { Returns size in bytes of image in given DXT format.} function GetDXTPixelsSize(Format: TImageFormat; Width, Height: LongInt): LongInt; forward; { Checks if Width and Height are valid for given DXT format. If they are not valid, they are changed to pass the check.} procedure CheckDXTDimensions(Format: TImageFormat; var Width, Height: LongInt); forward; { Returns size in bytes of image in BTC format.} function GetBTCPixelsSize(Format: TImageFormat; Width, Height: LongInt): LongInt; forward; { Optimized pixel readers/writers for 32bit and FP colors to be stored in TImageFormatInfo } function GetPixel32ifA8R8G8B8(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32): TColor32Rec; forward; procedure SetPixel32ifA8R8G8B8(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32; const Color: TColor32Rec); forward; function GetPixelFPifA8R8G8B8(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32): TColorFPRec; forward; procedure SetPixelFPifA8R8G8B8(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32; const Color: TColorFPRec); forward; function GetPixel32Channel8Bit(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32): TColor32Rec; forward; procedure SetPixel32Channel8Bit(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32; const Color: TColor32Rec); forward; function GetPixelFPChannel8Bit(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32): TColorFPRec; forward; procedure SetPixelFPChannel8Bit(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32; const Color: TColorFPRec); forward; function GetPixelFPFloat32(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32): TColorFPRec; forward; procedure SetPixelFPFloat32(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32; const Color: TColorFPRec); forward; var PFR3G3B2: TPixelFormatInfo; PFX5R1G1B1: TPixelFormatInfo; PFR5G6B5: TPixelFormatInfo; PFA1R5G5B5: TPixelFormatInfo; PFA4R4G4B4: TPixelFormatInfo; PFX1R5G5B5: TPixelFormatInfo; PFX4R4G4B4: TPixelFormatInfo; FInfos: PImageFormatInfoArray; var // Free Pascal generates hundreds of warnings here {$WARNINGS OFF} // indexed formats Index8Info: TImageFormatInfo = ( Format: ifIndex8; Name: 'Index8'; BytesPerPixel: 1; ChannelCount: 1; PaletteEntries: 256; HasAlphaChannel: True; IsIndexed: True; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPGeneric; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPGeneric); // grayscale formats Gray8Info: TImageFormatInfo = ( Format: ifGray8; Name: 'Gray8'; BytesPerPixel: 1; ChannelCount: 1; HasGrayChannel: True; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Channel8Bit; GetPixelFP: GetPixelFPChannel8Bit; SetPixel32: SetPixel32Channel8Bit; SetPixelFP: SetPixelFPChannel8Bit); A8Gray8Info: TImageFormatInfo = ( Format: ifA8Gray8; Name: 'A8Gray8'; BytesPerPixel: 2; ChannelCount: 2; HasGrayChannel: True; HasAlphaChannel: True; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Channel8Bit; GetPixelFP: GetPixelFPChannel8Bit; SetPixel32: SetPixel32Channel8Bit; SetPixelFP: SetPixelFPChannel8Bit); Gray16Info: TImageFormatInfo = ( Format: ifGray16; Name: 'Gray16'; BytesPerPixel: 2; ChannelCount: 1; HasGrayChannel: True; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPGeneric; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPGeneric); Gray32Info: TImageFormatInfo = ( Format: ifGray32; Name: 'Gray32'; BytesPerPixel: 4; ChannelCount: 1; HasGrayChannel: True; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPGeneric; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPGeneric); Gray64Info: TImageFormatInfo = ( Format: ifGray64; Name: 'Gray64'; BytesPerPixel: 8; ChannelCount: 1; HasGrayChannel: True; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPGeneric; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPGeneric); A16Gray16Info: TImageFormatInfo = ( Format: ifA16Gray16; Name: 'A16Gray16'; BytesPerPixel: 4; ChannelCount: 2; HasGrayChannel: True; HasAlphaChannel: True; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPGeneric; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPGeneric); // ARGB formats X5R1G1B1Info: TImageFormatInfo = ( Format: ifX5R1G1B1; Name: 'X5R1G1B1'; BytesPerPixel: 1; ChannelCount: 3; UsePixelFormat: True; PixelFormat: @PFX5R1G1B1; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPGeneric; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPGeneric); R3G3B2Info: TImageFormatInfo = ( Format: ifR3G3B2; Name: 'R3G3B2'; BytesPerPixel: 1; ChannelCount: 3; UsePixelFormat: True; PixelFormat: @PFR3G3B2; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPGeneric; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPGeneric); R5G6B5Info: TImageFormatInfo = ( Format: ifR5G6B5; Name: 'R5G6B5'; BytesPerPixel: 2; ChannelCount: 3; UsePixelFormat: True; PixelFormat: @PFR5G6B5; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPGeneric; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPGeneric); A1R5G5B5Info: TImageFormatInfo = ( Format: ifA1R5G5B5; Name: 'A1R5G5B5'; BytesPerPixel: 2; ChannelCount: 4; HasAlphaChannel: True; UsePixelFormat: True; PixelFormat: @PFA1R5G5B5; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPGeneric; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPGeneric); A4R4G4B4Info: TImageFormatInfo = ( Format: ifA4R4G4B4; Name: 'A4R4G4B4'; BytesPerPixel: 2; ChannelCount: 4; HasAlphaChannel: True; UsePixelFormat: True; PixelFormat: @PFA4R4G4B4; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPGeneric; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPGeneric); X1R5G5B5Info: TImageFormatInfo = ( Format: ifX1R5G5B5; Name: 'X1R5G5B5'; BytesPerPixel: 2; ChannelCount: 3; UsePixelFormat: True; PixelFormat: @PFX1R5G5B5; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPGeneric; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPGeneric); X4R4G4B4Info: TImageFormatInfo = ( Format: ifX4R4G4B4; Name: 'X4R4G4B4'; BytesPerPixel: 2; ChannelCount: 3; UsePixelFormat: True; PixelFormat: @PFX4R4G4B4; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPGeneric; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPGeneric); R8G8B8Info: TImageFormatInfo = ( Format: ifR8G8B8; Name: 'R8G8B8'; BytesPerPixel: 3; ChannelCount: 3; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Channel8Bit; GetPixelFP: GetPixelFPChannel8Bit; SetPixel32: SetPixel32Channel8Bit; SetPixelFP: SetPixelFPChannel8Bit); A8R8G8B8Info: TImageFormatInfo = ( Format: ifA8R8G8B8; Name: 'A8R8G8B8'; BytesPerPixel: 4; ChannelCount: 4; HasAlphaChannel: True; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32ifA8R8G8B8; GetPixelFP: GetPixelFPifA8R8G8B8; SetPixel32: SetPixel32ifA8R8G8B8; SetPixelFP: SetPixelFPifA8R8G8B8); X8R8G8B8Info: TImageFormatInfo = ( Format: ifX8R8G8B8; Name: 'X8R8G8B8'; BytesPerPixel: 4; ChannelCount: 3; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Channel8Bit; GetPixelFP: GetPixelFPChannel8Bit; SetPixel32: SetPixel32Channel8Bit; SetPixelFP: SetPixelFPChannel8Bit); R16G16B16Info: TImageFormatInfo = ( Format: ifR16G16B16; Name: 'R16G16B16'; BytesPerPixel: 6; ChannelCount: 3; RBSwapFormat: ifB16G16R16; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPGeneric; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPGeneric); A16R16G16B16Info: TImageFormatInfo = ( Format: ifA16R16G16B16; Name: 'A16R16G16B16'; BytesPerPixel: 8; ChannelCount: 4; HasAlphaChannel: True; RBSwapFormat: ifA16B16G16R16; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPGeneric; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPGeneric); B16G16R16Info: TImageFormatInfo = ( Format: ifB16G16R16; Name: 'B16G16R16'; BytesPerPixel: 6; ChannelCount: 3; IsRBSwapped: True; RBSwapFormat: ifR16G16B16; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPGeneric; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPGeneric); A16B16G16R16Info: TImageFormatInfo = ( Format: ifA16B16G16R16; Name: 'A16B16G16R16'; BytesPerPixel: 8; ChannelCount: 4; HasAlphaChannel: True; IsRBSwapped: True; RBSwapFormat: ifA16R16G16B16; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPGeneric; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPGeneric); // floating point formats R32FInfo: TImageFormatInfo = ( Format: ifR32F; Name: 'R32F'; BytesPerPixel: 4; ChannelCount: 1; IsFloatingPoint: True; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPFloat32; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPFloat32); A32R32G32B32FInfo: TImageFormatInfo = ( Format: ifA32R32G32B32F; Name: 'A32R32G32B32F'; BytesPerPixel: 16; ChannelCount: 4; HasAlphaChannel: True; IsFloatingPoint: True; RBSwapFormat: ifA32B32G32R32F; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPFloat32; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPFloat32); A32B32G32R32FInfo: TImageFormatInfo = ( Format: ifA32B32G32R32F; Name: 'A32B32G32R32F'; BytesPerPixel: 16; ChannelCount: 4; HasAlphaChannel: True; IsFloatingPoint: True; IsRBSwapped: True; RBSwapFormat: ifA32R32G32B32F; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPFloat32; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPFloat32); R16FInfo: TImageFormatInfo = ( Format: ifR16F; Name: 'R16F'; BytesPerPixel: 2; ChannelCount: 1; IsFloatingPoint: True; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPGeneric; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPGeneric); A16R16G16B16FInfo: TImageFormatInfo = ( Format: ifA16R16G16B16F; Name: 'A16R16G16B16F'; BytesPerPixel: 8; ChannelCount: 4; HasAlphaChannel: True; IsFloatingPoint: True; RBSwapFormat: ifA16B16G16R16F; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPGeneric; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPGeneric); A16B16G16R16FInfo: TImageFormatInfo = ( Format: ifA16B16G16R16F; Name: 'A16B16G16R16F'; BytesPerPixel: 8; ChannelCount: 4; HasAlphaChannel: True; IsFloatingPoint: True; IsRBSwapped: True; RBSwapFormat: ifA16R16G16B16F; GetPixelsSize: GetStdPixelsSize; CheckDimensions: CheckStdDimensions; GetPixel32: GetPixel32Generic; GetPixelFP: GetPixelFPGeneric; SetPixel32: SetPixel32Generic; SetPixelFP: SetPixelFPGeneric); // special formats DXT1Info: TImageFormatInfo = ( Format: ifDXT1; Name: 'DXT1'; ChannelCount: 4; HasAlphaChannel: True; IsSpecial: True; GetPixelsSize: GetDXTPixelsSize; CheckDimensions: CheckDXTDimensions; SpecialNearestFormat: ifA8R8G8B8); DXT3Info: TImageFormatInfo = ( Format: ifDXT3; Name: 'DXT3'; ChannelCount: 4; HasAlphaChannel: True; IsSpecial: True; GetPixelsSize: GetDXTPixelsSize; CheckDimensions: CheckDXTDimensions; SpecialNearestFormat: ifA8R8G8B8); DXT5Info: TImageFormatInfo = ( Format: ifDXT5; Name: 'DXT5'; ChannelCount: 4; HasAlphaChannel: True; IsSpecial: True; GetPixelsSize: GetDXTPixelsSize; CheckDimensions: CheckDXTDimensions; SpecialNearestFormat: ifA8R8G8B8); BTCInfo: TImageFormatInfo = ( Format: ifBTC; Name: 'BTC'; ChannelCount: 1; HasAlphaChannel: False; IsSpecial: True; GetPixelsSize: GetBTCPixelsSize; CheckDimensions: CheckDXTDimensions; SpecialNearestFormat: ifGray8); ATI1NInfo: TImageFormatInfo = ( Format: ifATI1N; Name: 'ATI1N'; ChannelCount: 1; HasAlphaChannel: False; IsSpecial: True; GetPixelsSize: GetDXTPixelsSize; CheckDimensions: CheckDXTDimensions; SpecialNearestFormat: ifGray8); ATI2NInfo: TImageFormatInfo = ( Format: ifATI2N; Name: 'ATI2N'; ChannelCount: 2; HasAlphaChannel: False; IsSpecial: True; GetPixelsSize: GetDXTPixelsSize; CheckDimensions: CheckDXTDimensions; SpecialNearestFormat: ifA8R8G8B8); {$WARNINGS ON} function PixelFormat(ABitCount, RBitCount, GBitCount, BBitCount: Byte): TPixelFormatInfo; forward; procedure InitImageFormats(var Infos: TImageFormatInfoArray); begin FInfos := @Infos; Infos[ifDefault] := @A8R8G8B8Info; // indexed formats Infos[ifIndex8] := @Index8Info; // grayscale formats Infos[ifGray8] := @Gray8Info; Infos[ifA8Gray8] := @A8Gray8Info; Infos[ifGray16] := @Gray16Info; Infos[ifGray32] := @Gray32Info; Infos[ifGray64] := @Gray64Info; Infos[ifA16Gray16] := @A16Gray16Info; // ARGB formats Infos[ifX5R1G1B1] := @X5R1G1B1Info; Infos[ifR3G3B2] := @R3G3B2Info; Infos[ifR5G6B5] := @R5G6B5Info; Infos[ifA1R5G5B5] := @A1R5G5B5Info; Infos[ifA4R4G4B4] := @A4R4G4B4Info; Infos[ifX1R5G5B5] := @X1R5G5B5Info; Infos[ifX4R4G4B4] := @X4R4G4B4Info; Infos[ifR8G8B8] := @R8G8B8Info; Infos[ifA8R8G8B8] := @A8R8G8B8Info; Infos[ifX8R8G8B8] := @X8R8G8B8Info; Infos[ifR16G16B16] := @R16G16B16Info; Infos[ifA16R16G16B16] := @A16R16G16B16Info; Infos[ifB16G16R16] := @B16G16R16Info; Infos[ifA16B16G16R16] := @A16B16G16R16Info; // floating point formats Infos[ifR32F] := @R32FInfo; Infos[ifA32R32G32B32F] := @A32R32G32B32FInfo; Infos[ifA32B32G32R32F] := @A32B32G32R32FInfo; Infos[ifR16F] := @R16FInfo; Infos[ifA16R16G16B16F] := @A16R16G16B16FInfo; Infos[ifA16B16G16R16F] := @A16B16G16R16FInfo; // special formats Infos[ifDXT1] := @DXT1Info; Infos[ifDXT3] := @DXT3Info; Infos[ifDXT5] := @DXT5Info; Infos[ifBTC] := @BTCInfo; Infos[ifATI1N] := @ATI1NInfo; Infos[ifATI2N] := @ATI2NInfo; PFR3G3B2 := PixelFormat(0, 3, 3, 2); PFX5R1G1B1 := PixelFormat(0, 1, 1, 1); PFR5G6B5 := PixelFormat(0, 5, 6, 5); PFA1R5G5B5 := PixelFormat(1, 5, 5, 5); PFA4R4G4B4 := PixelFormat(4, 4, 4, 4); PFX1R5G5B5 := PixelFormat(0, 5, 5, 5); PFX4R4G4B4 := PixelFormat(0, 4, 4, 4); end; { Internal unit helper functions } function PixelFormat(ABitCount, RBitCount, GBitCount, BBitCount: Byte): TPixelFormatInfo; begin Result.ABitMask := ((1 shl ABitCount) - 1) shl (RBitCount + GBitCount + BBitCount); Result.RBitMask := ((1 shl RBitCount) - 1) shl (GBitCount + BBitCount); Result.GBitMask := ((1 shl GBitCount) - 1) shl (BBitCount); Result.BBitMask := (1 shl BBitCount) - 1; Result.ABitCount := ABitCount; Result.RBitCount := RBitCount; Result.GBitCount := GBitCount; Result.BBitCount := BBitCount; Result.AShift := RBitCount + GBitCount + BBitCount; Result.RShift := GBitCount + BBitCount; Result.GShift := BBitCount; Result.BShift := 0; Result.ARecDiv := Max(1, Pow2Int(Result.ABitCount) - 1); Result.RRecDiv := Max(1, Pow2Int(Result.RBitCount) - 1); Result.GRecDiv := Max(1, Pow2Int(Result.GBitCount) - 1); Result.BRecDiv := Max(1, Pow2Int(Result.BBitCount) - 1); end; function PixelFormatMask(ABitMask, RBitMask, GBitMask, BBitMask: LongWord): TPixelFormatInfo; function GetBitCount(B: LongWord): LongWord; var I: LongWord; begin I := 0; while (I < 31) and (((1 shl I) and B) = 0) do Inc(I); Result := 0; while ((1 shl I) and B) <> 0 do begin Inc(I); Inc(Result); end; end; begin Result := PixelFormat(GetBitCount(ABitMask), GetBitCount(RBitMask), GetBitCount(GBitMask), GetBitCount(BBitMask)); end; function PFSetARGB(const PF: TPixelFormatInfo; A, R, G, B: Byte): TColor32; {$IFDEF USE_INLINE}inline;{$ENDIF} begin with PF do Result := (A shl ABitCount shr 8 shl AShift) or (R shl RBitCount shr 8 shl RShift) or (G shl GBitCount shr 8 shl GShift) or (B shl BBitCount shr 8 shl BShift); end; procedure PFGetARGB(const PF: TPixelFormatInfo; Color: LongWord; var A, R, G, B: Byte); {$IFDEF USE_INLINE}inline;{$ENDIF} begin with PF do begin A := (Color and ABitMask shr AShift) * 255 div ARecDiv; R := (Color and RBitMask shr RShift) * 255 div RRecDiv; G := (Color and GBitMask shr GShift) * 255 div GRecDiv; B := (Color and BBitMask shl BShift) * 255 div BRecDiv; end; end; function PFSetColor(const PF: TPixelFormatInfo; ARGB: TColor32): LongWord; {$IFDEF USE_INLINE}inline;{$ENDIF} begin with PF do Result := (Byte(ARGB shr 24) shl ABitCount shr 8 shl AShift) or (Byte(ARGB shr 16) shl RBitCount shr 8 shl RShift) or (Byte(ARGB shr 8) shl GBitCount shr 8 shl GShift) or (Byte(ARGB) shl BBitCount shr 8 shl BShift); end; function PFGetColor(const PF: TPixelFormatInfo; Color: LongWord): TColor32; {$IFDEF USE_INLINE}inline;{$ENDIF} begin with PF, TColor32Rec(Result) do begin A := (Color and ABitMask shr AShift) * 255 div ARecDiv; R := (Color and RBitMask shr RShift) * 255 div RRecDiv; G := (Color and GBitMask shr GShift) * 255 div GRecDiv; B := (Color and BBitMask shl BShift) * 255 div BRecDiv; end; end; { Color constructor functions } function Color24(R, G, B: Byte): TColor24Rec; begin Result.R := R; Result.G := G; Result.B := B; end; function Color32(A, R, G, B: Byte): TColor32Rec; begin Result.A := A; Result.R := R; Result.G := G; Result.B := B; end; function Color48(R, G, B: Word): TColor48Rec; begin Result.R := R; Result.G := G; Result.B := B; end; function Color64(A, R, G, B: Word): TColor64Rec; begin Result.A := A; Result.R := R; Result.G := G; Result.B := B; end; function ColorFP(A, R, G, B: Single): TColorFPRec; begin Result.A := A; Result.R := R; Result.G := G; Result.B := B; end; function ColorHF(A, R, G, B: THalfFloat): TColorHFRec; begin Result.A := A; Result.R := R; Result.G := G; Result.B := B; end; { Additional image manipulation functions (usually used internally by Imaging unit) } const MaxPossibleColors = 4096; HashSize = 32768; AlphaWeight = 1024; RedWeight = 612; GreenWeight = 1202; BlueWeight = 234; type PColorBin = ^TColorBin; TColorBin = record Color: TColor32Rec; Number: LongInt; Next: PColorBin; end; THashTable = array[0..HashSize - 1] of PColorBin; TColorBox = record AMin, AMax, RMin, RMax, GMin, GMax, BMin, BMax: LongInt; Total: LongInt; Represented: TColor32Rec; List: PColorBin; end; var Table: THashTable; Box: array[0..MaxPossibleColors - 1] of TColorBox; Boxes: LongInt; BoxesCreated: Boolean = False; procedure ReduceColorsMedianCut(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo; MaxColors: LongInt; ChannelMask: Byte; DstPal: PPalette32; Actions: TReduceColorsActions); procedure CreateHistogram (Src: PByte; SrcInfo: PImageFormatInfo; ChannelMask: Byte); var A, R, G, B: Byte; I, Addr: LongInt; PC: PColorBin; Col: TColor32Rec; begin for I := 0 to NumPixels - 1 do begin Col := GetPixel32Generic(Src, SrcInfo, nil); A := Col.A and ChannelMask; R := Col.R and ChannelMask; G := Col.G and ChannelMask; B := Col.B and ChannelMask; Addr := (A + 11 * B + 59 * R + 119 * G) mod HashSize; PC := Table[Addr]; while (PC <> nil) and ((PC.Color.R <> R) or (PC.Color.G <> G) or (PC.Color.B <> B) or (PC.Color.A <> A)) do PC := PC.Next; if PC = nil then begin New(PC); PC.Color.R := R; PC.Color.G := G; PC.Color.B := B; PC.Color.A := A; PC.Number := 1; PC.Next := Table[Addr]; Table[Addr] := PC; end else Inc(PC^.Number); Inc(Src, SrcInfo.BytesPerPixel); end; end; procedure InitBox (var Box : TColorBox); begin Box.AMin := 256; Box.RMin := 256; Box.GMin := 256; Box.BMin := 256; Box.AMax := -1; Box.RMax := -1; Box.GMax := -1; Box.BMax := -1; Box.Total := 0; Box.List := nil; end; procedure ChangeBox (var Box: TColorBox; const C: TColorBin); begin with C.Color do begin if A < Box.AMin then Box.AMin := A; if A > Box.AMax then Box.AMax := A; if B < Box.BMin then Box.BMin := B; if B > Box.BMax then Box.BMax := B; if G < Box.GMin then Box.GMin := G; if G > Box.GMax then Box.GMax := G; if R < Box.RMin then Box.RMin := R; if R > Box.RMax then Box.RMax := R; end; Inc(Box.Total, C.Number); end; procedure MakeColormap; var I, J: LongInt; CP, Pom: PColorBin; Cut, LargestIdx, Largest, Size, S: LongInt; CutA, CutR, CutG, CutB: Boolean; SumA, SumR, SumG, SumB: LongInt; Temp: TColorBox; begin I := 0; Boxes := 1; LargestIdx := 0; while (I < HashSize) and (Table[I] = nil) do Inc(i); if I < HashSize then begin // put all colors into Box[0] InitBox(Box[0]); repeat CP := Table[I]; while CP.Next <> nil do begin ChangeBox(Box[0], CP^); CP := CP.Next; end; ChangeBox(Box[0], CP^); CP.Next := Box[0].List; Box[0].List := Table[I]; Table[I] := nil; repeat Inc(I) until (I = HashSize) or (Table[I] <> nil); until I = HashSize; // now all colors are in Box[0] repeat // cut one color box Largest := 0; for I := 0 to Boxes - 1 do with Box[I] do begin Size := (AMax - AMin) * AlphaWeight; S := (RMax - RMin) * RedWeight; if S > Size then Size := S; S := (GMax - GMin) * GreenWeight; if S > Size then Size := S; S := (BMax - BMin) * BlueWeight; if S > Size then Size := S; if Size > Largest then begin Largest := Size; LargestIdx := I; end; end; if Largest > 0 then begin // cutting Box[LargestIdx] into Box[LargestIdx] and Box[Boxes] CutR := False; CutG := False; CutB := False; CutA := False; with Box[LargestIdx] do begin if (AMax - AMin) * AlphaWeight = Largest then begin Cut := (AMax + AMin) shr 1; CutA := True; end else if (RMax - RMin) * RedWeight = Largest then begin Cut := (RMax + RMin) shr 1; CutR := True; end else if (GMax - GMin) * GreenWeight = Largest then begin Cut := (GMax + GMin) shr 1; CutG := True; end else begin Cut := (BMax + BMin) shr 1; CutB := True; end; CP := List; end; InitBox(Box[LargestIdx]); InitBox(Box[Boxes]); repeat // distribute one color Pom := CP.Next; with CP.Color do begin if (CutA and (A <= Cut)) or (CutR and (R <= Cut)) or (CutG and (G <= Cut)) or (CutB and (B <= Cut)) then I := LargestIdx else I := Boxes; end; CP.Next := Box[i].List; Box[i].List := CP; ChangeBox(Box[i], CP^); CP := Pom; until CP = nil; Inc(Boxes); end; until (Boxes = MaxColors) or (Largest = 0); // compute box representation for I := 0 to Boxes - 1 do begin SumR := 0; SumG := 0; SumB := 0; SumA := 0; repeat CP := Box[I].List; Inc(SumR, CP.Color.R * CP.Number); Inc(SumG, CP.Color.G * CP.Number); Inc(SumB, CP.Color.B * CP.Number); Inc(SumA, CP.Color.A * CP.Number); Box[I].List := CP.Next; Dispose(CP); until Box[I].List = nil; with Box[I] do begin Represented.A := SumA div Total; Represented.R := SumR div Total; Represented.G := SumG div Total; Represented.B := SumB div Total; AMin := AMin and ChannelMask; RMin := RMin and ChannelMask; GMin := GMin and ChannelMask; BMin := BMin and ChannelMask; AMax := (AMax and ChannelMask) + (not ChannelMask); RMax := (RMax and ChannelMask) + (not ChannelMask); GMax := (GMax and ChannelMask) + (not ChannelMask); BMax := (BMax and ChannelMask) + (not ChannelMask); end; end; // sort color boxes for I := 0 to Boxes - 2 do begin Largest := 0; for J := I to Boxes - 1 do if Box[J].Total > Largest then begin Largest := Box[J].Total; LargestIdx := J; end; if LargestIdx <> I then begin Temp := Box[I]; Box[I] := Box[LargestIdx]; Box[LargestIdx] := Temp; end; end; end; end; procedure FillOutputPalette; var I: LongInt; begin FillChar(DstPal^, SizeOf(TColor32Rec) * MaxColors, $FF); for I := 0 to MaxColors - 1 do begin if I < Boxes then with Box[I].Represented do begin DstPal[I].A := A; DstPal[I].R := R; DstPal[I].G := G; DstPal[I].B := B; end else DstPal[I].Color := $FF000000; end; end; function MapColor(const Col: TColor32Rec) : LongInt; var I: LongInt; begin I := 0; with Col do while (I < Boxes) and ((Box[I].AMin > A) or (Box[I].AMax < A) or (Box[I].RMin > R) or (Box[I].RMax < R) or (Box[I].GMin > G) or (Box[I].GMax < G) or (Box[I].BMin > B) or (Box[I].BMax < B)) do Inc(I); if I = Boxes then MapColor := 0 else MapColor := I; end; procedure MapImage(Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo); var I: LongInt; Col: TColor32Rec; begin for I := 0 to NumPixels - 1 do begin Col := GetPixel32Generic(Src, SrcInfo, nil); IndexSetDstPixel(Dst, DstInfo, MapColor(Col)); Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end; end; begin MaxColors := ClampInt(MaxColors, 2, MaxPossibleColors); if (raUpdateHistogram in Actions) or (raMapImage in Actions) then begin Assert(not SrcInfo.IsSpecial); Assert(not SrcInfo.IsIndexed); end; if raCreateHistogram in Actions then FillChar(Table, SizeOf(Table), 0); if raUpdateHistogram in Actions then CreateHistogram(Src, SrcInfo, ChannelMask); if raMakeColorMap in Actions then begin MakeColorMap; FillOutputPalette; end; if raMapImage in Actions then MapImage(Src, Dst, SrcInfo, DstInfo); end; procedure StretchNearest(const SrcImage: TImageData; SrcX, SrcY, SrcWidth, SrcHeight: LongInt; var DstImage: TImageData; DstX, DstY, DstWidth, DstHeight: LongInt); var Info: TImageFormatInfo; ScaleX, ScaleY, X, Y, Xp, Yp: LongInt; DstPixel, SrcLine: PByte; begin GetImageFormatInfo(SrcImage.Format, Info); Assert(SrcImage.Format = DstImage.Format); Assert(not Info.IsSpecial); // Use integers instead of floats for source image pixel coords // Xp and Yp coords must be shifted right to get read source image coords ScaleX := (SrcWidth shl 16) div DstWidth; ScaleY := (SrcHeight shl 16) div DstHeight; Yp := 0; for Y := 0 to DstHeight - 1 do begin Xp := 0; SrcLine := @PByteArray(SrcImage.Bits)[((SrcY + Yp shr 16) * SrcImage.Width + SrcX) * Info.BytesPerPixel]; DstPixel := @PByteArray(DstImage.Bits)[((DstY + Y) * DstImage.Width + DstX) * Info.BytesPerPixel]; for X := 0 to DstWidth - 1 do begin case Info.BytesPerPixel of 1: PByte(DstPixel)^ := PByteArray(SrcLine)[Xp shr 16]; 2: PWord(DstPixel)^ := PWordArray(SrcLine)[Xp shr 16]; 3: PColor24Rec(DstPixel)^ := PPalette24(SrcLine)[Xp shr 16]; 4: PColor32(DstPixel)^ := PLongWordArray(SrcLine)[Xp shr 16]; 6: PColor48Rec(DstPixel)^ := PColor48RecArray(SrcLine)[Xp shr 16]; 8: PColor64(DstPixel)^ := PInt64Array(SrcLine)[Xp shr 16]; 16: PColorFPRec(DstPixel)^ := PColorFPRecArray(SrcLine)[Xp shr 16]; end; Inc(DstPixel, Info.BytesPerPixel); Inc(Xp, ScaleX); end; Inc(Yp, ScaleY); end; end; { Filter function for nearest filtering. Also known as box filter.} function FilterNearest(Value: Single): Single; begin if (Value > -0.5) and (Value <= 0.5) then Result := 1 else Result := 0; end; { Filter function for linear filtering. Also known as triangle or Bartlett filter.} function FilterLinear(Value: Single): Single; begin if Value < 0.0 then Value := -Value; if Value < 1.0 then Result := 1.0 - Value else Result := 0.0; end; { Cosine filter.} function FilterCosine(Value: Single): Single; begin Result := 0; if Abs(Value) < 1 then Result := (Cos(Value * Pi) + 1) / 2; end; { f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1 } function FilterHermite(Value: Single): Single; begin if Value < 0.0 then Value := -Value; if Value < 1 then Result := (2 * Value - 3) * Sqr(Value) + 1 else Result := 0; end; { Quadratic filter. Also known as Bell.} function FilterQuadratic(Value: Single): Single; begin if Value < 0.0 then Value := -Value; if Value < 0.5 then Result := 0.75 - Sqr(Value) else if Value < 1.5 then begin Value := Value - 1.5; Result := 0.5 * Sqr(Value); end else Result := 0.0; end; { Gaussian filter.} function FilterGaussian(Value: Single): Single; begin Result := Exp(-2.0 * Sqr(Value)) * Sqrt(2.0 / Pi); end; { 4th order (cubic) b-spline filter.} function FilterSpline(Value: Single): Single; var Temp: Single; begin if Value < 0.0 then Value := -Value; if Value < 1.0 then begin Temp := Sqr(Value); Result := 0.5 * Temp * Value - Temp + 2.0 / 3.0; end else if Value < 2.0 then begin Value := 2.0 - Value; Result := Sqr(Value) * Value / 6.0; end else Result := 0.0; end; { Lanczos-windowed sinc filter.} function FilterLanczos(Value: Single): Single; function SinC(Value: Single): Single; begin if Value <> 0.0 then begin Value := Value * Pi; Result := Sin(Value) / Value; end else Result := 1.0; end; begin if Value < 0.0 then Value := -Value; if Value < 3.0 then Result := SinC(Value) * SinC(Value / 3.0) else Result := 0.0; end; { Micthell cubic filter.} function FilterMitchell(Value: Single): Single; const B = 1.0 / 3.0; C = 1.0 / 3.0; var Temp: Single; begin if Value < 0.0 then Value := -Value; Temp := Sqr(Value); if Value < 1.0 then begin Value := (((12.0 - 9.0 * B - 6.0 * C) * (Value * Temp)) + ((-18.0 + 12.0 * B + 6.0 * C) * Temp) + (6.0 - 2.0 * B)); Result := Value / 6.0; end else if Value < 2.0 then begin Value := (((-B - 6.0 * C) * (Value * Temp)) + ((6.0 * B + 30.0 * C) * Temp) + ((-12.0 * B - 48.0 * C) * Value) + (8.0 * B + 24.0 * C)); Result := Value / 6.0; end else Result := 0.0; end; { CatmullRom spline filter.} function FilterCatmullRom(Value: Single): Single; begin if Value < 0.0 then Value := -Value; if Value < 1.0 then Result := 0.5 * (2.0 + Sqr(Value) * (-5.0 + 3.0 * Value)) else if Value < 2.0 then Result := 0.5 * (4.0 + Value * (-8.0 + Value * (5.0 - Value))) else Result := 0.0; end; procedure StretchResample(const SrcImage: TImageData; SrcX, SrcY, SrcWidth, SrcHeight: LongInt; var DstImage: TImageData; DstX, DstY, DstWidth, DstHeight: LongInt; Filter: TSamplingFilter; WrapEdges: Boolean); begin // Calls the other function with filter function and radius defined by Filter StretchResample(SrcImage, SrcX, SrcY, SrcWidth, SrcHeight, DstImage, DstX, DstY, DstWidth, DstHeight, SamplingFilterFunctions[Filter], SamplingFilterRadii[Filter], WrapEdges); end; var FullEdge: Boolean = True; { The following resampling code is modified and extended code from Graphics32 library by Alex A. Denisov.} function BuildMappingTable(DstLow, DstHigh, SrcLow, SrcHigh, SrcImageWidth: LongInt; Filter: TFilterFunction; Radius: Single; WrapEdges: Boolean): TMappingTable; var I, J, K, N: LongInt; Left, Right, SrcWidth, DstWidth: LongInt; Weight, Scale, Center, Count: Single; begin Result := nil; K := 0; SrcWidth := SrcHigh - SrcLow; DstWidth := DstHigh - DstLow; // Check some special cases if SrcWidth = 1 then begin SetLength(Result, DstWidth); for I := 0 to DstWidth - 1 do begin SetLength(Result[I], 1); Result[I][0].Pos := 0; Result[I][0].Weight := 1.0; end; Exit; end else if (SrcWidth = 0) or (DstWidth = 0) then Exit; if FullEdge then Scale := DstWidth / SrcWidth else Scale := (DstWidth - 1) / (SrcWidth - 1); SetLength(Result, DstWidth); // Pre-calculate filter contributions for a row or column if Scale = 0.0 then begin Assert(Length(Result) = 1); SetLength(Result[0], 1); Result[0][0].Pos := (SrcLow + SrcHigh) div 2; Result[0][0].Weight := 1.0; end else if Scale < 1.0 then begin // Sub-sampling - scales from bigger to smaller Radius := Radius / Scale; for I := 0 to DstWidth - 1 do begin if FullEdge then Center := SrcLow - 0.5 + (I + 0.5) / Scale else Center := SrcLow + I / Scale; Left := Floor(Center - Radius); Right := Ceil(Center + Radius); Count := -1.0; for J := Left to Right do begin Weight := Filter((Center - J) * Scale) * Scale; if Weight <> 0.0 then begin Count := Count + Weight; K := Length(Result[I]); SetLength(Result[I], K + 1); Result[I][K].Pos := ClampInt(J, SrcLow, SrcHigh - 1); Result[I][K].Weight := Weight; end; end; if Length(Result[I]) = 0 then begin SetLength(Result[I], 1); Result[I][0].Pos := Floor(Center); Result[I][0].Weight := 1.0; end else if Count <> 0.0 then Result[I][K div 2].Weight := Result[I][K div 2].Weight - Count; end; end else // if Scale > 1.0 then begin // Super-sampling - scales from smaller to bigger Scale := 1.0 / Scale; for I := 0 to DstWidth - 1 do begin if FullEdge then Center := SrcLow - 0.5 + (I + 0.5) * Scale else Center := SrcLow + I * Scale; Left := Floor(Center - Radius); Right := Ceil(Center + Radius); Count := -1.0; for J := Left to Right do begin Weight := Filter(Center - J); if Weight <> 0.0 then begin Count := Count + Weight; K := Length(Result[I]); SetLength(Result[I], K + 1); if WrapEdges then begin if J < 0 then N := SrcImageWidth + J else if J >= SrcImageWidth then N := J - SrcImageWidth else N := ClampInt(J, SrcLow, SrcHigh - 1); end else N := ClampInt(J, SrcLow, SrcHigh - 1); Result[I][K].Pos := N; Result[I][K].Weight := Weight; end; end; if Count <> 0.0 then Result[I][K div 2].Weight := Result[I][K div 2].Weight - Count; end; end; end; procedure FindExtremes(const Map: TMappingTable; var MinPos, MaxPos: LongInt); var I, J: LongInt; begin if Length(Map) > 0 then begin MinPos := Map[0][0].Pos; MaxPos := MinPos; for I := 0 to Length(Map) - 1 do for J := 0 to Length(Map[I]) - 1 do begin if MinPos > Map[I][J].Pos then MinPos := Map[I][J].Pos; if MaxPos < Map[I][J].Pos then MaxPos := Map[I][J].Pos; end; end; end; procedure StretchResample(const SrcImage: TImageData; SrcX, SrcY, SrcWidth, SrcHeight: LongInt; var DstImage: TImageData; DstX, DstY, DstWidth, DstHeight: LongInt; Filter: TFilterFunction; Radius: Single; WrapEdges: Boolean); const Channel8BitMax: Single = 255.0; type TBufferItem = record A, R, G, B: Integer; end; var MapX, MapY: TMappingTable; I, J, X, Y: LongInt; XMinimum, XMaximum: LongInt; LineBufferFP: array of TColorFPRec; LineBufferInt: array of TBufferItem; ClusterX, ClusterY: TCluster; Weight, AccumA, AccumR, AccumG, AccumB: Single; IWeight, IAccumA, IAccumR, IAccumG, IAccumB: Integer; DstLine: PByte; SrcColor: TColor32Rec; SrcFloat: TColorFPRec; Info: TImageFormatInfo; BytesPerChannel: LongInt; ChannelValueMax, InvChannelValueMax: Single; UseOptimizedVersion: Boolean; begin GetImageFormatInfo(SrcImage.Format, Info); Assert(SrcImage.Format = DstImage.Format); Assert(not Info.IsSpecial and not Info.IsIndexed); BytesPerChannel := Info.BytesPerPixel div Info.ChannelCount; UseOptimizedVersion := (BytesPerChannel = 1) and not Info.UsePixelFormat; // Create horizontal and vertical mapping tables MapX := BuildMappingTable(DstX, DstX + DstWidth, SrcX, SrcX + SrcWidth, SrcImage.Width, Filter, Radius, WrapEdges); MapY := BuildMappingTable(DstY, DstY + DstHeight, SrcY, SrcY + SrcHeight, SrcImage.Height, Filter, Radius, WrapEdges); if (MapX = nil) or (MapY = nil) then Exit; ClusterX := nil; ClusterY := nil; try // Find min and max X coords of pixels that will contribute to target image FindExtremes(MapX, XMinimum, XMaximum); if not UseOptimizedVersion then begin SetLength(LineBufferFP, XMaximum - XMinimum + 1); // Following code works for the rest of data formats for J := 0 to DstHeight - 1 do begin // First for each pixel in the current line sample vertically // and store results in LineBuffer. Then sample horizontally // using values in LineBuffer. ClusterY := MapY[J]; for X := XMinimum to XMaximum do begin // Clear accumulators AccumA := 0; AccumR := 0; AccumG := 0; AccumB := 0; // For each pixel in line compute weighted sum of pixels // in source column that will contribute to this pixel for Y := 0 to Length(ClusterY) - 1 do begin // Accumulate this pixel's weighted value Weight := ClusterY[Y].Weight; SrcFloat := Info.GetPixelFP(@PByteArray(SrcImage.Bits)[(ClusterY[Y].Pos * SrcImage.Width + X) * Info.BytesPerPixel], @Info, nil); AccumB := AccumB + SrcFloat.B * Weight; AccumG := AccumG + SrcFloat.G * Weight; AccumR := AccumR + SrcFloat.R * Weight; AccumA := AccumA + SrcFloat.A * Weight; end; // Store accumulated value for this pixel in buffer with LineBufferFP[X - XMinimum] do begin A := AccumA; R := AccumR; G := AccumG; B := AccumB; end; end; DstLine := @PByteArray(DstImage.Bits)[((J + DstY) * DstImage.Width + DstX) * Info.BytesPerPixel]; // Now compute final colors for targte pixels in the current row // by sampling horizontally for I := 0 to DstWidth - 1 do begin ClusterX := MapX[I]; // Clear accumulator AccumA := 0; AccumR := 0; AccumG := 0; AccumB := 0; // Compute weighted sum of values (which are already // computed weighted sums of pixels in source columns stored in LineBuffer) // that will contribute to the current target pixel for X := 0 to Length(ClusterX) - 1 do begin Weight := ClusterX[X].Weight; with LineBufferFP[ClusterX[X].Pos - XMinimum] do begin AccumB := AccumB + B * Weight; AccumG := AccumG + G * Weight; AccumR := AccumR + R * Weight; AccumA := AccumA + A * Weight; end; end; // Now compute final color to be written to dest image SrcFloat.A := AccumA; SrcFloat.R := AccumR; SrcFloat.G := AccumG; SrcFloat.B := AccumB; Info.SetPixelFP(DstLine, @Info, nil, SrcFloat); Inc(DstLine, Info.BytesPerPixel); end; end; end else begin SetLength(LineBufferInt, XMaximum - XMinimum + 1); // Following code is optimized for images with 8 bit channels for J := 0 to DstHeight - 1 do begin ClusterY := MapY[J]; for X := XMinimum to XMaximum do begin IAccumA := 0; IAccumR := 0; IAccumG := 0; IAccumB := 0; for Y := 0 to Length(ClusterY) - 1 do begin IWeight := Round(256 * ClusterY[Y].Weight); CopyPixel( @PByteArray(SrcImage.Bits)[(ClusterY[Y].Pos * SrcImage.Width + X) * Info.BytesPerPixel], @SrcColor, Info.BytesPerPixel); IAccumB := IAccumB + SrcColor.B * IWeight; IAccumG := IAccumG + SrcColor.G * IWeight; IAccumR := IAccumR + SrcColor.R * IWeight; IAccumA := IAccumA + SrcColor.A * IWeight; end; with LineBufferInt[X - XMinimum] do begin A := IAccumA; R := IAccumR; G := IAccumG; B := IAccumB; end; end; DstLine := @PByteArray(DstImage.Bits)[((J + DstY) * DstImage.Width + DstX)* Info.BytesPerPixel]; for I := 0 to DstWidth - 1 do begin ClusterX := MapX[I]; IAccumA := 0; IAccumR := 0; IAccumG := 0; IAccumB := 0; for X := 0 to Length(ClusterX) - 1 do begin IWeight := Round(256 * ClusterX[X].Weight); with LineBufferInt[ClusterX[X].Pos - XMinimum] do begin IAccumB := IAccumB + B * IWeight; IAccumG := IAccumG + G * IWeight; IAccumR := IAccumR + R * IWeight; IAccumA := IAccumA + A * IWeight; end; end; SrcColor.B := ClampInt(IAccumB, 0, $00FF0000) shr 16; SrcColor.G := ClampInt(IAccumG, 0, $00FF0000) shr 16; SrcColor.R := ClampInt(IAccumR, 0, $00FF0000) shr 16; SrcColor.A := ClampInt(IAccumA, 0, $00FF0000) shr 16; CopyPixel(@SrcColor, DstLine, Info.BytesPerPixel); Inc(DstLine, Info.BytesPerPixel); end; end; end; finally MapX := nil; MapY := nil; end; end; procedure FillMipMapLevel(const BiggerLevel: TImageData; Width, Height: LongInt; var SmallerLevel: TImageData); var Filter: TSamplingFilter; Info: TImageFormatInfo; CompatibleCopy: TImageData; begin Assert(TestImage(BiggerLevel)); Filter := TSamplingFilter(GetOption(ImagingMipMapFilter)); // If we have special format image we must create copy to allow pixel access GetImageFormatInfo(BiggerLevel.Format, Info); if Info.IsSpecial then begin InitImage(CompatibleCopy); CloneImage(BiggerLevel, CompatibleCopy); ConvertImage(CompatibleCopy, ifDefault); end else CompatibleCopy := BiggerLevel; // Create new smaller image NewImage(Width, Height, CompatibleCopy.Format, SmallerLevel); GetImageFormatInfo(CompatibleCopy.Format, Info); // If input is indexed we must copy its palette if Info.IsIndexed then CopyPalette(CompatibleCopy.Palette, SmallerLevel.Palette, 0, 0, Info.PaletteEntries); if (Filter = sfNearest) or Info.IsIndexed then begin StretchNearest(CompatibleCopy, 0, 0, CompatibleCopy.Width, CompatibleCopy.Height, SmallerLevel, 0, 0, Width, Height); end else begin StretchResample(CompatibleCopy, 0, 0, CompatibleCopy.Width, CompatibleCopy.Height, SmallerLevel, 0, 0, Width, Height, Filter); end; // Free copy and convert result to special format if necessary if CompatibleCopy.Format <> BiggerLevel.Format then begin ConvertImage(SmallerLevel, BiggerLevel.Format); FreeImage(CompatibleCopy); end; end; { Various format support functions } procedure CopyPixel(Src, Dest: Pointer; BytesPerPixel: LongInt); begin case BytesPerPixel of 1: PByte(Dest)^ := PByte(Src)^; 2: PWord(Dest)^ := PWord(Src)^; 3: PColor24Rec(Dest)^ := PColor24Rec(Src)^; 4: PLongWord(Dest)^ := PLongWord(Src)^; 6: PColor48Rec(Dest)^ := PColor48Rec(Src)^; 8: PInt64(Dest)^ := PInt64(Src)^; 16: PColorFPRec(Dest)^ := PColorFPRec(Src)^; end; end; function ComparePixels(PixelA, PixelB: Pointer; BytesPerPixel: LongInt): Boolean; begin case BytesPerPixel of 1: Result := PByte(PixelA)^ = PByte(PixelB)^; 2: Result := PWord(PixelA)^ = PWord(PixelB)^; 3: Result := (PWord(PixelA)^ = PWord(PixelB)^) and (PColor24Rec(PixelA).R = PColor24Rec(PixelB).R); 4: Result := PLongWord(PixelA)^ = PLongWord(PixelB)^; 6: Result := (PLongWord(PixelA)^ = PLongWord(PixelB)^) and (PColor48Rec(PixelA).R = PColor48Rec(PixelB).R); 8: Result := PInt64(PixelA)^ = PInt64(PixelB)^; 16: Result := (PFloatHelper(PixelA).Data2 = PFloatHelper(PixelB).Data2) and (PFloatHelper(PixelA).Data1 = PFloatHelper(PixelB).Data1); else Result := False; end; end; procedure TranslatePixel(SrcPixel, DstPixel: Pointer; SrcFormat, DstFormat: TImageFormat; SrcPalette, DstPalette: PPalette32); var SrcInfo, DstInfo: PImageFormatInfo; PixFP: TColorFPRec; begin SrcInfo := FInfos[SrcFormat]; DstInfo := FInfos[DstFormat]; PixFP := GetPixelFPGeneric(SrcPixel, SrcInfo, SrcPalette); SetPixelFPGeneric(DstPixel, DstInfo, DstPalette, PixFP); end; procedure ClampFloatPixel(var PixF: TColorFPRec); begin if PixF.A > 1.0 then PixF.A := 1.0; if PixF.R > 1.0 then PixF.R := 1.0; if PixF.G > 1.0 then PixF.G := 1.0; if PixF.B > 1.0 then PixF.B := 1.0; if PixF.A < 0.0 then PixF.A := 0.0; if PixF.R < 0.0 then PixF.R := 0.0; if PixF.G < 0.0 then PixF.G := 0.0; if PixF.B < 0.0 then PixF.B := 0.0; end; procedure AddPadBytes(DataIn: Pointer; DataOut: Pointer; Width, Height, Bpp, WidthBytes: LongInt); var I, W: LongInt; begin W := Width * Bpp; for I := 0 to Height - 1 do Move(PByteArray(DataIn)[I * W], PByteArray(DataOut)[I * WidthBytes], W); end; procedure RemovePadBytes(DataIn: Pointer; DataOut: Pointer; Width, Height, Bpp, WidthBytes: LongInt); var I, W: LongInt; begin W := Width * Bpp; for I := 0 to Height - 1 do Move(PByteArray(DataIn)[I * WidthBytes], PByteArray(DataOut)[I * W], W); end; procedure Convert1To8(DataIn, DataOut: Pointer; Width, Height, WidthBytes: LongInt); const Mask1: array[0..7] of Byte = ($80, $40, $20, $10, $08, $04, $02, $01); Shift1: array[0..7] of Byte = (7, 6, 5, 4, 3, 2, 1, 0); var X, Y: LongInt; begin for Y := 0 to Height - 1 do for X := 0 to Width - 1 do PByteArray(DataOut)[Y * Width + X] := (PByteArray(DataIn)[Y * WidthBytes + X shr 3] and Mask1[X and 7]) shr Shift1[X and 7]; end; procedure Convert2To8(DataIn, DataOut: Pointer; Width, Height, WidthBytes: LongInt); const Mask2: array[0..3] of Byte = ($C0, $30, $0C, $03); Shift2: array[0..3] of Byte = (6, 4, 2, 0); var X, Y: LongInt; begin for Y := 0 to Height - 1 do for X := 0 to Width - 1 do PByteArray(DataOut)[Y * Width + X] := (PByteArray(DataIn)[X shr 2] and Mask2[X and 3]) shr Shift2[X and 3]; end; procedure Convert4To8(DataIn, DataOut: Pointer; Width, Height, WidthBytes: LongInt); const Mask4: array[0..1] of Byte = ($F0, $0F); Shift4: array[0..1] of Byte = (4, 0); var X, Y: LongInt; begin for Y := 0 to Height - 1 do for X := 0 to Width - 1 do PByteArray(DataOut)[Y * Width + X] := (PByteArray(DataIn)[Y * WidthBytes + X shr 1] and Mask4[X and 1]) shr Shift4[X and 1]; end; function Has16BitImageAlpha(NumPixels: LongInt; Data: PWord): Boolean; var I: LongInt; begin Result := False; for I := 0 to NumPixels - 1 do begin if Data^ >= 1 shl 15 then begin Result := True; Exit; end; Inc(Data); end; end; function Has32BitImageAlpha(NumPixels: LongInt; Data: PLongWord): Boolean; var I: LongInt; begin Result := False; for I := 0 to NumPixels - 1 do begin if Data^ >= 1 shl 24 then begin Result := True; Exit; end; Inc(Data); end; end; function GetScanLine(ImageBits: Pointer; const FormatInfo: TImageFormatInfo; LineWidth, Index: LongInt): Pointer; var LineBytes: LongInt; begin Assert(not FormatInfo.IsSpecial); LineBytes := FormatInfo.GetPixelsSize(FormatInfo.Format, LineWidth, 1); Result := @PByteArray(ImageBits)[Index * LineBytes]; end; function IsImageFormatValid(Format: TImageFormat): Boolean; begin Result := FInfos[Format] <> nil; end; const HalfMin: Single = 5.96046448e-08; // Smallest positive half HalfMinNorm: Single = 6.10351562e-05; // Smallest positive normalized half HalfMax: Single = 65504.0; // Largest positive half HalfEpsilon: Single = 0.00097656; // Smallest positive e for which half (1.0 + e) != half (1.0) HalfNaN: THalfFloat = 65535; HalfPosInf: THalfFloat = 31744; HalfNegInf: THalfFloat = 64512; { Half/Float conversions inspired by half class from OpenEXR library. Float (Pascal Single type) is an IEEE 754 single-precision floating point number. Bit layout of Single: 31 (msb) | | 30 23 | | | | | | 22 0 (lsb) | | | | | X XXXXXXXX XXXXXXXXXXXXXXXXXXXXXXX s e m Bit layout of half: 15 (msb) | | 14 10 | | | | | | 9 0 (lsb) | | | | | X XXXXX XXXXXXXXXX s e m S is the sign-bit, e is the exponent and m is the significand (mantissa). } function HalfToFloat(Half: THalfFloat): Single; var Dst, Sign, Mantissa: LongWord; Exp: LongInt; begin // extract sign, exponent, and mantissa from half number Sign := Half shr 15; Exp := (Half and $7C00) shr 10; Mantissa := Half and 1023; if (Exp > 0) and (Exp < 31) then begin // common normalized number Exp := Exp + (127 - 15); Mantissa := Mantissa shl 13; Dst := (Sign shl 31) or (LongWord(Exp) shl 23) or Mantissa; // Result := Power(-1, Sign) * Power(2, Exp - 15) * (1 + Mantissa / 1024); end else if (Exp = 0) and (Mantissa = 0) then begin // zero - preserve sign Dst := Sign shl 31; end else if (Exp = 0) and (Mantissa <> 0) then begin // denormalized number - renormalize it while (Mantissa and $00000400) = 0 do begin Mantissa := Mantissa shl 1; Dec(Exp); end; Inc(Exp); Mantissa := Mantissa and not $00000400; // now assemble normalized number Exp := Exp + (127 - 15); Mantissa := Mantissa shl 13; Dst := (Sign shl 31) or (LongWord(Exp) shl 23) or Mantissa; // Result := Power(-1, Sign) * Power(2, -14) * (Mantissa / 1024); end else if (Exp = 31) and (Mantissa = 0) then begin // +/- infinity Dst := (Sign shl 31) or $7F800000; end else //if (Exp = 31) and (Mantisa <> 0) then begin // not a number - preserve sign and mantissa Dst := (Sign shl 31) or $7F800000 or (Mantissa shl 13); end; // reinterpret LongWord as Single Result := PSingle(@Dst)^; end; function FloatToHalf(Float: Single): THalfFloat; var Src: LongWord; Sign, Exp, Mantissa: LongInt; begin Src := PLongWord(@Float)^; // extract sign, exponent, and mantissa from Single number Sign := Src shr 31; Exp := LongInt((Src and $7F800000) shr 23) - 127 + 15; Mantissa := Src and $007FFFFF; if (Exp > 0) and (Exp < 30) then begin // simple case - round the significand and combine it with the sign and exponent Result := (Sign shl 15) or (Exp shl 10) or ((Mantissa + $00001000) shr 13); end else if Src = 0 then begin // input float is zero - return zero Result := 0; end else begin // difficult case - lengthy conversion if Exp <= 0 then begin if Exp < -10 then begin // input float's value is less than HalfMin, return zero Result := 0; end else begin // Float is a normalized Single whose magnitude is less than HalfNormMin. // We convert it to denormalized half. Mantissa := (Mantissa or $00800000) shr (1 - Exp); // round to nearest if (Mantissa and $00001000) > 0 then Mantissa := Mantissa + $00002000; // assemble Sign and Mantissa (Exp is zero to get denotmalized number) Result := (Sign shl 15) or (Mantissa shr 13); end; end else if Exp = 255 - 127 + 15 then begin if Mantissa = 0 then begin // input float is infinity, create infinity half with original sign Result := (Sign shl 15) or $7C00; end else begin // input float is NaN, create half NaN with original sign and mantissa Result := (Sign shl 15) or $7C00 or (Mantissa shr 13); end; end else begin // Exp is > 0 so input float is normalized Single // round to nearest if (Mantissa and $00001000) > 0 then begin Mantissa := Mantissa + $00002000; if (Mantissa and $00800000) > 0 then begin Mantissa := 0; Exp := Exp + 1; end; end; if Exp > 30 then begin // exponent overflow - return infinity half Result := (Sign shl 15) or $7C00; end else // assemble normalized half Result := (Sign shl 15) or (Exp shl 10) or (Mantissa shr 13); end; end; end; function ColorHalfToFloat(ColorHF: TColorHFRec): TColorFPRec; begin Result.A := HalfToFloat(ColorHF.A); Result.R := HalfToFloat(ColorHF.R); Result.G := HalfToFloat(ColorHF.G); Result.B := HalfToFloat(ColorHF.B); end; function ColorFloatToHalf(ColorFP: TColorFPRec): TColorHFRec; begin Result.A := FloatToHalf(ColorFP.A); Result.R := FloatToHalf(ColorFP.R); Result.G := FloatToHalf(ColorFP.G); Result.B := FloatToHalf(ColorFP.B); end; procedure VisualizePalette(Pal: PPalette32; Entries: Integer; out PalImage: TImageData); var I: Integer; Pix: PColor32; begin InitImage(PalImage); NewImage(Entries, 1, ifA8R8G8B8, PalImage); Pix := PalImage.Bits; for I := 0 to Entries - 1 do begin Pix^ := Pal[I].Color; Inc(Pix); end; end; { Pixel readers/writers for different image formats } procedure ChannelGetSrcPixel(Src: PByte; SrcInfo: PImageFormatInfo; var Pix: TColor64Rec); var A, R, G, B: Byte; begin FillChar(Pix, SizeOf(Pix), 0); // returns 64 bit color value with 16 bits for each channel case SrcInfo.BytesPerPixel of 1: begin PFGetARGB(SrcInfo.PixelFormat^, Src^, A, R, G, B); Pix.A := A shl 8; Pix.R := R shl 8; Pix.G := G shl 8; Pix.B := B shl 8; end; 2: begin PFGetARGB(SrcInfo.PixelFormat^, PWord(Src)^, A, R, G, B); Pix.A := A shl 8; Pix.R := R shl 8; Pix.G := G shl 8; Pix.B := B shl 8; end; 3: with Pix do begin R := MulDiv(PColor24Rec(Src).R, 65535, 255); G := MulDiv(PColor24Rec(Src).G, 65535, 255); B := MulDiv(PColor24Rec(Src).B, 65535, 255); end; 4: with Pix do begin A := MulDiv(PColor32Rec(Src).A, 65535, 255); R := MulDiv(PColor32Rec(Src).R, 65535, 255); G := MulDiv(PColor32Rec(Src).G, 65535, 255); B := MulDiv(PColor32Rec(Src).B, 65535, 255); end; 6: with Pix do begin R := PColor48Rec(Src).R; G := PColor48Rec(Src).G; B := PColor48Rec(Src).B; end; 8: Pix.Color := PColor64(Src)^; end; // if src has no alpha, we set it to max (otherwise we would have to // test if dest has alpha or not in each ChannelToXXX function) if not SrcInfo.HasAlphaChannel then Pix.A := 65535; if SrcInfo.IsRBSwapped then SwapValues(Pix.R, Pix.B); end; procedure ChannelSetDstPixel(Dst: PByte; DstInfo: PImageFormatInfo; const Pix: TColor64Rec); var PixW: TColor64Rec; begin PixW := Pix; if DstInfo.IsRBSwapped then SwapValues(PixW.R, PixW.B); // Pix contains 64 bit color value with 16 bit for each channel case DstInfo.BytesPerPixel of 1: Dst^ := PFSetARGB(DstInfo.PixelFormat^, PixW.A shr 8, PixW.R shr 8, PixW.G shr 8, PixW.B shr 8); 2: PWord(Dst)^ := PFSetARGB(DstInfo.PixelFormat^, PixW.A shr 8, PixW.R shr 8, PixW.G shr 8, PixW.B shr 8); 3: with PColor24Rec(Dst)^ do begin R := MulDiv(PixW.R, 255, 65535); G := MulDiv(PixW.G, 255, 65535); B := MulDiv(PixW.B, 255, 65535); end; 4: with PColor32Rec(Dst)^ do begin A := MulDiv(PixW.A, 255, 65535); R := MulDiv(PixW.R, 255, 65535); G := MulDiv(PixW.G, 255, 65535); B := MulDiv(PixW.B, 255, 65535); end; 6: with PColor48Rec(Dst)^ do begin R := PixW.R; G := PixW.G; B := PixW.B; end; 8: PColor64(Dst)^ := PixW.Color; end; end; procedure GrayGetSrcPixel(Src: PByte; SrcInfo: PImageFormatInfo; var Gray: TColor64Rec; var Alpha: Word); begin FillChar(Gray, SizeOf(Gray), 0); // Source alpha is scaled to 16 bits and stored in Alpha, // grayscale value is scaled to 64 bits and stored in Gray case SrcInfo.BytesPerPixel of 1: Gray.A := MulDiv(Src^, 65535, 255); 2: if SrcInfo.HasAlphaChannel then with PWordRec(Src)^ do begin Alpha := MulDiv(High, 65535, 255); Gray.A := MulDiv(Low, 65535, 255); end else Gray.A := PWord(Src)^; 4: if SrcInfo.HasAlphaChannel then with PLongWordRec(Src)^ do begin Alpha := High; Gray.A := Low; end else with PLongWordRec(Src)^ do begin Gray.A := High; Gray.R := Low; end; 8: Gray.Color := PColor64(Src)^; end; // if src has no alpha, we set it to max (otherwise we would have to // test if dest has alpha or not in each GrayToXXX function) if not SrcInfo.HasAlphaChannel then Alpha := 65535; end; procedure GraySetDstPixel(Dst: PByte; DstInfo: PImageFormatInfo; const Gray: TColor64Rec; Alpha: Word); begin // Gray contains grayscale value scaled to 64 bits, Alpha contains // alpha value scaled to 16 bits case DstInfo.BytesPerPixel of 1: Dst^ := MulDiv(Gray.A, 255, 65535); 2: if DstInfo.HasAlphaChannel then with PWordRec(Dst)^ do begin High := MulDiv(Alpha, 255, 65535); Low := MulDiv(Gray.A, 255, 65535); end else PWord(Dst)^ := Gray.A; 4: if DstInfo.HasAlphaChannel then with PLongWordRec(Dst)^ do begin High := Alpha; Low := Gray.A; end else with PLongWordRec(Dst)^ do begin High := Gray.A; Low := Gray.R; end; 8: PColor64(Dst)^ := Gray.Color; end; end; procedure FloatGetSrcPixel(Src: PByte; SrcInfo: PImageFormatInfo; var Pix: TColorFPRec); var PixHF: TColorHFRec; begin if SrcInfo.BytesPerPixel in [4, 16] then begin // IEEE 754 single-precision channels FillChar(Pix, SizeOf(Pix), 0); case SrcInfo.BytesPerPixel of 4: Pix.R := PSingle(Src)^; 16: Pix := PColorFPRec(Src)^; end; end else begin // half float channels FillChar(PixHF, SizeOf(PixHF), 0); case SrcInfo.BytesPerPixel of 2: PixHF.R := PHalfFloat(Src)^; 8: PixHF := PColorHFRec(Src)^; end; Pix := ColorHalfToFloat(PixHF); end; // if src has no alpha, we set it to max (otherwise we would have to // test if dest has alpha or not in each FloatToXXX function) if not SrcInfo.HasAlphaChannel then Pix.A := 1.0; if SrcInfo.IsRBSwapped then SwapValues(Pix.R, Pix.B); end; procedure FloatSetDstPixel(Dst: PByte; DstInfo: PImageFormatInfo; const Pix: TColorFPRec); var PixW: TColorFPRec; PixHF: TColorHFRec; begin PixW := Pix; if DstInfo.IsRBSwapped then SwapValues(PixW.R, PixW.B); if DstInfo.BytesPerPixel in [4, 16] then begin case DstInfo.BytesPerPixel of 4: PSingle(Dst)^ := PixW.R; 16: PColorFPRec(Dst)^ := PixW; end; end else begin PixHF := ColorFloatToHalf(PixW); case DstInfo.BytesPerPixel of 2: PHalfFloat(Dst)^ := PixHF.R; 8: PColorHFRec(Dst)^ := PixHF; end; end; end; procedure IndexGetSrcPixel(Src: PByte; SrcInfo: PImageFormatInfo; var Index: LongWord); begin case SrcInfo.BytesPerPixel of 1: Index := Src^; end; end; procedure IndexSetDstPixel(Dst: PByte; DstInfo: PImageFormatInfo; Index: LongWord); begin case DstInfo.BytesPerPixel of 1: Dst^ := Byte(Index); 2: PWord(Dst)^ := Word(Index); 4: PLongWord(Dst)^ := Index; end; end; { Pixel readers/writers for 32bit and FP colors} function GetPixel32Generic(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32): TColor32Rec; var Pix64: TColor64Rec; PixF: TColorFPRec; Alpha: Word; Index: LongWord; begin if Info.Format = ifA8R8G8B8 then begin Result := PColor32Rec(Bits)^ end else if Info.Format = ifR8G8B8 then begin PColor24Rec(@Result)^ := PColor24Rec(Bits)^; Result.A := $FF; end else if Info.IsFloatingPoint then begin FloatGetSrcPixel(Bits, Info, PixF); Result.A := ClampToByte(Round(PixF.A * 255.0)); Result.R := ClampToByte(Round(PixF.R * 255.0)); Result.G := ClampToByte(Round(PixF.G * 255.0)); Result.B := ClampToByte(Round(PixF.B * 255.0)); end else if Info.HasGrayChannel then begin GrayGetSrcPixel(Bits, Info, Pix64, Alpha); Result.A := MulDiv(Alpha, 255, 65535); Result.R := MulDiv(Pix64.A, 255, 65535); Result.G := MulDiv(Pix64.A, 255, 65535); Result.B := MulDiv(Pix64.A, 255, 65535); end else if Info.IsIndexed then begin IndexGetSrcPixel(Bits, Info, Index); Result := Palette[Index]; end else begin ChannelGetSrcPixel(Bits, Info, Pix64); Result.A := MulDiv(Pix64.A, 255, 65535); Result.R := MulDiv(Pix64.R, 255, 65535); Result.G := MulDiv(Pix64.G, 255, 65535); Result.B := MulDiv(Pix64.B, 255, 65535); end; end; procedure SetPixel32Generic(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32; const Color: TColor32Rec); var Pix64: TColor64Rec; PixF: TColorFPRec; Alpha: Word; Index: LongWord; begin if Info.Format = ifA8R8G8B8 then begin PColor32Rec(Bits)^ := Color end else if Info.Format = ifR8G8B8 then begin PColor24Rec(Bits)^ := Color.Color24Rec; end else if Info.IsFloatingPoint then begin PixF.A := Color.A * OneDiv8Bit; PixF.R := Color.R * OneDiv8Bit; PixF.G := Color.G * OneDiv8Bit; PixF.B := Color.B * OneDiv8Bit; FloatSetDstPixel(Bits, Info, PixF); end else if Info.HasGrayChannel then begin Alpha := MulDiv(Color.A, 65535, 255); Pix64.Color := 0; Pix64.A := MulDiv(Round(GrayConv.R * Color.R + GrayConv.G * Color.G + GrayConv.B * Color.B), 65535, 255); GraySetDstPixel(Bits, Info, Pix64, Alpha); end else if Info.IsIndexed then begin Index := FindColor(Palette, Info.PaletteEntries, Color.Color); IndexSetDstPixel(Bits, Info, Index); end else begin Pix64.A := MulDiv(Color.A, 65535, 255); Pix64.R := MulDiv(Color.R, 65535, 255); Pix64.G := MulDiv(Color.G, 65535, 255); Pix64.B := MulDiv(Color.B, 65535, 255); ChannelSetDstPixel(Bits, Info, Pix64); end; end; function GetPixelFPGeneric(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32): TColorFPRec; var Pix32: TColor32Rec; Pix64: TColor64Rec; Alpha: Word; Index: LongWord; begin if Info.IsFloatingPoint then begin FloatGetSrcPixel(Bits, Info, Result); end else if Info.HasGrayChannel then begin GrayGetSrcPixel(Bits, Info, Pix64, Alpha); Result.A := Alpha * OneDiv16Bit; Result.R := Pix64.A * OneDiv16Bit; Result.G := Pix64.A * OneDiv16Bit; Result.B := Pix64.A * OneDiv16Bit; end else if Info.IsIndexed then begin IndexGetSrcPixel(Bits, Info, Index); Pix32 := Palette[Index]; Result.A := Pix32.A * OneDiv8Bit; Result.R := Pix32.R * OneDiv8Bit; Result.G := Pix32.G * OneDiv8Bit; Result.B := Pix32.B * OneDiv8Bit; end else begin ChannelGetSrcPixel(Bits, Info, Pix64); Result.A := Pix64.A * OneDiv16Bit; Result.R := Pix64.R * OneDiv16Bit; Result.G := Pix64.G * OneDiv16Bit; Result.B := Pix64.B * OneDiv16Bit; end; end; procedure SetPixelFPGeneric(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32; const Color: TColorFPRec); var Pix32: TColor32Rec; Pix64: TColor64Rec; Alpha: Word; Index: LongWord; begin if Info.IsFloatingPoint then begin FloatSetDstPixel(Bits, Info, Color); end else if Info.HasGrayChannel then begin Alpha := ClampToWord(Round(Color.A * 65535.0)); Pix64.Color := 0; Pix64.A := ClampToWord(Round((GrayConv.R * Color.R + GrayConv.G * Color.G + GrayConv.B * Color.B) * 65535.0)); GraySetDstPixel(Bits, Info, Pix64, Alpha); end else if Info.IsIndexed then begin Pix32.A := ClampToByte(Round(Color.A * 255.0)); Pix32.R := ClampToByte(Round(Color.R * 255.0)); Pix32.G := ClampToByte(Round(Color.G * 255.0)); Pix32.B := ClampToByte(Round(Color.B * 255.0)); Index := FindColor(Palette, Info.PaletteEntries, Pix32.Color); IndexSetDstPixel(Bits, Info, Index); end else begin Pix64.A := ClampToWord(Round(Color.A * 65535.0)); Pix64.R := ClampToWord(Round(Color.R * 65535.0)); Pix64.G := ClampToWord(Round(Color.G * 65535.0)); Pix64.B := ClampToWord(Round(Color.B * 65535.0)); ChannelSetDstPixel(Bits, Info, Pix64); end; end; { Image format conversion functions } procedure ChannelToChannel(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo); var I: LongInt; Pix64: TColor64Rec; begin // two most common conversions (RGB->ARGB and ARGB->RGB for 24/32 bit // images) are made separately from general ARGB conversion to // make them faster if (SrcInfo.BytesPerPixel = 3) and (DstInfo.BytesPerPixel = 4) then for I := 0 to NumPixels - 1 do begin PColor24Rec(Dst)^ := PColor24Rec(Src)^; if DstInfo.HasAlphaChannel then PColor32Rec(Dst).A := 255; Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end else if (SrcInfo.BytesPerPixel = 4) and (DstInfo.BytesPerPixel = 3) then for I := 0 to NumPixels - 1 do begin PColor24Rec(Dst)^ := PColor24Rec(Src)^; Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end else for I := 0 to NumPixels - 1 do begin // general ARGB conversion ChannelGetSrcPixel(Src, SrcInfo, Pix64); ChannelSetDstPixel(Dst, DstInfo, Pix64); Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end; end; procedure ChannelToGray(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo); var I: LongInt; Pix64: TColor64Rec; Alpha: Word; begin // two most common conversions (R8G8B8->Gray8 nad A8R8G8B8->Gray8) // are made separately from general conversions to make them faster if (SrcInfo.BytesPerPixel in [3, 4]) and (DstInfo.Format = ifGray8) then for I := 0 to NumPixels - 1 do begin Dst^ := Round(GrayConv.R * PColor24Rec(Src).R + GrayConv.G * PColor24Rec(Src).G + GrayConv.B * PColor24Rec(Src).B); Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end else for I := 0 to NumPixels - 1 do begin ChannelGetSrcPixel(Src, SrcInfo, Pix64); // alpha is saved from source pixel to Alpha, // Gray value is computed and set to highest word of Pix64 so // Pix64.Color contains grayscale value scaled to 64 bits Alpha := Pix64.A; with GrayConv do Pix64.A := Round(R * Pix64.R + G * Pix64.G + B * Pix64.B); GraySetDstPixel(Dst, DstInfo, Pix64, Alpha); Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end; end; procedure ChannelToFloat(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo); var I: LongInt; Pix64: TColor64Rec; PixF: TColorFPRec; begin for I := 0 to NumPixels - 1 do begin ChannelGetSrcPixel(Src, SrcInfo, Pix64); // floating point channel values are scaled to 1.0 PixF.A := Pix64.A * OneDiv16Bit; PixF.R := Pix64.R * OneDiv16Bit; PixF.G := Pix64.G * OneDiv16Bit; PixF.B := Pix64.B * OneDiv16Bit; FloatSetDstPixel(Dst, DstInfo, PixF); Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end; end; procedure ChannelToIndex(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo; DstPal: PPalette32); begin ReduceColorsMedianCut(NumPixels, Src, Dst, SrcInfo, DstInfo, DstInfo.PaletteEntries, GetOption(ImagingColorReductionMask), DstPal); end; procedure GrayToGray(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo); var I: LongInt; Gray: TColor64Rec; Alpha: Word; begin // two most common conversions (Gray8->Gray16 nad Gray16->Gray8) // are made separately from general conversions to make them faster if (SrcInfo.Format = ifGray8) and (DstInfo.Format = ifGray16) then begin for I := 0 to NumPixels - 1 do PWordArray(Dst)[I] := PByteArray(Src)[I] shl 8; end else if (DstInfo.Format = ifGray8) and (SrcInfo.Format = ifGray16) then begin for I := 0 to NumPixels - 1 do PByteArray(Dst)[I] := PWordArray(Src)[I] shr 8; end else for I := 0 to NumPixels - 1 do begin // general grayscale conversion GrayGetSrcPixel(Src, SrcInfo, Gray, Alpha); GraySetDstPixel(Dst, DstInfo, Gray, Alpha); Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end; end; procedure GrayToChannel(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo); var I: LongInt; Pix64: TColor64Rec; Alpha: Word; begin // two most common conversions (Gray8->R8G8B8 nad Gray8->A8R8G8B8) // are made separately from general conversions to make them faster if (DstInfo.BytesPerPixel in [3, 4]) and (SrcInfo.Format = ifGray8) then for I := 0 to NumPixels - 1 do begin PColor24Rec(Dst).R := Src^; PColor24Rec(Dst).G := Src^; PColor24Rec(Dst).B := Src^; if DstInfo.HasAlphaChannel then PColor32Rec(Dst).A := $FF; Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end else for I := 0 to NumPixels - 1 do begin GrayGetSrcPixel(Src, SrcInfo, Pix64, Alpha); // most significant word of grayscale value is used for // each channel and alpha channel is set to Alpha Pix64.R := Pix64.A; Pix64.G := Pix64.A; Pix64.B := Pix64.A; Pix64.A := Alpha; ChannelSetDstPixel(Dst, DstInfo, Pix64); Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end; end; procedure GrayToFloat(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo); var I: LongInt; Gray: TColor64Rec; PixF: TColorFPRec; Alpha: Word; begin for I := 0 to NumPixels - 1 do begin GrayGetSrcPixel(Src, SrcInfo, Gray, Alpha); // most significant word of grayscale value is used for // each channel and alpha channel is set to Alpha // then all is scaled to 0..1 PixF.R := Gray.A * OneDiv16Bit; PixF.G := Gray.A * OneDiv16Bit; PixF.B := Gray.A * OneDiv16Bit; PixF.A := Alpha * OneDiv16Bit; FloatSetDstPixel(Dst, DstInfo, PixF); Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end; end; procedure GrayToIndex(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo; DstPal: PPalette32); var I: LongInt; Idx: LongWord; Gray: TColor64Rec; Alpha, Shift: Word; begin FillGrayscalePalette(DstPal, DstInfo.PaletteEntries); Shift := Log2Int(DstInfo.PaletteEntries); // most common conversion (Gray8->Index8) // is made separately from general conversions to make it faster if (SrcInfo.Format = ifGray8) and (DstInfo.Format = ifIndex8) then for I := 0 to NumPixels - 1 do begin Dst^ := Src^; Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end else for I := 0 to NumPixels - 1 do begin // gray value is read from src and index to precomputed // grayscale palette is computed and written to dst // (we assume here that there will be no more than 65536 palette // entries in dst format, gray value is shifted so the highest // gray value match the highest possible index in palette) GrayGetSrcPixel(Src, SrcInfo, Gray, Alpha); Idx := Gray.A shr (16 - Shift); IndexSetDstPixel(Dst, DstInfo, Idx); Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end; end; procedure FloatToFloat(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo); var I: LongInt; PixF: TColorFPRec; begin for I := 0 to NumPixels - 1 do begin // general floating point conversion FloatGetSrcPixel(Src, SrcInfo, PixF); FloatSetDstPixel(Dst, DstInfo, PixF); Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end; end; procedure FloatToChannel(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo); var I: LongInt; Pix64: TColor64Rec; PixF: TColorFPRec; begin for I := 0 to NumPixels - 1 do begin FloatGetSrcPixel(Src, SrcInfo, PixF); ClampFloatPixel(PixF); // floating point channel values are scaled to 1.0 Pix64.A := ClampToWord(Round(PixF.A * 65535)); Pix64.R := ClampToWord(Round(PixF.R * 65535)); Pix64.G := ClampToWord(Round(PixF.G * 65535)); Pix64.B := ClampToWord(Round(PixF.B * 65535)); ChannelSetDstPixel(Dst, DstInfo, Pix64); Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end; end; procedure FloatToGray(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo); var I: LongInt; PixF: TColorFPRec; Gray: TColor64Rec; Alpha: Word; begin for I := 0 to NumPixels - 1 do begin FloatGetSrcPixel(Src, SrcInfo, PixF); ClampFloatPixel(PixF); // alpha is saved from source pixel to Alpha, // Gray value is computed and set to highest word of Pix64 so // Pix64.Color contains grayscale value scaled to 64 bits Alpha := ClampToWord(Round(PixF.A * 65535.0)); Gray.A := ClampToWord(Round((GrayConv.R * PixF.R + GrayConv.G * PixF.G + GrayConv.B * PixF.B) * 65535.0)); GraySetDstPixel(Dst, DstInfo, Gray, Alpha); Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end; end; procedure FloatToIndex(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo; DstPal: PPalette32); begin ReduceColorsMedianCut(NumPixels, Src, Dst, SrcInfo, DstInfo, DstInfo.PaletteEntries, GetOption(ImagingColorReductionMask), DstPal); end; procedure IndexToIndex(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo; SrcPal, DstPal: PPalette32); var I: LongInt; begin // there is only one indexed format now, so it is just a copy for I := 0 to NumPixels - 1 do begin Dst^ := Src^; Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end; for I := 0 to SrcInfo.PaletteEntries - 1 do DstPal[I] := SrcPal[I]; end; procedure IndexToChannel(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo; SrcPal: PPalette32); var I: LongInt; Pix64: TColor64Rec; Idx: LongWord; begin // two most common conversions (Index8->R8G8B8 nad Index8->A8R8G8B8) // are made separately from general conversions to make them faster if (SrcInfo.Format = ifIndex8) and (DstInfo.Format in [ifR8G8B8, ifA8R8G8B8]) then for I := 0 to NumPixels - 1 do begin with PColor24Rec(Dst)^ do begin R := SrcPal[Src^].R; G := SrcPal[Src^].G; B := SrcPal[Src^].B; end; if DstInfo.Format = ifA8R8G8B8 then PColor32Rec(Dst).A := SrcPal[Src^].A; Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end else for I := 0 to NumPixels - 1 do begin // index to palette is read from source and color // is retrieved from palette entry. Color is then // scaled to 16bits and written to dest IndexGetSrcPixel(Src, SrcInfo, Idx); with Pix64 do begin A := SrcPal[Idx].A shl 8; R := SrcPal[Idx].R shl 8; G := SrcPal[Idx].G shl 8; B := SrcPal[Idx].B shl 8; end; ChannelSetDstPixel(Dst, DstInfo, Pix64); Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end; end; procedure IndexToGray(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo; SrcPal: PPalette32); var I: LongInt; Gray: TColor64Rec; Alpha: Word; Idx: LongWord; begin // most common conversion (Index8->Gray8) // is made separately from general conversions to make it faster if (SrcInfo.Format = ifIndex8) and (DstInfo.Format = ifGray8) then begin for I := 0 to NumPixels - 1 do begin Dst^ := Round(GrayConv.R * SrcPal[Src^].R + GrayConv.G * SrcPal[Src^].G + GrayConv.B * SrcPal[Src^].B); Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end end else for I := 0 to NumPixels - 1 do begin // index to palette is read from source and color // is retrieved from palette entry. Color is then // transformed to grayscale and assigned to the highest // byte of Gray value IndexGetSrcPixel(Src, SrcInfo, Idx); Alpha := SrcPal[Idx].A shl 8; Gray.A := MulDiv(Round(GrayConv.R * SrcPal[Idx].R + GrayConv.G * SrcPal[Idx].G + GrayConv.B * SrcPal[Idx].B), 65535, 255); GraySetDstPixel(Dst, DstInfo, Gray, Alpha); Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end; end; procedure IndexToFloat(NumPixels: LongInt; Src, Dst: PByte; SrcInfo, DstInfo: PImageFormatInfo; SrcPal: PPalette32); var I: LongInt; Idx: LongWord; PixF: TColorFPRec; begin for I := 0 to NumPixels - 1 do begin // index to palette is read from source and color // is retrieved from palette entry. Color is then // scaled to 0..1 and written to dest IndexGetSrcPixel(Src, SrcInfo, Idx); with PixF do begin A := SrcPal[Idx].A * OneDiv8Bit; R := SrcPal[Idx].R * OneDiv8Bit; G := SrcPal[Idx].G * OneDiv8Bit; B := SrcPal[Idx].B * OneDiv8Bit; end; FloatSetDstPixel(Dst, DstInfo, PixF); Inc(Src, SrcInfo.BytesPerPixel); Inc(Dst, DstInfo.BytesPerPixel); end; end; { Special formats conversion functions } type // DXT RGB color block TDXTColorBlock = packed record Color0, Color1: Word; Mask: LongWord; end; PDXTColorBlock = ^TDXTColorBlock; // DXT explicit alpha for a block TDXTAlphaBlockExp = packed record Alphas: array[0..3] of Word; end; PDXTAlphaBlockExp = ^TDXTAlphaBlockExp; // DXT interpolated alpha for a block TDXTAlphaBlockInt = packed record Alphas: array[0..7] of Byte; end; PDXTAlphaBlockInt = ^TDXTAlphaBlockInt; TPixelInfo = record Color: Word; Alpha: Byte; Orig: TColor32Rec; end; TPixelBlock = array[0..15] of TPixelInfo; function DecodeCol(Color: Word): TColor32Rec; {$IFDEF USE_INLINE} inline; {$ENDIF} begin Result.A := $FF; { Result.R := ((Color and $F800) shr 11) shl 3; Result.G := ((Color and $07E0) shr 5) shl 2; Result.B := (Color and $001F) shl 3;} // this color expansion is slower but gives better results Result.R := (Color shr 11) * 255 div 31; Result.G := ((Color shr 5) and $3F) * 255 div 63; Result.B := (Color and $1F) * 255 div 31; end; procedure DecodeDXT1(SrcBits, DestBits: PByte; Width, Height: LongInt); var Sel, X, Y, I, J, K: LongInt; Block: TDXTColorBlock; Colors: array[0..3] of TColor32Rec; begin for Y := 0 to Height div 4 - 1 do for X := 0 to Width div 4 - 1 do begin Block := PDXTColorBlock(SrcBits)^; Inc(SrcBits, SizeOf(Block)); // we read and decode endpoint colors Colors[0] := DecodeCol(Block.Color0); Colors[1] := DecodeCol(Block.Color1); // and interpolate between them if Block.Color0 > Block.Color1 then begin // interpolation for block without alpha Colors[2].A := $FF; Colors[2].R := (Colors[0].R shl 1 + Colors[1].R + 1) div 3; Colors[2].G := (Colors[0].G shl 1 + Colors[1].G + 1) div 3; Colors[2].B := (Colors[0].B shl 1 + Colors[1].B + 1) div 3; Colors[3].A := $FF; Colors[3].R := (Colors[0].R + Colors[1].R shl 1 + 1) div 3; Colors[3].G := (Colors[0].G + Colors[1].G shl 1 + 1) div 3; Colors[3].B := (Colors[0].B + Colors[1].B shl 1 + 1) div 3; end else begin // interpolation for block with alpha Colors[2].A := $FF; Colors[2].R := (Colors[0].R + Colors[1].R) shr 1; Colors[2].G := (Colors[0].G + Colors[1].G) shr 1; Colors[2].B := (Colors[0].B + Colors[1].B) shr 1; Colors[3].A := 0; Colors[3].R := (Colors[0].R + Colors[1].R shl 1 + 1) div 3; Colors[3].G := (Colors[0].G + Colors[1].G shl 1 + 1) div 3; Colors[3].B := (Colors[0].B + Colors[1].B shl 1 + 1) div 3; end; // we distribute the dxt block colors across the 4x4 block of the // destination image accroding to the dxt block mask K := 0; for J := 0 to 3 do for I := 0 to 3 do begin Sel := (Block.Mask and (3 shl (K shl 1))) shr (K shl 1); if ((X shl 2 + I) < Width) and ((Y shl 2 + J) < Height) then PPalette32(DestBits)[(Y shl 2 + J) * Width + X shl 2 + I] := Colors[Sel]; Inc(K); end; end; end; procedure DecodeDXT3(SrcBits, DestBits: PByte; Width, Height: LongInt); var Sel, X, Y, I, J, K: LongInt; Block: TDXTColorBlock; AlphaBlock: TDXTAlphaBlockExp; Colors: array[0..3] of TColor32Rec; AWord: Word; begin for Y := 0 to Height div 4 - 1 do for X := 0 to Width div 4 - 1 do begin AlphaBlock := PDXTAlphaBlockExp(SrcBits)^; Inc(SrcBits, SizeOf(AlphaBlock)); Block := PDXTColorBlock(SrcBits)^; Inc(SrcBits, SizeOf(Block)); // we read and decode endpoint colors Colors[0] := DecodeCol(Block.Color0); Colors[1] := DecodeCol(Block.Color1); // and interpolate between them Colors[2].R := (Colors[0].R shl 1 + Colors[1].R + 1) div 3; Colors[2].G := (Colors[0].G shl 1 + Colors[1].G + 1) div 3; Colors[2].B := (Colors[0].B shl 1 + Colors[1].B + 1) div 3; Colors[3].R := (Colors[0].R + Colors[1].R shl 1 + 1) div 3; Colors[3].G := (Colors[0].G + Colors[1].G shl 1 + 1) div 3; Colors[3].B := (Colors[0].B + Colors[1].B shl 1 + 1) div 3; // we distribute the dxt block colors and alphas // across the 4x4 block of the destination image // accroding to the dxt block mask and alpha block K := 0; for J := 0 to 3 do begin AWord := AlphaBlock.Alphas[J]; for I := 0 to 3 do begin Sel := (Block.Mask and (3 shl (K shl 1))) shr (K shl 1); if (X shl 2 + I < Width) and (Y shl 2 + J < Height) then begin Colors[Sel].A := AWord and $0F; Colors[Sel].A := Colors[Sel].A or (Colors[Sel].A shl 4); PPalette32(DestBits)[(Y shl 2 + J) * Width + X shl 2 + I] := Colors[Sel]; end; Inc(K); AWord := AWord shr 4; end; end; end; end; procedure GetInterpolatedAlphas(var AlphaBlock: TDXTAlphaBlockInt); begin with AlphaBlock do if Alphas[0] > Alphas[1] then begin // Interpolation of six alphas Alphas[2] := (6 * Alphas[0] + 1 * Alphas[1] + 3) div 7; Alphas[3] := (5 * Alphas[0] + 2 * Alphas[1] + 3) div 7; Alphas[4] := (4 * Alphas[0] + 3 * Alphas[1] + 3) div 7; Alphas[5] := (3 * Alphas[0] + 4 * Alphas[1] + 3) div 7; Alphas[6] := (2 * Alphas[0] + 5 * Alphas[1] + 3) div 7; Alphas[7] := (1 * Alphas[0] + 6 * Alphas[1] + 3) div 7; end else begin // Interpolation of four alphas, two alphas are set directly Alphas[2] := (4 * Alphas[0] + 1 * Alphas[1] + 2) div 5; Alphas[3] := (3 * Alphas[0] + 2 * Alphas[1] + 2) div 5; Alphas[4] := (2 * Alphas[0] + 3 * Alphas[1] + 2) div 5; Alphas[5] := (1 * Alphas[0] + 4 * Alphas[1] + 2) div 5; Alphas[6] := 0; Alphas[7] := $FF; end; end; procedure DecodeDXT5(SrcBits, DestBits: PByte; Width, Height: LongInt); var Sel, X, Y, I, J, K: LongInt; Block: TDXTColorBlock; AlphaBlock: TDXTAlphaBlockInt; Colors: array[0..3] of TColor32Rec; AMask: array[0..1] of LongWord; begin for Y := 0 to Height div 4 - 1 do for X := 0 to Width div 4 - 1 do begin AlphaBlock := PDXTAlphaBlockInt(SrcBits)^; Inc(SrcBits, SizeOf(AlphaBlock)); Block := PDXTColorBlock(SrcBits)^; Inc(SrcBits, SizeOf(Block)); // we read and decode endpoint colors Colors[0] := DecodeCol(Block.Color0); Colors[1] := DecodeCol(Block.Color1); // and interpolate between them Colors[2].R := (Colors[0].R shl 1 + Colors[1].R + 1) div 3; Colors[2].G := (Colors[0].G shl 1 + Colors[1].G + 1) div 3; Colors[2].B := (Colors[0].B shl 1 + Colors[1].B + 1) div 3; Colors[3].R := (Colors[0].R + Colors[1].R shl 1 + 1) div 3; Colors[3].G := (Colors[0].G + Colors[1].G shl 1 + 1) div 3; Colors[3].B := (Colors[0].B + Colors[1].B shl 1 + 1) div 3; // 6 bit alpha mask is copied into two long words for // easier usage AMask[0] := PLongWord(@AlphaBlock.Alphas[2])^ and $00FFFFFF; AMask[1] := PLongWord(@AlphaBlock.Alphas[5])^ and $00FFFFFF; // alpha interpolation between two endpoint alphas GetInterpolatedAlphas(AlphaBlock); // we distribute the dxt block colors and alphas // across the 4x4 block of the destination image // accroding to the dxt block mask and alpha block mask K := 0; for J := 0 to 3 do for I := 0 to 3 do begin Sel := (Block.Mask and (3 shl (K shl 1))) shr (K shl 1); if ((X shl 2 + I) < Width) and ((Y shl 2 + J) < Height) then begin Colors[Sel].A := AlphaBlock.Alphas[AMask[J shr 1] and 7]; PPalette32(DestBits)[(Y shl 2 + J) * Width + (X shl 2 + I)] := Colors[Sel]; end; Inc(K); AMask[J shr 1] := AMask[J shr 1] shr 3; end; end; end; procedure GetBlock(var Block: TPixelBlock; SrcBits: Pointer; XPos, YPos, Width, Height: LongInt); var X, Y, I: LongInt; Src: PColor32Rec; begin I := 0; // 4x4 pixel block is filled with information about every // pixel in the block: alpha, original color, 565 color for Y := 0 to 3 do for X := 0 to 3 do begin Src := @PPalette32(SrcBits)[(YPos shl 2 + Y) * Width + XPos shl 2 + X]; Block[I].Color := ((Src.R shr 3) shl 11) or ((Src.G shr 2) shl 5) or (Src.B shr 3); Block[I].Alpha := Src.A; Block[I].Orig := Src^; Inc(I); end; end; function ColorDistance(const C1, C2: TColor32Rec): LongInt; {$IFDEF USE_INLINE} inline;{$ENDIF} begin Result := (C1.R - C2.R) * (C1.R - C2.R) + (C1.G - C2.G) * (C1.G - C2.G) + (C1.B - C2.B) * (C1.B - C2.B); end; procedure GetEndpoints(const Block: TPixelBlock; var Ep0, Ep1: Word); var I, J, Farthest, Dist: LongInt; Colors: array[0..15] of TColor32Rec; begin // we choose two colors from the pixel block which has the // largest distance between them for I := 0 to 15 do Colors[I] := Block[I].Orig; Farthest := -1; for I := 0 to 15 do for J := I + 1 to 15 do begin Dist := ColorDistance(Colors[I], Colors[J]); if Dist > Farthest then begin Farthest := Dist; Ep0 := Block[I].Color; Ep1 := Block[J].Color; end; end; end; procedure GetAlphaEndpoints(const Block: TPixelBlock; var Min, Max: Byte); var I: LongInt; begin Min := 255; Max := 0; // we choose the lowest and the highest alpha values for I := 0 to 15 do begin if Block[I].Alpha < Min then Min := Block[I].Alpha; if Block[I].Alpha > Max then Max := Block[I].Alpha; end; end; procedure FixEndpoints(var Ep0, Ep1: Word; HasAlpha: Boolean); var Temp: Word; begin // if dxt block has alpha information, Ep0 must be smaller // than Ep1, if the block has no alpha Ep1 must be smaller if HasAlpha then begin if Ep0 > Ep1 then begin Temp := Ep0; Ep0 := Ep1; Ep1 := Temp; end; end else if Ep0 < Ep1 then begin Temp := Ep0; Ep0 := Ep1; Ep1 := Temp; end; end; function GetColorMask(Ep0, Ep1: Word; NumCols: LongInt; const Block: TPixelBlock): LongWord; var I, J, Closest, Dist: LongInt; Colors: array[0..3] of TColor32Rec; Mask: array[0..15] of Byte; begin // we decode endpoint colors Colors[0] := DecodeCol(Ep0); Colors[1] := DecodeCol(Ep1); // and interpolate colors between (3 for DXT1 with alpha, 4 for the others) if NumCols = 3 then begin Colors[2].R := (Colors[0].R + Colors[1].R) shr 1; Colors[2].G := (Colors[0].G + Colors[1].G) shr 1; Colors[2].B := (Colors[0].B + Colors[1].B) shr 1; Colors[3].R := (Colors[0].R + Colors[1].R) shr 1; Colors[3].G := (Colors[0].G + Colors[1].G) shr 1; Colors[3].B := (Colors[0].B + Colors[1].B) shr 1; end else begin Colors[2].R := (Colors[0].R shl 1 + Colors[1].R + 1) div 3; Colors[2].G := (Colors[0].G shl 1 + Colors[1].G + 1) div 3; Colors[2].B := (Colors[0].B shl 1 + Colors[1].B + 1) div 3; Colors[3].R := (Colors[0].R + Colors[1].R shl 1 + 1) div 3; Colors[3].G := (Colors[0].G + Colors[1].G shl 1 + 1) div 3; Colors[3].B := (Colors[0].B + Colors[1].B shl 1 + 1) div 3; end; for I := 0 to 15 do begin // this is only for DXT1 with alpha if (Block[I].Alpha < 128) and (NumCols = 3) then begin Mask[I] := 3; Continue; end; // for each of the 16 input pixels the nearest color in the // 4 dxt colors is found Closest := MaxInt; for J := 0 to NumCols - 1 do begin Dist := ColorDistance(Block[I].Orig, Colors[J]); if Dist < Closest then begin Closest := Dist; Mask[I] := J; end; end; end; Result := 0; for I := 0 to 15 do Result := Result or (Mask[I] shl (I shl 1)); end; procedure GetAlphaMask(Ep0, Ep1: Byte; var Block: TPixelBlock; Mask: PByteArray); var Alphas: array[0..7] of Byte; M: array[0..15] of Byte; I, J, Closest, Dist: LongInt; begin Alphas[0] := Ep0; Alphas[1] := Ep1; // interpolation between two given alpha endpoints // (I use 6 interpolated values mode) Alphas[2] := (6 * Alphas[0] + 1 * Alphas[1] + 3) div 7; Alphas[3] := (5 * Alphas[0] + 2 * Alphas[1] + 3) div 7; Alphas[4] := (4 * Alphas[0] + 3 * Alphas[1] + 3) div 7; Alphas[5] := (3 * Alphas[0] + 4 * Alphas[1] + 3) div 7; Alphas[6] := (2 * Alphas[0] + 5 * Alphas[1] + 3) div 7; Alphas[7] := (1 * Alphas[0] + 6 * Alphas[1] + 3) div 7; // the closest interpolated values for each of the input alpha // is found for I := 0 to 15 do begin Closest := MaxInt; for J := 0 to 7 do begin Dist := Abs(Alphas[J] - Block[I].Alpha); if Dist < Closest then begin Closest := Dist; M[I] := J; end; end; end; Mask[0] := M[0] or (M[1] shl 3) or ((M[2] and 3) shl 6); Mask[1] := ((M[2] and 4) shr 2) or (M[3] shl 1) or (M[4] shl 4) or ((M[5] and 1) shl 7); Mask[2] := ((M[5] and 6) shr 1) or (M[6] shl 2) or (M[7] shl 5); Mask[3] := M[8] or (M[9] shl 3) or ((M[10] and 3) shl 6); Mask[4] := ((M[10] and 4) shr 2) or (M[11] shl 1) or (M[12] shl 4) or ((M[13] and 1) shl 7); Mask[5] := ((M[13] and 6) shr 1) or (M[14] shl 2) or (M[15] shl 5); end; procedure EncodeDXT1(SrcBits: PByte; DestBits: PByte; Width, Height: LongInt); var X, Y, I: LongInt; HasAlpha: Boolean; Block: TDXTColorBlock; Pixels: TPixelBlock; begin for Y := 0 to Height div 4 - 1 do for X := 0 to Width div 4 - 1 do begin GetBlock(Pixels, SrcBits, X, Y, Width, Height); HasAlpha := False; for I := 0 to 15 do if Pixels[I].Alpha < 128 then begin HasAlpha := True; Break; end; GetEndpoints(Pixels, Block.Color0, Block.Color1); FixEndpoints(Block.Color0, Block.Color1, HasAlpha); if HasAlpha then Block.Mask := GetColorMask(Block.Color0, Block.Color1, 3, Pixels) else Block.Mask := GetColorMask(Block.Color0, Block.Color1, 4, Pixels); PDXTColorBlock(DestBits)^ := Block; Inc(DestBits, SizeOf(Block)); end; end; procedure EncodeDXT3(SrcBits: Pointer; DestBits: PByte; Width, Height: LongInt); var X, Y, I: LongInt; Block: TDXTColorBlock; AlphaBlock: TDXTAlphaBlockExp; Pixels: TPixelBlock; begin for Y := 0 to Height div 4 - 1 do for X := 0 to Width div 4 - 1 do begin GetBlock(Pixels, SrcBits, X, Y, Width, Height); for I := 0 to 7 do PByteArray(@AlphaBlock.Alphas)[I] := (Pixels[I shl 1].Alpha shr 4) or ((Pixels[I shl 1 + 1].Alpha shr 4) shl 4); GetEndpoints(Pixels, Block.Color0, Block.Color1); FixEndpoints(Block.Color0, Block.Color1, False); Block.Mask := GetColorMask(Block.Color0, Block.Color1, 4, Pixels); PDXTAlphaBlockExp(DestBits)^ := AlphaBlock; Inc(DestBits, SizeOf(AlphaBlock)); PDXTColorBlock(DestBits)^ := Block; Inc(DestBits, SizeOf(Block)); end; end; procedure EncodeDXT5(SrcBits: Pointer; DestBits: PByte; Width, Height: LongInt); var X, Y: LongInt; Block: TDXTColorBlock; AlphaBlock: TDXTAlphaBlockInt; Pixels: TPixelBlock; begin for Y := 0 to Height div 4 - 1 do for X := 0 to Width div 4 - 1 do begin GetBlock(Pixels, SrcBits, X, Y, Width, Height); GetEndpoints(Pixels, Block.Color0, Block.Color1); FixEndpoints(Block.Color0, Block.Color1, False); Block.Mask := GetColorMask(Block.Color0, Block.Color1, 4, Pixels); GetAlphaEndPoints(Pixels, AlphaBlock.Alphas[1], AlphaBlock.Alphas[0]); GetAlphaMask(AlphaBlock.Alphas[0], AlphaBlock.Alphas[1], Pixels, PByteArray(@AlphaBlock.Alphas[2])); PDXTAlphaBlockInt(DestBits)^ := AlphaBlock; Inc(DestBits, SizeOf(AlphaBlock)); PDXTColorBlock(DestBits)^ := Block; Inc(DestBits, SizeOf(Block)); end; end; type TBTCBlock = packed record MLower, MUpper: Byte; BitField: Word; end; PBTCBlock = ^TBTCBlock; procedure EncodeBTC(SrcBits: Pointer; DestBits: PByte; Width, Height: Integer); var X, Y, I, J: Integer; Block: TBTCBlock; M, MLower, MUpper, K: Integer; Pixels: array[0..15] of Byte; begin for Y := 0 to Height div 4 - 1 do for X := 0 to Width div 4 - 1 do begin M := 0; MLower := 0; MUpper := 0; FillChar(Block, SizeOf(Block), 0); K := 0; // Store 4x4 pixels and compute average, lower, and upper intensity levels for I := 0 to 3 do for J := 0 to 3 do begin Pixels[K] := PByteArray(SrcBits)[(Y shl 2 + I) * Width + X shl 2 + J]; Inc(M, Pixels[K]); Inc(K); end; M := M div 16; K := 0; // Now compute upper and lower levels, number of upper pixels, // and update bit field (1 when pixel is above avg. level M) for I := 0 to 15 do begin if Pixels[I] > M then begin Inc(MUpper, Pixels[I]); Inc(K); Block.BitField := Block.BitField or (1 shl I); end else Inc(MLower, Pixels[I]); end; // Scale levels and save them to block if K > 0 then Block.MUpper := ClampToByte(MUpper div K) else Block.MUpper := 0; Block.MLower := ClampToByte(MLower div (16 - K)); // Finally save block to dest data PBTCBlock(DestBits)^ := Block; Inc(DestBits, SizeOf(Block)); end; end; procedure GetOneChannelBlock(var Block: TPixelBlock; SrcBits: Pointer; XPos, YPos, Width, Height, BytesPP, ChannelIdx: Integer); var X, Y, I: Integer; Src: PByte; begin I := 0; // 4x4 pixel block is filled with information about every pixel in the block, // but only one channel value is stored in Alpha field for Y := 0 to 3 do for X := 0 to 3 do begin Src := @PByteArray(SrcBits)[(YPos * 4 + Y) * Width * BytesPP + (XPos * 4 + X) * BytesPP + ChannelIdx]; Block[I].Alpha := Src^; Inc(I); end; end; procedure EncodeATI1N(SrcBits: Pointer; DestBits: PByte; Width, Height: Integer); var X, Y: Integer; AlphaBlock: TDXTAlphaBlockInt; Pixels: TPixelBlock; begin for Y := 0 to Height div 4 - 1 do for X := 0 to Width div 4 - 1 do begin // Encode one channel GetOneChannelBlock(Pixels, SrcBits, X, Y, Width, Height, 1, 0); GetAlphaEndPoints(Pixels, AlphaBlock.Alphas[1], AlphaBlock.Alphas[0]); GetAlphaMask(AlphaBlock.Alphas[0], AlphaBlock.Alphas[1], Pixels, PByteArray(@AlphaBlock.Alphas[2])); PDXTAlphaBlockInt(DestBits)^ := AlphaBlock; Inc(DestBits, SizeOf(AlphaBlock)); end; end; procedure EncodeATI2N(SrcBits: Pointer; DestBits: PByte; Width, Height: Integer); var X, Y: Integer; AlphaBlock: TDXTAlphaBlockInt; Pixels: TPixelBlock; begin for Y := 0 to Height div 4 - 1 do for X := 0 to Width div 4 - 1 do begin // Encode Red/X channel GetOneChannelBlock(Pixels, SrcBits, X, Y, Width, Height, 4, ChannelRed); GetAlphaEndPoints(Pixels, AlphaBlock.Alphas[1], AlphaBlock.Alphas[0]); GetAlphaMask(AlphaBlock.Alphas[0], AlphaBlock.Alphas[1], Pixels, PByteArray(@AlphaBlock.Alphas[2])); PDXTAlphaBlockInt(DestBits)^ := AlphaBlock; Inc(DestBits, SizeOf(AlphaBlock)); // Encode Green/Y channel GetOneChannelBlock(Pixels, SrcBits, X, Y, Width, Height, 4, ChannelGreen); GetAlphaEndPoints(Pixels, AlphaBlock.Alphas[1], AlphaBlock.Alphas[0]); GetAlphaMask(AlphaBlock.Alphas[0], AlphaBlock.Alphas[1], Pixels, PByteArray(@AlphaBlock.Alphas[2])); PDXTAlphaBlockInt(DestBits)^ := AlphaBlock; Inc(DestBits, SizeOf(AlphaBlock)); end; end; procedure DecodeBTC(SrcBits, DestBits: PByte; Width, Height: Integer); var X, Y, I, J, K: Integer; Block: TBTCBlock; Dest: PByte; begin for Y := 0 to Height div 4 - 1 do for X := 0 to Width div 4 - 1 do begin Block := PBTCBlock(SrcBits)^; Inc(SrcBits, SizeOf(Block)); K := 0; // Just write MUpper when there is '1' in bit field and MLower // when there is '0' for I := 0 to 3 do for J := 0 to 3 do begin Dest := @PByteArray(DestBits)[(Y shl 2 + I) * Width + X shl 2 + J]; if Block.BitField and (1 shl K) <> 0 then Dest^ := Block.MUpper else Dest^ := Block.MLower; Inc(K); end; end; end; procedure DecodeATI1N(SrcBits, DestBits: PByte; Width, Height: Integer); var X, Y, I, J: Integer; AlphaBlock: TDXTAlphaBlockInt; AMask: array[0..1] of LongWord; begin for Y := 0 to Height div 4 - 1 do for X := 0 to Width div 4 - 1 do begin AlphaBlock := PDXTAlphaBlockInt(SrcBits)^; Inc(SrcBits, SizeOf(AlphaBlock)); // 6 bit alpha mask is copied into two long words for // easier usage AMask[0] := PLongWord(@AlphaBlock.Alphas[2])^ and $00FFFFFF; AMask[1] := PLongWord(@AlphaBlock.Alphas[5])^ and $00FFFFFF; // alpha interpolation between two endpoint alphas GetInterpolatedAlphas(AlphaBlock); // we distribute the dxt block alphas // across the 4x4 block of the destination image for J := 0 to 3 do for I := 0 to 3 do begin PByteArray(DestBits)[(Y shl 2 + J) * Width + (X shl 2 + I)] := AlphaBlock.Alphas[AMask[J shr 1] and 7]; AMask[J shr 1] := AMask[J shr 1] shr 3; end; end; end; procedure DecodeATI2N(SrcBits, DestBits: PByte; Width, Height: Integer); var X, Y, I, J: Integer; Color: TColor32Rec; AlphaBlock1, AlphaBlock2: TDXTAlphaBlockInt; AMask1: array[0..1] of LongWord; AMask2: array[0..1] of LongWord; begin for Y := 0 to Height div 4 - 1 do for X := 0 to Width div 4 - 1 do begin // Read the first alpha block and get masks AlphaBlock1 := PDXTAlphaBlockInt(SrcBits)^; Inc(SrcBits, SizeOf(AlphaBlock1)); AMask1[0] := PLongWord(@AlphaBlock1.Alphas[2])^ and $00FFFFFF; AMask1[1] := PLongWord(@AlphaBlock1.Alphas[5])^ and $00FFFFFF; // Read the secind alpha block and get masks AlphaBlock2 := PDXTAlphaBlockInt(SrcBits)^; Inc(SrcBits, SizeOf(AlphaBlock2)); AMask2[0] := PLongWord(@AlphaBlock2.Alphas[2])^ and $00FFFFFF; AMask2[1] := PLongWord(@AlphaBlock2.Alphas[5])^ and $00FFFFFF; // alpha interpolation between two endpoint alphas GetInterpolatedAlphas(AlphaBlock1); GetInterpolatedAlphas(AlphaBlock2); Color.A := $FF; Color.B := 0; // Distribute alpha block values across 4x4 pixel block, // first alpha block represents Red channel, second is Green. for J := 0 to 3 do for I := 0 to 3 do begin Color.R := AlphaBlock1.Alphas[AMask1[J shr 1] and 7]; Color.G := AlphaBlock2.Alphas[AMask2[J shr 1] and 7]; PColor32RecArray(DestBits)[(Y shl 2 + J) * Width + (X shl 2 + I)] := Color; AMask1[J shr 1] := AMask1[J shr 1] shr 3; AMask2[J shr 1] := AMask2[J shr 1] shr 3; end; end; end; procedure SpecialToUnSpecial(const SrcImage: TImageData; DestBits: Pointer; SpecialFormat: TImageFormat); begin case SpecialFormat of ifDXT1: DecodeDXT1(SrcImage.Bits, DestBits, SrcImage.Width, SrcImage.Height); ifDXT3: DecodeDXT3(SrcImage.Bits, DestBits, SrcImage.Width, SrcImage.Height); ifDXT5: DecodeDXT5(SrcImage.Bits, DestBits, SrcImage.Width, SrcImage.Height); ifBTC: DecodeBTC (SrcImage.Bits, DestBits, SrcImage.Width, SrcImage.Height); ifATI1N: DecodeATI1N(SrcImage.Bits, DestBits, SrcImage.Width, SrcImage.Height); ifATI2N: DecodeATI2N(SrcImage.Bits, DestBits, SrcImage.Width, SrcImage.Height); end; end; procedure UnSpecialToSpecial(SrcBits: Pointer; const DestImage: TImageData; SpecialFormat: TImageFormat); begin case SpecialFormat of ifDXT1: EncodeDXT1(SrcBits, DestImage.Bits, DestImage.Width, DestImage.Height); ifDXT3: EncodeDXT3(SrcBits, DestImage.Bits, DestImage.Width, DestImage.Height); ifDXT5: EncodeDXT5(SrcBits, DestImage.Bits, DestImage.Width, DestImage.Height); ifBTC: EncodeBTC (SrcBits, DestImage.Bits, DestImage.Width, DestImage.Height); ifATI1N: EncodeATI1N(SrcBits, DestImage.Bits, DestImage.Width, DestImage.Height); ifATI2N: EncodeATI2N(SrcBits, DestImage.Bits, DestImage.Width, DestImage.Height); end; end; procedure ConvertSpecial(var Image: TImageData; SrcInfo, DstInfo: PImageFormatInfo); var WorkImage: TImageData; procedure CheckSize(var Img: TImageData; Info: PImageFormatInfo); var Width, Height: Integer; begin Width := Img.Width; Height := Img.Height; DstInfo.CheckDimensions(Info.Format, Width, Height); ResizeImage(Img, Width, Height, rfNearest); end; begin if SrcInfo.IsSpecial and DstInfo.IsSpecial then begin // Convert source to nearest 'normal' format InitImage(WorkImage); NewImage(Image.Width, Image.Height, SrcInfo.SpecialNearestFormat, WorkImage); SpecialToUnSpecial(Image, WorkImage.Bits, SrcInfo.Format); FreeImage(Image); // Make sure output of SpecialToUnSpecial is the same as input of // UnSpecialToSpecial if SrcInfo.SpecialNearestFormat <> DstInfo.SpecialNearestFormat then ConvertImage(WorkImage, DstInfo.SpecialNearestFormat); // Convert work image to dest special format CheckSize(WorkImage, DstInfo); NewImage(WorkImage.Width, WorkImage.Height, DstInfo.Format, Image); UnSpecialToSpecial(WorkImage.Bits, Image, DstInfo.Format); FreeImage(WorkImage); end else if SrcInfo.IsSpecial and not DstInfo.IsSpecial then begin // Convert source to nearest 'normal' format InitImage(WorkImage); NewImage(Image.Width, Image.Height, SrcInfo.SpecialNearestFormat, WorkImage); SpecialToUnSpecial(Image, WorkImage.Bits, SrcInfo.Format); FreeImage(Image); // Now convert to dest format ConvertImage(WorkImage, DstInfo.Format); Image := WorkImage; end else if not SrcInfo.IsSpecial and DstInfo.IsSpecial then begin // Convert source to nearest format WorkImage := Image; ConvertImage(WorkImage, DstInfo.SpecialNearestFormat); // Now convert from nearest to dest CheckSize(WorkImage, DstInfo); InitImage(Image); NewImage(WorkImage.Width, WorkImage.Height, DstInfo.Format, Image); UnSpecialToSpecial(WorkImage.Bits, Image, DstInfo.Format); FreeImage(WorkImage); end; end; function GetStdPixelsSize(Format: TImageFormat; Width, Height: LongInt): LongInt; begin if FInfos[Format] <> nil then Result := Width * Height * FInfos[Format].BytesPerPixel else Result := 0; end; procedure CheckStdDimensions(Format: TImageFormat; var Width, Height: LongInt); begin end; function GetDXTPixelsSize(Format: TImageFormat; Width, Height: LongInt): LongInt; begin // DXT can be used only for images with dimensions that are // multiples of four CheckDXTDimensions(Format, Width, Height); Result := Width * Height; if Format in [ifDXT1, ifATI1N] then Result := Result div 2; end; procedure CheckDXTDimensions(Format: TImageFormat; var Width, Height: LongInt); begin // DXT image dimensions must be multiples of four Width := (Width + 3) and not 3; // div 4 * 4; Height := (Height + 3) and not 3; // div 4 * 4; end; function GetBTCPixelsSize(Format: TImageFormat; Width, Height: LongInt): LongInt; begin // BTC can be used only for images with dimensions that are // multiples of four CheckDXTDimensions(Format, Width, Height); Result := Width * Height div 4; // 2bits/pixel end; { Optimized pixel readers/writers for 32bit and FP colors to be stored in TImageFormatInfo } function GetPixel32ifA8R8G8B8(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32): TColor32Rec; begin Result.Color := PLongWord(Bits)^; end; procedure SetPixel32ifA8R8G8B8(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32; const Color: TColor32Rec); begin PLongWord(Bits)^ := Color.Color; end; function GetPixelFPifA8R8G8B8(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32): TColorFPRec; begin Result.A := PColor32Rec(Bits).A * OneDiv8Bit; Result.R := PColor32Rec(Bits).R * OneDiv8Bit; Result.G := PColor32Rec(Bits).G * OneDiv8Bit; Result.B := PColor32Rec(Bits).B * OneDiv8Bit; end; procedure SetPixelFPifA8R8G8B8(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32; const Color: TColorFPRec); begin PColor32Rec(Bits).A := ClampToByte(Round(Color.A * 255.0)); PColor32Rec(Bits).R := ClampToByte(Round(Color.R * 255.0)); PColor32Rec(Bits).G := ClampToByte(Round(Color.G * 255.0)); PColor32Rec(Bits).B := ClampToByte(Round(Color.B * 255.0)); end; function GetPixel32Channel8Bit(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32): TColor32Rec; begin case Info.Format of ifR8G8B8, ifX8R8G8B8: begin Result.A := $FF; PColor24Rec(@Result)^ := PColor24Rec(Bits)^; end; ifGray8, ifA8Gray8: begin if Info.HasAlphaChannel then Result.A := PWordRec(Bits).High else Result.A := $FF; Result.R := PWordRec(Bits).Low; Result.G := PWordRec(Bits).Low; Result.B := PWordRec(Bits).Low; end; end; end; procedure SetPixel32Channel8Bit(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32; const Color: TColor32Rec); begin case Info.Format of ifR8G8B8, ifX8R8G8B8: begin PColor24Rec(Bits)^ := PColor24Rec(@Color)^; end; ifGray8, ifA8Gray8: begin if Info.HasAlphaChannel then PWordRec(Bits).High := Color.A; PWordRec(Bits).Low := Round(GrayConv.R * Color.R + GrayConv.G * Color.G + GrayConv.B * Color.B); end; end; end; function GetPixelFPChannel8Bit(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32): TColorFPRec; begin case Info.Format of ifR8G8B8, ifX8R8G8B8: begin Result.A := 1.0; Result.R := PColor24Rec(Bits).R * OneDiv8Bit; Result.G := PColor24Rec(Bits).G * OneDiv8Bit; Result.B := PColor24Rec(Bits).B * OneDiv8Bit; end; ifGray8, ifA8Gray8: begin if Info.HasAlphaChannel then Result.A := PWordRec(Bits).High * OneDiv8Bit else Result.A := 1.0; Result.R := PWordRec(Bits).Low * OneDiv8Bit; Result.G := PWordRec(Bits).Low * OneDiv8Bit; Result.B := PWordRec(Bits).Low * OneDiv8Bit; end; end; end; procedure SetPixelFPChannel8Bit(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32; const Color: TColorFPRec); begin case Info.Format of ifR8G8B8, ifX8R8G8B8: begin PColor24Rec(Bits).R := ClampToByte(Round(Color.R * 255.0)); PColor24Rec(Bits).G := ClampToByte(Round(Color.G * 255.0)); PColor24Rec(Bits).B := ClampToByte(Round(Color.B * 255.0)); end; ifGray8, ifA8Gray8: begin if Info.HasAlphaChannel then PWordRec(Bits).High := ClampToByte(Round(Color.A * 255.0)); PWordRec(Bits).Low := ClampToByte(Round((GrayConv.R * Color.R + GrayConv.G * Color.G + GrayConv.B * Color.B) * 255.0)); end; end; end; function GetPixelFPFloat32(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32): TColorFPRec; begin case Info.Format of ifA32R32G32B32F: begin Result := PColorFPRec(Bits)^; end; ifA32B32G32R32F: begin Result := PColorFPRec(Bits)^; SwapValues(Result.R, Result.B); end; ifR32F: begin Result.A := 1.0; Result.R := PSingle(Bits)^; Result.G := 0.0; Result.B := 0.0; end; end; end; procedure SetPixelFPFloat32(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32; const Color: TColorFPRec); begin case Info.Format of ifA32R32G32B32F: begin PColorFPRec(Bits)^ := Color; end; ifA32B32G32R32F: begin PColorFPRec(Bits)^ := Color; SwapValues(PColorFPRec(Bits).R, PColorFPRec(Bits).B); end; ifR32F: begin PSingle(Bits)^ := Color.R; end; end; end; initialization // Initialize default sampling filter function pointers and radii SamplingFilterFunctions[sfNearest] := FilterNearest; SamplingFilterFunctions[sfLinear] := FilterLinear; SamplingFilterFunctions[sfCosine] := FilterCosine; SamplingFilterFunctions[sfHermite] := FilterHermite; SamplingFilterFunctions[sfQuadratic] := FilterQuadratic; SamplingFilterFunctions[sfGaussian] := FilterGaussian; SamplingFilterFunctions[sfSpline] := FilterSpline; SamplingFilterFunctions[sfLanczos] := FilterLanczos; SamplingFilterFunctions[sfMitchell] := FilterMitchell; SamplingFilterFunctions[sfCatmullRom] := FilterCatmullRom; SamplingFilterRadii[sfNearest] := 1.0; SamplingFilterRadii[sfLinear] := 1.0; SamplingFilterRadii[sfCosine] := 1.0; SamplingFilterRadii[sfHermite] := 1.0; SamplingFilterRadii[sfQuadratic] := 1.5; SamplingFilterRadii[sfGaussian] := 1.25; SamplingFilterRadii[sfSpline] := 2.0; SamplingFilterRadii[sfLanczos] := 3.0; SamplingFilterRadii[sfMitchell] := 2.0; SamplingFilterRadii[sfCatmullRom] := 2.0; { File Notes: -- TODOS ---------------------------------------------------- - nothing now -- 0.26.3 Changes/Bug Fixes ----------------------------------- - Filtered resampling ~10% faster now. - Fixed DXT3 alpha encoding. - ifIndex8 format now has HasAlphaChannel=True. -- 0.25.0 Changes/Bug Fixes ----------------------------------- - Made some resampling stuff public so that it can be used in canvas class. - Added some color constructors. - Added VisualizePalette helper function. - Fixed ConvertSpecial, not very readable before and error when converting special->special. -- 0.24.3 Changes/Bug Fixes ----------------------------------- - Some refactorings a changes to DXT based formats. - Added ifATI1N and ifATI2N image data formats support structures and functions. -- 0.23 Changes/Bug Fixes ----------------------------------- - Added ifBTC image format support structures and functions. -- 0.21 Changes/Bug Fixes ----------------------------------- - FillMipMapLevel now works well with indexed and special formats too. - Moved Convert1To8 and Convert4To8 functions from ImagingBitmaps here and created new Convert2To8 function. They are now used by more than one file format loader. -- 0.19 Changes/Bug Fixes ----------------------------------- - StretchResample now uses pixel get/set functions stored in TImageFormatInfo so it is much faster for formats that override them with optimized ones - added pixel set/get functions optimized for various image formats (to be stored in TImageFormatInfo) - bug in ConvertSpecial caused problems when converting DXTC images to bitmaps in ImagingCoponents - bug in StretchRect caused that it didn't work with ifR32F and ifR16F formats - removed leftover code in FillMipMapLevel which disabled filtered resizing of images witch ChannelSize <> 8bits - added half float converting functions and support for half based image formats where needed - added TranslatePixel and IsImageFormatValid functions - fixed possible range overflows when converting from FP to integer images - added pixel set/get functions: GetPixel32Generic, GetPixelFPGeneric, SetPixel32Generic, SetPixelFPGeneric - fixed occasional range overflows in StretchResample -- 0.17 Changes/Bug Fixes ----------------------------------- - added StretchNearest, StretchResample and some sampling functions - added ChannelCount values to TImageFormatInfo constants - added resolution validity check to GetDXTPixelsSize -- 0.15 Changes/Bug Fixes ----------------------------------- - added RBSwapFormat values to some TImageFromatInfo definitions - fixed bug in ConvertSpecial (causing DXT images to convert only to 32bit) - added CopyPixel, ComparePixels helper functions -- 0.13 Changes/Bug Fixes ----------------------------------- - replaced pixel format conversions for colors not to be darkened when converting from low bit counts - ReduceColorsMedianCut was updated to support creating one optimal palette for more images and it is somewhat faster now too - there was ugly bug in DXTC dimensions checking } end.