{
  Vampyre Imaging Library
  by Marek Mauder
  https://github.com/galfar/imaginglib
  https://imaginglib.sourceforge.io
  - - - - -
  This Source Code Form is subject to the terms of the Mozilla Public
  License, v. 2.0. If a copy of the MPL was not distributed with this
  file, You can obtain one at https://mozilla.org/MPL/2.0.
} 

{ 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}
{ Helper function that converts pixel in any format to 32bit ARGB pixel.
  For common formats it's faster than calling GetPixel32 etc.}
procedure ConvertToPixel32(SrcPix: PByte; DestPix: PColor32Rec;
  const SrcInfo: TImageFormatInfo; SrcPalette: PPalette32 = nil); {$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. Used mostly by file loaders for formats
  supporting 1bit images. Scaling of pixel values to 8bits is optional
  (indexed formats don't need this).}
procedure Convert1To8(DataIn, DataOut: PByte; Width, Height,
  WidthBytes: LongInt; ScaleTo8Bits: Boolean);
{ Converts 2bit image data to 8bit. Used mostly by file loaders for formats
  supporting 2bit images. Scaling of pixel values to 8bits is optional
  (indexed formats don't need this).}
procedure Convert2To8(DataIn, DataOut: PByte; Width, Height,
  WidthBytes: LongInt; ScaleTo8Bits: Boolean);
{ Converts 4bit image data to 8bit. Used mostly by file loaders for formats
  supporting 4bit images. Scaling of pixel values to 8bits is optional
  (indexed formats don't need this).}
procedure Convert4To8(DataIn, DataOut: PByte; Width, Height,
  WidthBytes: LongInt; ScaleTo8Bits: Boolean);

{ 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/X8R8G8B8 format.}
function Has32BitImageAlpha(NumPixels: LongInt; Data: PUInt32): Boolean;
{ Checks if there is any relevant alpha data (any entry has alpha <> 255)
  in the given palette.}
function PaletteHasAlpha(Palette: PPalette32; PaletteEntries: Integer): Boolean;
{ Checks if given palette has only grayscale entries.}
function PaletteIsGrayScale(Palette: PPalette32; PaletteEntries: Integer): 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}

{ Converts ARGB color to grayscale. }
function Color32ToGray(Color32: TColor32): Byte; {$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: UInt32);
{ Sets pixel of image in any indexed format. Index is index to the palette.}
procedure IndexSetDstPixel(Dst: PByte; DstInfo: PImageFormatInfo;
  Index: UInt32);


{ 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);

{ 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);

  // Constants 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;
{ Returns size in bytes of image in binary format (1bit image).}
function GetBinaryPixelsSize(Format: TImageFormat; Width, Height: LongInt): LongInt; forward;

function GetBCPixelsSize(Format: TImageFormat; Width, Height: LongInt): LongInt; forward;
procedure CheckBCDimensions(Format: TImageFormat; var Width, Height: 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);

  R32G32B32FInfo: TImageFormatInfo = (
    Format: ifR32G32B32F;
    Name: 'R32G32B32F';
    BytesPerPixel: 12;
    ChannelCount: 3;
    IsFloatingPoint: True;
    RBSwapFormat: ifB32G32R32F;
    GetPixelsSize: GetStdPixelsSize;
    CheckDimensions: CheckStdDimensions;
    GetPixel32: GetPixel32Generic;
    GetPixelFP: GetPixelFPFloat32;
    SetPixel32: SetPixel32Generic;
    SetPixelFP: SetPixelFPFloat32);

  B32G32R32FInfo: TImageFormatInfo = (
    Format: ifB32G32R32F;
    Name: 'B32G32R32F';
    BytesPerPixel: 12;
    ChannelCount: 3;
    IsFloatingPoint: True;
    IsRBSwapped: True;
    RBSwapFormat: ifR32G32B32F;
    GetPixelsSize: GetStdPixelsSize;
    CheckDimensions: CheckStdDimensions;
    GetPixel32: GetPixel32Generic;
    GetPixelFP: GetPixelFPFloat32;
    SetPixel32: SetPixel32Generic;
    SetPixelFP: SetPixelFPFloat32);

  // 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);

  BinaryInfo: TImageFormatInfo = (
    Format: ifBinary;
    Name: 'Binary';
    ChannelCount: 1;
    HasAlphaChannel: False;
    IsSpecial: True;
    GetPixelsSize: GetBinaryPixelsSize;
    CheckDimensions: CheckStdDimensions;
    SpecialNearestFormat: ifGray8);

  { Passtrough formats }

  {ETC1Info: TImageFormatInfo = (
    Format: ifETC1;
    Name: 'ETC1';
    ChannelCount: 3;
    HasAlphaChannel: False;
    IsSpecial: True;
    IsPassthrough: True;
    GetPixelsSize: GetBCPixelsSize;
    CheckDimensions: CheckBCDimensions;
    SpecialNearestFormat: ifR8G8B8);

  ETC2RGBInfo: TImageFormatInfo = (
    Format: ifETC2RGB;
    Name: 'ETC2RGB';
    ChannelCount: 3;
    HasAlphaChannel: False;
    IsSpecial: True;
    IsPassthrough: True;
    GetPixelsSize: GetBCPixelsSize;
    CheckDimensions: CheckBCDimensions;
    SpecialNearestFormat: ifR8G8B8);

  ETC2RGBAInfo: TImageFormatInfo = (
    Format: ifETC2RGBA;
    Name: 'ETC2RGBA';
    ChannelCount: 4;
    HasAlphaChannel: True;
    IsSpecial: True;
    IsPassthrough: True;
    GetPixelsSize: GetBCPixelsSize;
    CheckDimensions: CheckBCDimensions;
    SpecialNearestFormat: ifA8R8G8B8);

  ETC2PAInfo: TImageFormatInfo = (
    Format: ifETC2PA;
    Name: 'ETC2PA';
    ChannelCount: 4;
    HasAlphaChannel: True;
    IsSpecial: True;
    IsPassthrough: True;
    GetPixelsSize: GetBCPixelsSize;
    CheckDimensions: CheckBCDimensions;
    SpecialNearestFormat: ifA8R8G8B8);

  DXBC6Info: TImageFormatInfo = (
    Format: ifDXBC6;
    Name: 'DXBC6';
    ChannelCount: 4;
    HasAlphaChannel: True;
    IsSpecial: True;
    IsPassthrough: True;
    GetPixelsSize: GetBCPixelsSize;
    CheckDimensions: CheckBCDimensions;
    SpecialNearestFormat: ifA8R8G8B8);

  DXBC7Info: TImageFormatInfo = (
    Format: ifDXBC6;
    Name: 'DXBC7';
    ChannelCount: 4;
    HasAlphaChannel: True;
    IsSpecial: True;
    IsPassthrough: True;
    GetPixelsSize: GetBCPixelsSize;
    CheckDimensions: CheckBCDimensions;
    SpecialNearestFormat: ifA8R8G8B8);

  PVRTCInfo: TImageFormatInfo = (
    Format: ifPVRTC;
    Name: 'PVRTC';
    ChannelCount: 4;
    HasAlphaChannel: True;
    IsSpecial: True;
    IsPassthrough: True;
    GetPixelsSize: GetBCPixelsSize;
    CheckDimensions: CheckBCDimensions;
    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;
  Infos[ifR32G32B32F] := @R32G32B32FInfo;
  Infos[ifB32G32R32F] := @B32G32R32FInfo;
  // special formats
  Infos[ifDXT1] := @DXT1Info;
  Infos[ifDXT3] := @DXT3Info;
  Infos[ifDXT5] := @DXT5Info;
  Infos[ifBTC] :=  @BTCInfo;
  Infos[ifATI1N] := @ATI1NInfo;
  Infos[ifATI2N] := @ATI2NInfo;
  Infos[ifBinary] := @BinaryInfo;

  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: UInt32): TPixelFormatInfo;

  function GetBitCount(B: UInt32): UInt32;
  var
    I: UInt32;
  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: UInt32;
  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): UInt32;
{$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: UInt32): 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;

{ 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;

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)^ := PUInt32Array(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;

{ Mitchell 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: Single;
begin
  Result := nil;
  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);
      for J := Left to Right do
      begin
        Weight := Filter((Center - J) * Scale) * Scale;
        if Weight <> 0.0 then
        begin
          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;
    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);
      for J := Left to Right do
      begin
        Weight := Filter(Center - J);
        if Weight <> 0.0 then
        begin
          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;
    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);
var
  MapX, MapY: TMappingTable;
  I, J, X, Y: LongInt;
  XMinimum, XMaximum: LongInt;
  LineBufferFP: array of TColorFPRec;
  ClusterX, ClusterY: TCluster;
  Weight, AccumA, AccumR, AccumG, AccumB: Single;
  DstLine: PByte;
  SrcFloat: TColorFPRec;
  Info: TImageFormatInfo;
  BytesPerChannel: Integer;
begin
  GetImageFormatInfo(SrcImage.Format, Info);
  Assert(SrcImage.Format = DstImage.Format);
  Assert(not Info.IsSpecial and not Info.IsIndexed);
  BytesPerChannel := Info.BytesPerPixel div Info.ChannelCount;

  // 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;

  try
    // Find min and max X coords of pixels that will contribute to target image
    FindExtremes(MapX, XMinimum, XMaximum);
    SetLength(LineBufferFP, XMaximum - XMinimum + 1);

    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);
          AccumA := AccumA + SrcFloat.A * Weight;
          AccumR := AccumR + SrcFloat.R * Weight;
          AccumG := AccumG + SrcFloat.G * Weight;
          AccumB := AccumB + SrcFloat.B * 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 target 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
            AccumA := AccumA + A * Weight;
            AccumR := AccumR + R * Weight;
            AccumG := AccumG + G * Weight;
            AccumB := AccumB + B * 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;

  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: PUInt32(Dest)^ := PUInt32(Src)^;
    6: PColor48Rec(Dest)^ := PColor48Rec(Src)^;
    8: PInt64(Dest)^ := PInt64(Src)^;
    12: PColor96FPRec(Dest)^ := PColor96FPRec(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 := PUInt32(PixelA)^ = PUInt32(PixelB)^;
    6: Result := (PUInt32(PixelA)^ = PUInt32(PixelB)^) and (PColor48Rec(PixelA).R = PColor48Rec(PixelB).R);
    8: Result := PInt64(PixelA)^ = PInt64(PixelB)^;
    12: Result := (PFloatHelper(PixelA).Data = PFloatHelper(PixelB).Data) and
          (PFloatHelper(PixelA).Data32 = PFloatHelper(PixelB).Data32);
    16: Result := (PFloatHelper(PixelA).Data = PFloatHelper(PixelB).Data) and
          (PFloatHelper(PixelA).Data64 = PFloatHelper(PixelB).Data64);
  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 ConvertToPixel32(SrcPix: PByte; DestPix: PColor32Rec;
  const SrcInfo: TImageFormatInfo; SrcPalette: PPalette32);
begin
  case SrcInfo.Format of
    ifIndex8:
      begin
        DestPix^ := SrcPalette[SrcPix^];
      end;
    ifGray8:
      begin
        DestPix.R := SrcPix^;
        DestPix.G := SrcPix^;
        DestPix.B := SrcPix^;
        DestPix.A := 255;
      end;
    ifA8Gray8:
      begin
        DestPix.R := SrcPix^;
        DestPix.G := SrcPix^;
        DestPix.B := SrcPix^;
        DestPix.A := PWordRec(SrcPix).High;
      end;
    ifGray16:
      begin
        DestPix.R := PWord(SrcPix)^ shr 8;
        DestPix.G := DestPix.R;
        DestPix.B := DestPix.R;
        DestPix.A := 255;
      end;
    ifR8G8B8:
      begin
        DestPix.Color24Rec := PColor24Rec(SrcPix)^;
        DestPix.A := 255;
      end;
    ifA8R8G8B8:
      begin
        DestPix^ := PColor32Rec(SrcPix)^;
      end;
    ifR16G16B16:
      begin
        DestPix.R := PColor48Rec(SrcPix).R shr 8;
        DestPix.G := PColor48Rec(SrcPix).G shr 8;
        DestPix.B := PColor48Rec(SrcPix).B shr 8;
        DestPix.A := 255;
      end;
    ifA16R16G16B16:
      begin
        DestPix.R := PColor64Rec(SrcPix).R shr 8;
        DestPix.G := PColor64Rec(SrcPix).G shr 8;
        DestPix.B := PColor64Rec(SrcPix).B shr 8;
        DestPix.A := PColor64Rec(SrcPix).A shr 8;
      end;
  else
    DestPix^ := SrcInfo.GetPixel32(SrcPix, @SrcInfo, SrcPalette);
  end;
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: PByte; Width, Height,
  WidthBytes: LongInt; ScaleTo8Bits: Boolean);
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);
  Scaling: Byte = 255;
var
  X, Y: LongInt;
  InArray: PByteArray absolute DataIn;
begin
  for Y := 0 to Height - 1 do
    for X := 0 to Width - 1 do
    begin
      DataOut^ := (InArray[Y * WidthBytes + X shr 3] and Mask1[X and 7]) shr Shift1[X and 7];
      if ScaleTo8Bits then
        DataOut^ := DataOut^ * Scaling;
      Inc(DataOut);
    end;
end;

procedure Convert2To8(DataIn, DataOut: PByte; Width, Height,
  WidthBytes: LongInt; ScaleTo8Bits: Boolean);
const
  Mask2: array[0..3] of Byte = ($C0, $30, $0C, $03);
  Shift2: array[0..3] of Byte = (6, 4, 2, 0);
  Scaling: Byte = 85;
var
  X, Y: LongInt;
  InArray: PByteArray absolute DataIn;
begin
  for Y := 0 to Height - 1 do
    for X := 0 to Width - 1 do
    begin
      DataOut^ := (InArray[Y * WidthBytes + X shr 2] and Mask2[X and 3]) shr Shift2[X and 3];
      if ScaleTo8Bits then
        DataOut^ := DataOut^ * Scaling;
      Inc(DataOut);
    end;
end;

procedure Convert4To8(DataIn, DataOut: PByte; Width, Height,
  WidthBytes: LongInt; ScaleTo8Bits: Boolean);
const
  Mask4: array[0..1] of Byte = ($F0, $0F);
  Shift4: array[0..1] of Byte = (4, 0);
  Scaling: Byte = 17;
var
  X, Y: LongInt;
  InArray: PByteArray absolute DataIn;
begin
  for Y := 0 to Height - 1 do
    for X := 0 to Width - 1 do
    begin
      DataOut^ := (InArray[Y * WidthBytes + X shr 1] and  Mask4[X and 1]) shr Shift4[X and 1];
      if ScaleTo8Bits then
        DataOut^ := DataOut^ * Scaling;
      Inc(DataOut);
    end;
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: PUInt32): 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 PaletteHasAlpha(Palette: PPalette32; PaletteEntries: Integer): Boolean;
var
  I: Integer;
begin
  for I := 0 to PaletteEntries - 1 do
  begin
    if Palette[I].A <> 255 then
    begin
      Result := True;
      Exit;
    end;
  end;
  Result := False;
end;

function PaletteIsGrayScale(Palette: PPalette32; PaletteEntries: Integer): Boolean;
var
  I: Integer;
begin
  for I := 0 to PaletteEntries - 1 do
  begin
    if (Palette[I].R <> Palette[I].G) or (Palette[I].R <> Palette[I].B)  then
    begin
      Result := False;
      Exit;
    end;
  end;
  Result := True;
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: UInt32;
  Exp: Int32;
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 (UInt32(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 (UInt32(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: UInt32;
  Sign, Exp, Mantissa: Int32;
begin
  Src := PUInt32(@Float)^;
  // Extract sign, exponent, and mantissa from Single number
  Sign := Src shr 31;
  Exp := Int32((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 denormalized 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;

function Color32ToGray(Color32: TColor32): Byte;
begin
  Result := Round(GrayConv.R * TColor32Rec(Color32).R +
                  GrayConv.G * TColor32Rec(Color32).G +
                  GrayConv.B * TColor32Rec(Color32).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 PUInt32Rec(Src)^ do
        begin
          Alpha := High;
          Gray.A := Low;
        end
      else
        with PUInt32Rec(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 PUInt32Rec(Dst)^ do
        begin
          High := Alpha;
          Low := Gray.A;
        end
      else
        with PUInt32Rec(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
  Assert(SrcInfo.BytesPerPixel in [2, 4, 8, 12, 16]);

  if SrcInfo.BytesPerPixel in [4, 12, 16] then
  begin
    // IEEE 754 single-precision channels
    FillChar(Pix, SizeOf(Pix), 0);
    case SrcInfo.BytesPerPixel of
      4: Pix.R := PSingle(Src)^;
      12: Pix.Color96Rec := PColor96FPRec(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
  Assert(DstInfo.BytesPerPixel in [2, 4, 8, 12, 16]);

  PixW := Pix;
  if DstInfo.IsRBSwapped then
    SwapValues(PixW.R, PixW.B);

  if DstInfo.BytesPerPixel in [4, 12, 16] then
  begin
    case DstInfo.BytesPerPixel of
      4:  PSingle(Dst)^ := PixW.R;
      12: PColor96FPRec(Dst)^:= PixW.Color96Rec;
      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: UInt32);
begin
  case SrcInfo.BytesPerPixel of
    1: Index := Src^;
  end;
end;

procedure IndexSetDstPixel(Dst: PByte; DstInfo: PImageFormatInfo;
  Index: UInt32);
begin
  case DstInfo.BytesPerPixel of
    1: Dst^ := Byte(Index);
    2: PWord(Dst)^ := Word(Index);
    4: PUInt32(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: UInt32;
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: UInt32;
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: UInt32;
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: UInt32;
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
  begin
    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;
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: UInt32;
  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: UInt32;
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: UInt32;
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: UInt32;
  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: UInt32;
  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 according 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
      // according 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 UInt32;
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] := PUInt32(@AlphaBlock.Alphas[2])^ and $00FFFFFF;
      AMask[1] := PUInt32(@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): UInt32;
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 EncodeBinary(SrcBits: Pointer; DestBits: PByte; Width, Height: Integer);
var
  Src: PByte absolute SrcBits;
  Bitmap: PByteArray absolute DestBits;
  X, Y, WidthBytes: Integer;
  PixelThresholded, Threshold: Byte;
begin
  Threshold := ClampToByte(GetOption(ImagingBinaryThreshold));
  WidthBytes := (Width + 7) div 8;

  for Y := 0 to Height - 1 do
    for X := 0 to Width - 1 do
    begin
      if Src^ > Threshold then
        PixelThresholded := 255
      else
        PixelThresholded := 0;

      Bitmap[Y * WidthBytes + X div 8] := Bitmap[Y * WidthBytes + X div 8] or // OR current value of byte with following:
        (PixelThresholded and 1)  // To make 1 from 255, 0 remains 0
        shl (7 - (X mod 8));  // Put current bit to proper place in byte

      Inc(Src);
    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 UInt32;
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] := PUInt32(@AlphaBlock.Alphas[2])^ and $00FFFFFF;
      AMask[1] := PUInt32(@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 UInt32;
  AMask2: array[0..1] of UInt32;
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] := PUInt32(@AlphaBlock1.Alphas[2])^ and $00FFFFFF;
      AMask1[1] := PUInt32(@AlphaBlock1.Alphas[5])^ and $00FFFFFF;
      // Read the secind alpha block and get masks
      AlphaBlock2 := PDXTAlphaBlockInt(SrcBits)^;
      Inc(SrcBits, SizeOf(AlphaBlock2));
      AMask2[0] := PUInt32(@AlphaBlock2.Alphas[2])^ and $00FFFFFF;
      AMask2[1] := PUInt32(@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 DecodeBinary(SrcBits, DestBits: PByte; Width, Height: Integer); {$IFDEF USE_INLINE}inline;{$ENDIF}
begin
  Convert1To8(SrcBits, DestBits, Width, Height, (Width + 7) div 8, True);
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);
    ifBinary: DecodeBinary(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);
    ifBinary: EncodeBinary(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: LongInt;
  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;

function GetBCPixelsSize(Format: TImageFormat; Width, Height: LongInt): LongInt;
begin
  raise ENotImplemented.Create();
end;

procedure CheckBCDimensions(Format: TImageFormat; var Width, Height: LongInt);
begin
  raise ENotImplemented.Create();
end;

function GetBinaryPixelsSize(Format: TImageFormat; Width, Height: LongInt): LongInt;
begin
  // Binary images are aligned on BYTE boundary
  Result := ((Width + 7) div 8) * Height; // 1bit/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 := PUInt32(Bits)^;
end;

procedure SetPixel32ifA8R8G8B8(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32; const Color: TColor32Rec);
begin
  PUInt32(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, ifA32B32G32R32F:
      begin
        Result := PColorFPRec(Bits)^;
      end;
    ifR32G32B32F, ifB32G32R32F:
      begin
        Result.A := 1.0;
        Result.Color96Rec := PColor96FPRec(Bits)^;
      end;
    ifR32F:
      begin
        Result.A := 1.0;
        Result.R := PSingle(Bits)^;
        Result.G := 0.0;
        Result.B := 0.0;
      end;
  end;
  if Info.IsRBSwapped then
    SwapValues(Result.R, Result.B);
end;

procedure SetPixelFPFloat32(Bits: Pointer; Info: PImageFormatInfo; Palette: PPalette32; const Color: TColorFPRec);
begin
  case Info.Format of
    ifA32R32G32B32F, ifA32B32G32R32F:
      begin
        PColorFPRec(Bits)^ := Color;
      end;
    ifR32G32B32F, ifB32G32R32F:
      begin
        PColor96FPRec(Bits)^ := Color.Color96Rec;
      end;
    ifR32F:
      begin
        PSingle(Bits)^ := Color.R;
      end;
  end;
  if Info.IsRBSwapped then
    SwapValues(PColor96FPRec(Bits).R, PColor96FPRec(Bits).B);
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.80 -------------------------------------------------------
    - Added PaletteIsGrayScale and Color32ToGray functions.

  -- 0.77 Changes/Bug Fixes -------------------------------------
    - NOT YET: Added support for Passthrough image data formats.
    - Added ConvertToPixel32 helper function.

  -- 0.26.5 Changes/Bug Fixes -----------------------------------
    - Removed optimized codepath for few data formats from StretchResample
      function. It was quite buggy and not so much faster anyway.
    - Added PaletteHasAlpha function.
    - Added support functions for ifBinary data format.
    - Added optional pixel scaling to Convert1To8, Convert2To8,
      abd Convert4To8 functions.

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

