Files
HeidiSQL/components/graphicex/GraphicEx.pas

8165 lines
290 KiB
ObjectPascal
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

unit GraphicEx;
{$TYPEDADDRESS OFF}
// The contents of this file are subject to the Mozilla Public License
// Version 1.1 (the "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
// specific language governing rights and limitations under the License.
//
// The original code is GraphicColor.pas, released November 1, 1999.
//
// The initial developer of the original code is Dipl. Ing. Mike Lischke (Pleißa, Germany, www.delphi-gems.com),
//
// Portions created by Dipl. Ing. Mike Lischke are Copyright
// (C) 1999-2003 Dipl. Ing. Mike Lischke. All Rights Reserved.
//----------------------------------------------------------------------------------------------------------------------
//
// GraphicEx -
// This unit is an addendum to Graphics.pas, in order to enable your application
// to import many common graphic files.
//
// See help file for a description of supported image types. Additionally, there is a resample routine
// (Stretch) based on code from Anders Melander (http://www.melander.dk/delphi/resampler/index.html)
// which has been optimized quite a lot to work faster and bug fixed.
//
// version - 9.9
//
// 03-SEP-2000 ml:
// EPS with alpha channel, workaround for TIFs with wrong alpha channel indication,
// workaround for bad packbits compressed (TIF) images
// 28-AUG-2000 ml:
// small bugfixes
// 27-AUG-2000 ml:
// changed all FreeMemory(P) calls back to ... if Assigned(P) then FreeMem(P); ...
// 24-AUG-2000 ml:
// small bug in LZ77 decoder removed
// 18-AUG-2000 ml:
// TIF deflate decoding scheme
// 15-AUG-2000 ml:
// workaround for TIF images without compression, but prediction scheme set (which is not really used in this case)
// 12-AUG-2000 ml:
// small changes
// 16-SEP-2008 sz:
// version for D2009
// 28-SEP-2008 sz:
// fixed memoryleak in GetGraphicFilter
// 02-NOV-2008 sz:
// restored PCD format for Delphi 2009
//
//
//
// For older history please look into the help file.
//
// Note: The library provides usually only load support for the listed image formats but will perhaps be enhanced
// in the future to save those types too. It can be compiled with Delphi 4 or newer versions.
//
//----------------------------------------------------------------------------------------------------------------------
interface
{$I GraphicConfiguration.inc}
uses
Windows, Classes, ExtCtrls, Graphics, SysUtils, JPEG,
GraphicCompression, GraphicStrings, GraphicColor;
type
TCardinalArray = array of Cardinal;
TByteArray = array of Byte;
TFloatArray = array of Single;
TImageOptions = set of (
ioTiled, // image consists of tiles not strips (TIF)
ioBigEndian, // byte order in values >= words is reversed (TIF, RLA, SGI)
ioMinIsWhite, // minimum value in grayscale palette is white not black (TIF)
ioReversed, // bit order in bytes is reveresed (TIF)
ioUseGamma // gamma correction is used
);
// describes the compression used in the image file
TCompressionType = (
ctUnknown, // compression type is unknown
ctNone, // no compression at all
ctRLE, // run length encoding
ctPackedBits, // Macintosh packed bits
ctLZW, // Lempel-Zif-Welch
ctFax3, // CCITT T.4 (1d), also known as fax group 3
ctFaxRLE, // modified Huffman (CCITT T.4 derivative)
ctFax4, // CCITT T.6, also known as fax group 4
ctFaxRLEW, // CCITT T.4 with word alignment
ctLZ77, // Hufman inflate/deflate
ctJPEG, // TIF JPEG compression (new version)
ctOJPEG, // TIF JPEG compression (old version)
ctThunderscan, // TIF thunderscan compression
ctNext,
ctIT8CTPAD,
ctIT8LW,
ctIT8MP,
ctIT8BL,
ctPixarFilm,
ctPixarLog,
ctDCS,
ctJBIG,
ctPCDHuffmann // PhotoCD Hufman compression
);
// properties of a particular image which are set while loading an image or when
// they are explicitly requested via ReadImageProperties
PImageProperties = ^TImageProperties;
TImageProperties = record
Version: Cardinal; // TIF, PSP, GIF
Options: TImageOptions; // all images
Width, // all images
Height: Cardinal; // all images
ColorScheme: TColorScheme; // all images
BitsPerSample, // all Images
SamplesPerPixel, // all images
BitsPerPixel: Byte; // all images
Compression: TCompressionType; // all images
FileGamma: Single; // RLA, PNG
XResolution,
YResolution: Single; // given in dpi (TIF, PCX, PSP)
Interlaced, // GIF, PNG
HasAlpha: Boolean; // TIF, PNG
// informational data, used internally and/or by decoders
// TIF
FirstIFD,
PlanarConfig, // most of this data is needed in the JPG decoder
CurrentRow,
TileWidth,
TileLength,
BytesPerLine: Cardinal;
RowsPerStrip: TCardinalArray;
YCbCrSubSampling,
JPEGTables: TByteArray;
JPEGColorMode,
JPEGTablesMode: Cardinal;
CurrentStrip,
StripCount,
Predictor: Integer;
// PCD
Overview: Boolean; // true if image is an overview image
Rotate: Byte; // describes how the image is rotated (aka landscape vs. portrait image)
ImageCount: Word; // number of subimages if this is an overview image
// GIF
LocalColorTable: Boolean; // image uses an own color palette instead of the global one
// RLA
BottomUp: Boolean; // images is bottom to top
// PSD
Channels: Byte; // up to 24 channels per image
// PNG
FilterMode: Byte;
end;
// This is the general base class for all image types implemented in GraphicEx.
// It contains some generally used class/data.
TGraphicExGraphic = class(TBitmap)
private
FColorManager: TColorManager;
FImageProperties: TImageProperties;
FBasePosition: Cardinal; // stream start position
FStream: TStream; // used for local references of the stream the class is currently loading from
FProgressRect: TRect;
public
constructor Create; override;
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
class function CanLoad(const FileName: String): Boolean; overload; virtual;
class function CanLoad(Stream: TStream): Boolean; overload; virtual;
procedure LoadFromResourceName(Instance: THandle; const ResName: String);
procedure LoadFromResourceID(Instance: THandle; ResID: Integer);
function ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean; virtual;
property ColorManager: TColorManager read FColorManager;
property ImageProperties: TImageProperties read FImageProperties write FImageProperties;
end;
TGraphicExGraphicClass = class of TGraphicExGraphic;
{$ifdef SGIGraphic}
// *.bw, *.rgb, *.rgba, *.sgi images
TSGIGraphic = class(TGraphicExGraphic)
private
FRowStart,
FRowSize: TCardinalArray; // start and length of a line (if compressed)
FDecoder: TDecoder; // ...same applies here
procedure ReadAndDecode(Red, Green, Blue, Alpha: Pointer; Row, BPC: Cardinal);
public
class function CanLoad(Stream: TStream): Boolean; override;
procedure LoadFromStream(Stream: TStream); override;
function ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean; override;
end;
{$endif}
{$ifdef AutodeskGraphic}
// *.cel, *.pic images
TAutodeskGraphic = class(TGraphicExGraphic)
public
class function CanLoad(Stream: TStream): Boolean; override;
procedure LoadFromStream(Stream: TStream); override;
function ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean; override;
end;
{$endif}
{$ifdef TIFFGraphic}
// *.tif, *.tiff images
// one entry in a an IFD (image file directory)
TIFDEntry = packed record
Tag: Word;
DataType: Word;
DataLength: Cardinal;
Offset: Cardinal;
end;
TTIFFPalette = array[0..787] of Word;
TTIFFGraphic = class(TGraphicExGraphic)
private
FIFD: array of TIFDEntry; // the tags of one image file directory
FPalette: TTIFFPalette;
FYCbCrPositioning: Cardinal;
FYCbCrCoefficients: TFloatArray;
function FindTag(Tag: Cardinal; var Index: Cardinal): Boolean;
procedure GetValueList(Stream: TStream; Tag: Cardinal; var Values: TByteArray); overload;
procedure GetValueList(Stream: TStream; Tag: Cardinal; var Values: TCardinalArray); overload;
procedure GetValueList(Stream: TStream; Tag: Cardinal; var Values: TFloatArray); overload;
function GetValue(Stream: TStream; Tag: Cardinal; Default: Single = 0): Single; overload;
function GetValue(Tag: Cardinal; Default: Cardinal = 0): Cardinal; overload;
function GetValue(Tag: Cardinal; var Size: Cardinal; Default: Cardinal = 0): Cardinal; overload;
procedure SortIFD;
procedure SwapIFD;
public
class function CanLoad(Stream: TStream): Boolean; override;
procedure LoadFromStream(Stream: TStream); override;
procedure SaveToStream(Stream: TStream); override;
function ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean; override;
end;
{$ifdef EPSGraphic}
TEPSGraphic = class(TTIFFGraphic)
public
class function CanLoad(Stream: TStream): Boolean; override;
procedure LoadFromStream(Stream: TStream); override;
function ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean; override;
end;
{$endif} // EPSGraphic
{$endif} // TIFFGraphic
{$ifdef TargaGraphic}
// *.tga; *.vst; *.icb; *.vda; *.win images
TTargaGraphic = class(TGraphicExGraphic)
public
class function CanLoad(Stream: TStream): Boolean; override;
procedure LoadFromStream(Stream: TStream); override;
function ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean; override;
procedure SaveToStream(Stream: TStream); overload; override;
procedure SaveToStream(Stream: TStream; Compressed: Boolean); reintroduce; overload;
end;
{$endif}
{$ifdef PCXGraphic}
// *.pcx; *.pcc; *.scr images
// Note: Due to the badly designed format a PCX/SCR file cannot be part in a larger stream because the position of the
// color palette as well as the decoding size can only be determined by the size of the image.
// Hence the image must be the only one in the stream or the last one.
TPCXGraphic = class(TGraphicExGraphic)
public
class function CanLoad(Stream: TStream): Boolean; override;
procedure LoadFromStream(Stream: TStream); override;
function ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean; override;
end;
{$endif}
{$ifdef PCDGraphic}
// *.pcd images
// Note: By default the BASE resolution of a PCD image is loaded with LoadFromStream.
TPCDGraphic = class(TGraphicExGraphic)
public
class function CanLoad(Stream: TStream): Boolean; override;
procedure LoadFromStream(Stream: TStream); override;
function ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean; override;
class var DefaultResolution: Integer; // 2 = default; 0: 128 × 192; 1: 256 × 384; 2: 512 × 768; 3: 1024 × 1536; 4: 2048 × 3072; 5: 4096 × 6144 optional
end;
{$endif}
{$ifdef PortableMapGraphic}
// *.ppm, *.pgm, *.pbm images
TPPMGraphic = class(TGraphicExGraphic)
private
FBuffer: array[0..4095] of AnsiChar;
FIndex: Integer;
function CurrentChar: AnsiChar;
function GetChar: AnsiChar;
function GetNumber: Cardinal;
function ReadLine: AnsiString;
public
class function CanLoad(Stream: TStream): Boolean; override;
procedure LoadFromStream(Stream: TStream); override;
function ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean; override;
end;
{$endif}
{$ifdef CUTGraphic}
// *.cut (+ *.pal) images
// Note: Also this format should not be used in a stream unless it is the only image or the last one!
TCUTGraphic = class(TGraphicExGraphic)
private
FPaletteFile: String;
protected
procedure LoadPalette;
public
class function CanLoad(Stream: TStream): Boolean; override;
procedure LoadFromFile(const FileName: String); override;
procedure LoadFromStream(Stream: TStream); override;
function ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean; override;
property PaletteFile: String read FPaletteFile write FPaletteFile;
end;
{$endif}
{$ifdef GIFGraphic}
// *.gif images
TGIFGraphic = class(TGraphicExGraphic)
private
function SkipExtensions: Byte;
public
class function CanLoad(Stream: TStream): Boolean; override;
procedure LoadFromStream(Stream: TStream); override;
function ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean; override;
end;
{$endif}
{$ifdef RLAGraphic}
// *.rla, *.rpf images
// implementation based on code from Dipl. Ing. Ingo Neumann (ingo@upstart.de, ingo_n@dialup.nacamar.de)
TRLAGraphic = class(TGraphicExGraphic)
private
procedure SwapHeader(var Header); // start position of the image header in the stream
public
class function CanLoad(Stream: TStream): Boolean; override;
procedure LoadFromStream(Stream: TStream); override;
function ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean; override;
end;
{$endif}
{$ifdef PhotoshopGraphic}
// *.psd, *.pdd images
TPSDGraphic = class(TGraphicExGraphic)
public
class function CanLoad(Stream: TStream): Boolean; override;
procedure LoadFromStream(Stream: TStream); override;
function ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean; override;
end;
{$endif}
{$ifdef PaintshopProGraphic}
// *.psp images (file version 3 and 4)
TPSPGraphic = class(TGraphicExGraphic)
public
class function CanLoad(Stream: TStream): Boolean; override;
procedure LoadFromStream(Stream: TStream); override;
function ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean; override;
end;
{$endif}
{$ifdef PortableNetworkGraphic}
// *.png images
TChunkType = array[0..3] of AnsiChar;
// This header is followed by a variable number of data bytes, which are followed by the CRC for this data.
// The actual size of this data is given by field length in the chunk header.
// CRC is Cardinal (4 byte unsigned integer).
TPNGChunkHeader = packed record
Length: Cardinal; // size of data (entire chunk excluding itself, CRC and type)
case integer of
0: (ChunkType: TChunkType);
1: (Mask: DWORD);
end;
TPNGGraphic = class(TGraphicExGraphic)
private
FDecoder: TLZ77Decoder;
FIDATSize: Integer; // remaining bytes in the current IDAT chunk
FRawBuffer, // buffer to load raw chunk data and to check CRC
FCurrentSource: Pointer; // points into FRawBuffer for current position of decoding
FHeader: TPNGChunkHeader; // header of the current chunk
FCurrentCRC: Cardinal; // running CRC for the current chunk
FSourceBPP: Integer; // bits per pixel used in the file
FPalette: HPALETTE; // used to hold the palette handle until we can set it finally after the pixel format
// has been set too (as this destroys the current palette)
FTransparency: TByteArray; // If the image is indexed then this array might contain alpha values (depends on file)
// each entry corresponding to the same palette index as the index in this array.
// For grayscale and RGB images FTransparentColor contains the (only) transparent
// color.
FTransparentColor: TColor; // transparent color for gray and RGB
FBackgroundColor: TColor; // index or color ref
procedure ApplyFilter(Filter: Byte; Line, PrevLine, Target: PByte; BPP, BytesPerRow: Integer);
function IsChunk(ChunkType: TChunkType): Boolean;
function LoadAndSwapHeader: Cardinal;
procedure LoadBackgroundColor(const Description);
procedure LoadIDAT(const Description);
procedure LoadTransparency(const Description);
procedure ReadDataAndCheckCRC;
procedure ReadRow(RowBuffer: Pointer; BytesPerRow: Integer);
function SetupColorDepth(ColorType, BitDepth: Integer): Integer;
public
class function CanLoad(Stream: TStream): Boolean; override;
procedure LoadFromStream(Stream: TStream); override;
function ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean; override;
property BackgroundColor: TColor read FBackgroundColor;
property Transparency: TByteArray read FTransparency;
end;
{$endif} // PortableNetworkGraphic
// ---------- file format management stuff
TFormatType = (
ftAnimation, // format contains an animation (like GIF or AVI)
ftLayered, // format supports multiple layers (like PSP, PSD)
ftMultiImage, // format can contain more than one image (like TIF or GIF)
ftRaster, // format is contains raster data (this is mainly used)
ftVector // format contains vector data (like DXF or PSP file version 4)
);
TFormatTypes = set of TFormatType;
TFilterSortType = (
fstNone, // do not sort entries, list them as they are registered
fstBoth, // sort entries first by description then by extension
fstDescription, // sort entries by description only
fstExtension // sort entries by extension only
);
TFilterOption = (
foCompact, // use the compact form in filter strings instead listing each extension on a separate line
foIncludeAll, // include the 'All image files' filter string
foIncludeExtension // add the extension to the description
);
TFilterOptions = set of TFilterOption;
// The file format list is an alternative to Delphi's own poor implementation which does neither allow to filter
// graphic formats nor to build common entries in filter strings nor does it care for duplicate entries or
// alphabetic ordering. Additionally, some properties are maintained for each format to do searches, filter partiuclar
// formats for a certain case etc.
TFileFormatList = class
private
FClassList,
FExtensionList: TList;
protected
function FindExtension(const Extension: String): Integer;
public
constructor Create;
destructor Destroy; override;
function FindGraphicClass(GraphicClass: TGraphicClass): Integer;
procedure Clear;
function GetDescription(Graphic: TGraphicClass): String;
procedure GetExtensionList(List: TStrings);
function GetGraphicFilter(Formats: TFormatTypes; SortType: TFilterSortType; Options: TFilterOptions;
GraphicClass: TGraphicClass): String;
function GraphicFromExtension(S: String): TGraphicClass;
function GraphicFromContent(const FileName: String): TGraphicExGraphicClass; overload;
function GraphicFromContent(Stream: TStream): TGraphicExGraphicClass; overload;
procedure RegisterFileFormat(const Extension, Common, Individual: String; FormatTypes: TFormatTypes;
Replace, RegisterDefault: Boolean; GraphicClass: TGraphicClass);
procedure UnregisterFileFormat(const Extension: String; GraphicClass: TGraphicClass);
end;
// resampling support types
TResamplingFilter = (sfBox, sfTriangle, sfHermite, sfBell, sfSpline, sfLanczos3, sfMitchell);
// Resampling support routines
procedure Stretch(NewWidth, NewHeight: Cardinal; Filter: TResamplingFilter; Radius: Single; Source, Target: TBitmap); overload;
procedure Stretch(NewWidth, NewHeight: Cardinal; Filter: TResamplingFilter; Radius: Single; Source: TBitmap); overload;
var
FileFormatList: TFileFormatList;
//----------------------------------------------------------------------------------------------------------------------
implementation
uses
Consts, Math, MZLib;
type
// resampling support types
TRGBInt = record
R, G, B: Integer;
end;
PRGBWord = ^TRGBWord;
TRGBWord = record
R, G, B: Word;
end;
PRGBAWord = ^TRGBAWord;
TRGBAWord = record
R, G, B, A: Word;
end;
PBGR = ^TBGR;
TBGR = packed record
B, G, R: Byte;
end;
PBGRA = ^TBGRA;
TBGRA = packed record
B, G, R, A: Byte;
end;
PRGB = ^TRGB;
TRGB = packed record
R, G, B: Byte;
end;
PRGBA = ^TRGBA;
TRGBA = packed record
R, G, B, A: Byte;
end;
PPixelArray = ^TPixelArray;
TPixelArray = array[0..0] of TBGR;
TFilterFunction = function(Value: Single): Single;
// contributor for a Pixel
PContributor = ^TContributor;
TContributor = record
Weight: Integer; // Pixel Weight
Pixel: Integer; // Source Pixel
end;
TContributors = array of TContributor;
// list of source pixels contributing to a destination pixel
TContributorEntry = record
N: Integer;
Contributors: TContributors;
end;
TContributorList = array of TContributorEntry;
const
DefaultFilterRadius: array[TResamplingFilter] of Single = (0.5, 1, 1, 1.5, 2, 3, 2);
threadvar // globally used cache for current image (speeds up resampling about 10%)
CurrentLineR: array of Integer;
CurrentLineG: array of Integer;
CurrentLineB: array of Integer;
//----------------------------------------------------------------------------------------------------------------------
procedure GraphicExError(ErrorString: String); overload;
begin
raise EInvalidGraphic.Create(ErrorString);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure GraphicExError(ErrorString: String; Args: array of const); overload;
begin
raise EInvalidGraphic.CreateFmt(ErrorString, Args);
end;
//----------------------------------------------------------------------------------------------------------------------
// TODO : Pointer arithmetics
procedure Upsample(Width, Height, ScaledWidth: Cardinal; Pixels: PByte);
// Creates a new image that is a integral size greater than an existing one.
var
X, Y: Cardinal;
P, Q, R: PByte;
begin
for Y := 0 to Height - 1 do
begin
P := Pixels + (Height - 1 - Y) * ScaledWidth + (Width - 1);
Q := Pixels + ((Height - 1 - Y) shl 1) * ScaledWidth + ((Width - 1) shl 1);
Q^ := P^;
(Q + 1)^ := P^;
for X := 1 to Width - 1 do
begin
Dec(P);
Dec(Q, 2);
Q^ := P^;
(Q + 1)^ := Byte((Word(P^) + Word((P + 1)^) + 1) shr 1);
end;
end;
for Y := 0 to Height - 2 do
begin
P := Pixels + (Y shl 1) * ScaledWidth;
Q := P + ScaledWidth;
R := Q + ScaledWidth;
for X := 0 to Width - 2 do
begin
Q^ := Byte((Word(P^) + Word(R^) + 1) shr 1);
(Q + 1)^ := Byte((Word(P^) + Word((P + 2)^) + Word(R^) + Word((R + 2)^) + 2) shr 2);
Inc(Q, 2);
Inc(P, 2);
Inc(R, 2);
end;
Q^ := Byte((Word(P^) + Word(R^) + 1) shr 1);
Inc(P);
Inc(Q);
Q^ := Byte((Word(P^) + Word(R^) + 1) shr 1);
end;
P := Pixels + (2 * Height - 2) * ScaledWidth;
Q := Pixels + (2 * Height - 1) * ScaledWidth;
Move(P^, Q^, 2 * Width);
end;
//----------------- filter functions for stretching --------------------------------------------------------------------
function HermiteFilter(Value: Single): Single;
// f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1
begin
if Value < 0 then Value := -Value;
if Value < 1 then Result := (2 * Value - 3) * Sqr(Value) + 1
else Result := 0;
end;
//----------------------------------------------------------------------------------------------------------------------
function BoxFilter(Value: Single): Single;
// This filter is also known as 'nearest neighbour' Filter.
begin
if (Value > -0.5) and (Value <= 0.5) then Result := 1
else Result := 0;
end;
//----------------------------------------------------------------------------------------------------------------------
function TriangleFilter(Value: Single): Single;
// aka 'linear' or 'bilinear' filter
begin
if Value < 0 then Value := -Value;
if Value < 1 then Result := 1 - Value
else Result := 0;
end;
//----------------------------------------------------------------------------------------------------------------------
function BellFilter(Value: Single): Single;
begin
if Value < 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;
end;
//----------------------------------------------------------------------------------------------------------------------
function SplineFilter(Value: Single): Single;
// B-spline filter
var
Temp: Single;
begin
if Value < 0 then Value := -Value;
if Value < 1 then
begin
Temp := Sqr(Value);
Result := 0.5 * Temp * Value - Temp + 2 / 3;
end
else
if Value < 2 then
begin
Value := 2 - Value;
Result := Sqr(Value) * Value / 6;
end
else Result := 0;
end;
//----------------------------------------------------------------------------------------------------------------------
function Lanczos3Filter(Value: Single): Single;
//--------------- local function --------------------------------------------
function SinC(Value: Single): Single;
begin
if Value <> 0 then
begin
Value := Value * Pi;
Result := Sin(Value) / Value;
end
else Result := 1;
end;
//---------------------------------------------------------------------------
begin
if Value < 0 then Value := -Value;
if Value < 3 then Result := SinC(Value) * SinC(Value / 3)
else Result := 0;
end;
//----------------------------------------------------------------------------------------------------------------------
function MitchellFilter(Value: Single): Single;
const
B = 1 / 3;
C = 1 / 3;
var Temp: Single;
begin
if Value < 0 then Value := -Value;
Temp := Sqr(Value);
if Value < 1 then
begin
Value := (((12 - 9 * B - 6 * C) * (Value * Temp))
+ ((-18 + 12 * B + 6 * C) * Temp)
+ (6 - 2 * B));
Result := Value / 6;
end
else
if Value < 2 then
begin
Value := (((-B - 6 * C) * (Value * Temp))
+ ((6 * B + 30 * C) * Temp)
+ ((-12 * B - 48 * C) * Value)
+ (8 * B + 24 * C));
Result := Value / 6;
end
else Result := 0;
end;
//----------------------------------------------------------------------------------------------------------------------
const
FilterList: array[TResamplingFilter] of TFilterFunction = (
BoxFilter,
TriangleFilter,
HermiteFilter,
BellFilter,
SplineFilter,
Lanczos3Filter,
MitchellFilter
);
//----------------------------------------------------------------------------------------------------------------------
procedure FillLineChache(N, Delta: Integer; Line: Pointer);
var
I: Integer;
Run: PBGR;
begin
Run := Line;
for I := 0 to N - 1 do
begin
CurrentLineR[I] := Run.R;
CurrentLineG[I] := Run.G;
CurrentLineB[I] := Run.B;
Inc(PByte(Run), Delta);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function ApplyContributors(N: Integer; Contributors: TContributors): TBGR;
var
J: Integer;
RGB: TRGBInt;
Total,
Weight: Integer;
Pixel: Cardinal;
Contr: ^TContributor;
begin
RGB.R := 0;
RGB.G := 0;
RGB.B := 0;
Total := 0;
Contr := @Contributors[0];
for J := 0 to N - 1 do
begin
Weight := Contr.Weight;
Inc(Total, Weight);
Pixel := Contr.Pixel;
Inc(RGB.r, CurrentLineR[Pixel] * Weight);
Inc(RGB.g, CurrentLineG[Pixel] * Weight);
Inc(RGB.b, CurrentLineB[Pixel] * Weight);
Inc(Contr);
end;
if Total = 0 then
begin
Result.R := ClampByte(RGB.R shr 8);
Result.G := ClampByte(RGB.G shr 8);
Result.B := ClampByte(RGB.B shr 8);
end
else
begin
Result.R := ClampByte(RGB.R div Total);
Result.G := ClampByte(RGB.G div Total);
Result.B := ClampByte(RGB.B div Total);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure DoStretch(Filter: TFilterFunction; Radius: Single; Source, Target: TBitmap);
// This is the actual scaling routine. Target must be allocated already with sufficient size. Source must
// contain valid data, Radius must not be 0 and Filter must not be nil.
var
ScaleX,
ScaleY: Single; // Zoom scale factors
I, J,
K, N: Integer; // Loop variables
Center: Single; // Filter calculation variables
Width: Single;
Weight: Integer; // Filter calculation variables
Left,
Right: Integer; // Filter calculation variables
Work: TBitmap;
ContributorList: TContributorList;
SourceLine,
DestLine: PPixelArray;
DestPixel: PBGR;
Delta,
DestDelta: Integer;
SourceHeight,
SourceWidth,
TargetHeight,
TargetWidth: Integer;
begin
// shortcut variables
SourceHeight := Source.Height;
SourceWidth := Source.Width;
TargetHeight := Target.Height;
TargetWidth := Target.Width;
if (SourceHeight = 0) or (SourceWidth = 0) or
(TargetHeight = 0) or (TargetWidth = 0) then Exit;
// create intermediate image to hold horizontal zoom
Work := TBitmap.Create;
try
Work.PixelFormat := pf24Bit;
Work.Height := SourceHeight;
Work.Width := TargetWidth;
if SourceWidth = 1 then ScaleX := TargetWidth / SourceWidth
else ScaleX := (TargetWidth - 1) / (SourceWidth - 1);
if (SourceHeight = 1) or (TargetHeight = 1) then ScaleY := TargetHeight / SourceHeight
else ScaleY := (TargetHeight - 1) / (SourceHeight - 1);
// pre-calculate filter contributions for a row
SetLength(ContributorList, TargetWidth);
// horizontal sub-sampling
if ScaleX < 1 then
begin
// scales from bigger to smaller Width
Width := Radius / ScaleX;
for I := 0 to TargetWidth - 1 do
begin
ContributorList[I].N := 0;
SetLength(ContributorList[I].Contributors, Trunc(2 * Width + 1));
Center := I / ScaleX;
Left := Floor(Center - Width);
Right := Ceil(Center + Width);
for J := Left to Right do
begin
Weight := Round(Filter((Center - J) * ScaleX) * ScaleX * 256);
if Weight <> 0 then
begin
if J < 0 then N := -J
else
if J >= SourceWidth then N := SourceWidth - J + SourceWidth - 1
else N := J;
K := ContributorList[I].N;
Inc(ContributorList[I].N);
ContributorList[I].Contributors[K].Pixel := N;
ContributorList[I].Contributors[K].Weight := Weight;
end;
end;
end;
end
else
begin
// horizontal super-sampling
// scales from smaller to bigger Width
for I := 0 to TargetWidth - 1 do
begin
ContributorList[I].N := 0;
SetLength(ContributorList[I].Contributors, Trunc(2 * Radius + 1));
Center := I / ScaleX;
Left := Floor(Center - Radius);
Right := Ceil(Center + Radius);
for J := Left to Right do
begin
Weight := Round(Filter(Center - J) * 256);
if Weight <> 0 then
begin
if J < 0 then N := -J
else
if J >= SourceWidth then N := SourceWidth - J + SourceWidth - 1
else N := J;
K := ContributorList[I].N;
Inc(ContributorList[I].N);
ContributorList[I].Contributors[K].Pixel := N;
ContributorList[I].Contributors[K].Weight := Weight;
end;
end;
end;
end;
// now apply filter to sample horizontally from Src to Work
SetLength(CurrentLineR, SourceWidth);
SetLength(CurrentLineG, SourceWidth);
SetLength(CurrentLineB, SourceWidth);
for K := 0 to SourceHeight - 1 do
begin
SourceLine := Source.ScanLine[K];
FillLineChache(SourceWidth, 3, SourceLine);
DestPixel := Work.ScanLine[K];
for I := 0 to TargetWidth - 1 do
with ContributorList[I] do
begin
DestPixel^ := ApplyContributors(N, ContributorList[I].Contributors);
// move on to next column
Inc(DestPixel);
end;
end;
// free the memory allocated for horizontal filter weights, since we need the stucture again
for I := 0 to TargetWidth - 1 do ContributorList[I].Contributors := nil;
ContributorList := nil;
// pre-calculate filter contributions for a column
SetLength(ContributorList, TargetHeight);
// vertical sub-sampling
if ScaleY < 1 then
begin
// scales from bigger to smaller height
Width := Radius / ScaleY;
for I := 0 to TargetHeight - 1 do
begin
ContributorList[I].N := 0;
SetLength(ContributorList[I].Contributors, Trunc(2 * Width + 1));
Center := I / ScaleY;
Left := Floor(Center - Width);
Right := Ceil(Center + Width);
for J := Left to Right do
begin
Weight := Round(Filter((Center - J) * ScaleY) * ScaleY * 256);
if Weight <> 0 then
begin
if J < 0 then N := -J
else
if J >= SourceHeight then N := SourceHeight - J + SourceHeight - 1
else N := J;
K := ContributorList[I].N;
Inc(ContributorList[I].N);
ContributorList[I].Contributors[K].Pixel := N;
ContributorList[I].Contributors[K].Weight := Weight;
end;
end;
end
end
else
begin
// vertical super-sampling
// scales from smaller to bigger height
for I := 0 to TargetHeight - 1 do
begin
ContributorList[I].N := 0;
SetLength(ContributorList[I].Contributors, Trunc(2 * Radius + 1));
Center := I / ScaleY;
Left := Floor(Center - Radius);
Right := Ceil(Center + Radius);
for J := Left to Right do
begin
Weight := Round(Filter(Center - J) * 256);
if Weight <> 0 then
begin
if J < 0 then N := -J
else
if J >= SourceHeight then N := SourceHeight - J + SourceHeight - 1
else N := J;
K := ContributorList[I].N;
Inc(ContributorList[I].N);
ContributorList[I].Contributors[K].Pixel := N;
ContributorList[I].Contributors[K].Weight := Weight;
end;
end;
end;
end;
// apply filter to sample vertically from Work to Target
SetLength(CurrentLineR, SourceHeight);
SetLength(CurrentLineG, SourceHeight);
SetLength(CurrentLineB, SourceHeight);
SourceLine := Work.ScanLine[0];
Delta := Integer(Work.ScanLine[1]) - Integer(SourceLine);
DestLine := Target.ScanLine[0];
DestDelta := Integer(Target.ScanLine[1]) - Integer(DestLine);
for K := 0 to TargetWidth - 1 do
begin
DestPixel := Pointer(DestLine);
FillLineChache(SourceHeight, Delta, SourceLine);
for I := 0 to TargetHeight - 1 do
with ContributorList[I] do
begin
DestPixel^ := ApplyContributors(N, ContributorList[I].Contributors);
Inc(Integer(DestPixel), DestDelta);
end;
Inc(SourceLine);
Inc(DestLine);
end;
// free the memory allocated for vertical filter weights
for I := 0 to TargetHeight - 1 do ContributorList[I].Contributors := nil;
// this one is done automatically on exit, but is here for completeness
ContributorList := nil;
finally
Work.Free;
CurrentLineR := nil;
CurrentLineG := nil;
CurrentLineB := nil;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure Stretch(NewWidth, NewHeight: Cardinal; Filter: TResamplingFilter; Radius: Single; Source, Target: TBitmap);
// Scales the source bitmap to the given size (NewWidth, NewHeight) and stores the Result in Target.
// Filter describes the filter function to be applied and Radius the size of the filter area.
// Is Radius = 0 then the recommended filter area will be used (see DefaultFilterRadius).
begin
if Radius = 0 then Radius := DefaultFilterRadius[Filter];
Target.Handle := 0;
Target.PixelFormat := pf24Bit;
Target.Width := NewWidth;
Target.Height := NewHeight;
Source.PixelFormat := pf24Bit;
DoStretch(FilterList[Filter], Radius, Source, Target);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure Stretch(NewWidth, NewHeight: Cardinal; Filter: TResamplingFilter; Radius: Single; Source: TBitmap);
var
Target: TBitmap;
begin
if Radius = 0 then Radius := DefaultFilterRadius[Filter];
Target := TBitmap.Create;
try
Target.PixelFormat := pf24Bit;
Target.Width := NewWidth;
Target.Height := NewHeight;
Source.PixelFormat := pf24Bit;
DoStretch(FilterList[Filter], Radius, Source, Target);
Source.Assign(Target);
finally
Target.Free;
end;
end;
//----------------- support functions for image loading ----------------------------------------------------------------
procedure SwapShort(P: PWord; Count: Cardinal);
// swaps high and low byte of 16 bit values
// EAX contains P, EDX contains Count
asm
@@Loop:
MOV CX, [EAX]
XCHG CH, CL
MOV [EAX], CX
ADD EAX, 2
DEC EDX
JNZ @@Loop
end;
//----------------------------------------------------------------------------------------------------------------------
procedure SwapLong(P: PInteger; Count: Cardinal); overload;
// swaps high and low bytes of 32 bit values
// EAX contains P, EDX contains Count
asm
@@Loop:
MOV ECX, [EAX]
BSWAP ECX
MOV [EAX], ECX
ADD EAX, 4
DEC EDX
JNZ @@Loop
end;
//----------------------------------------------------------------------------------------------------------------------
function SwapLong(Value: Cardinal): Cardinal; overload;
// swaps high and low bytes of the given 32 bit value
asm
BSWAP EAX
end;
//----------------- various conversion routines ------------------------------------------------------------------------
procedure Depredict1(P: Pointer; Count: Cardinal);
// EAX contains P and EDX Count
asm
@@1:
MOV CL, [EAX]
ADD [EAX + 1], CL
INC EAX
DEC EDX
JNZ @@1
end;
//----------------------------------------------------------------------------------------------------------------------
procedure Depredict3(P: Pointer; Count: Cardinal);
// EAX contains P and EDX Count
asm
MOV ECX, EDX
SHL ECX, 1
ADD ECX, EDX // 3 * Count
@@1:
MOV DL, [EAX]
ADD [EAX + 3], DL
INC EAX
DEC ECX
JNZ @@1
end;
//----------------------------------------------------------------------------------------------------------------------
procedure Depredict4(P: Pointer; Count: Cardinal);
// EAX contains P and EDX Count
asm
SHL EDX, 2 // 4 * Count
@@1:
MOV CL, [EAX]
ADD [EAX + 4], CL
INC EAX
DEC EDX
JNZ @@1
end;
//----------------- TGraphicExGraphic ----------------------------------------------------------------------------------
constructor TGraphicExGraphic.Create;
begin
inherited;
FColorManager := TColorManager.Create;
end;
//----------------------------------------------------------------------------------------------------------------------
destructor TGraphicExGraphic.Destroy;
begin
FColorManager.Free;
inherited;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TGraphicExGraphic.Assign(Source: TPersistent);
begin
if Source is TGraphicExGraphic then FImageProperties := TGraphicExGraphic(Source).FImageProperties;
inherited;
end;
//----------------------------------------------------------------------------------------------------------------------
class function TGraphicExGraphic.CanLoad(const FileName: String): Boolean;
var
Stream: TFileStream;
begin
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Result := CanLoad(Stream);
finally
Stream.Free;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
class function TGraphicExGraphic.CanLoad(Stream: TStream): Boolean;
// Descentants have to override this method and return True if they consider the data in Stream
// as loadable by the particular class.
// Note: Make sure the stream position is the same on exit as it was on enter!
begin
Result := False;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TGraphicExGraphic.LoadFromResourceID(Instance: THandle; ResID: Integer);
var
Stream: TResourceStream;
begin
Stream := TResourceStream.CreateFromID(Instance, ResID, RT_RCDATA);
try
LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TGraphicExGraphic.LoadFromResourceName(Instance: THandle; const ResName: String);
var
Stream: TResourceStream;
begin
Stream := TResourceStream.Create(Instance, ResName, RT_RCDATA);
try
LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TGraphicExGraphic.ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean;
// Initializes the internal image properties structure.
// Descentants must override this method to fill in the actual values.
// Result is always False to show there is no image to load.
begin
Finalize(FImageProperties);
ZeroMemory(@FImageProperties, SizeOf(FImageProperties));
FImageProperties.FileGamma := 1;
Result := False;
end;
//----------------- TAutodeskGraphic -----------------------------------------------------------------------------------
{$ifdef AutodeskGraphic}
type
TAutodeskHeader = packed record
Width,
Height,
XCoord,
YCoord: Word;
Depth,
Compression: Byte;
DataSize: Cardinal;
Reserved: array[0..15] of Byte;
end;
//----------------------------------------------------------------------------------------------------------------------
class function TAutodeskGraphic.CanLoad(Stream: TStream): Boolean;
var
FileID: Word;
Header: TAutodeskHeader;
LastPosition: Cardinal;
begin
with Stream do
begin
Result := (Size - Position) > (SizeOf(FileID) + SizeOf(Header));
if Result then
begin
LastPosition := Position;
Read(FileID, SizeOf(FileID));
Result := FileID = $9119;
if Result then
begin
// read image dimensions
Read(Header, SizeOf(Header));
Result := (Header.Depth = 8) and (Header.Compression = 0);
end;
Position := LastPosition;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TAutodeskGraphic.LoadFromStream(Stream: TStream);
var
FileID: Word;
FileHeader: TAutodeskHeader;
LogPalette: TMaxLogPalette;
I: Integer;
begin
Handle := 0;
FBasePosition := Stream.Position;
if ReadImageProperties(Stream, 0) then
begin
with Stream do
begin
Position := FBasePosition;
FProgressRect := Rect(0, 0, Width, 1);
Progress(Self, psStarting, 0, False, FProgressRect, gesTransfering);
Read(FileID, 2);
// read image dimensions
Read(FileHeader, SizeOf(FileHeader));
// read palette entries and create a palette
ZeroMemory(@LogPalette, SizeOf(LogPalette));
LogPalette.palVersion := $300;
LogPalette.palNumEntries := 256;
for I := 0 to 255 do
begin
Read(LogPalette.palPalEntry[I], 3);
LogPalette.palPalEntry[I].peBlue := LogPalette.palPalEntry[I].peBlue shl 2;
LogPalette.palPalEntry[I].peGreen := LogPalette.palPalEntry[I].peGreen shl 2;
LogPalette.palPalEntry[I].peRed := LogPalette.palPalEntry[I].peRed shl 2;
end;
// setup bitmap properties
PixelFormat := pf8Bit;
Palette := CreatePalette(PLogPalette(@LogPalette)^);
Width := FileHeader.Width;
Height := FileHeader.Height;
// finally read image data
for I := 0 to Height - 1 do
begin
Read(Scanline[I]^, FileHeader.Width);
Progress(Self, psRunning, MulDiv(I, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
Progress(Self, psEnding, 0, False, FProgressRect, '');
end;
end
else GraphicExError(gesInvalidImage, ['Autodesk']);
end;
//----------------------------------------------------------------------------------------------------------------------
function TAutodeskGraphic.ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean;
var
FileID: Word;
Header: TAutodeskHeader;
begin
Result := inherited ReadImageProperties(Stream, ImageIndex);
with Stream, FImageProperties do
begin
Read(FileID, 2);
if FileID = $9119 then
begin
// read image dimensions
Read(Header, SizeOf(Header));
ColorScheme := csIndexed;
Width := Header.Width;
Height := Header.Height;
BitsPerSample := 8;
SamplesPerPixel := 1;
BitsPerPixel := 8;
Compression := ctNone;
Result := True;
end;
end;
end;
{$endif} // AutodeskGraphic
//----------------- TSGIGraphic ----------------------------------------------------------------------------------------
{$ifdef SGIGraphic}
const
SGIMagic = 474;
SGI_COMPRESSION_VERBATIM = 0;
SGI_COMPRESSION_RLE = 1;
type
TSGIHeader = packed record
Magic: SmallInt; // IRIS image file magic number
Storage, // Storage format
BPC: Byte; // Number of bytes per pixel channel (1 or 2)
Dimension: Word; // Number of dimensions
// 1 - one single scanline (and one channel) of length XSize
// 2 - two dimensional (one channel) of size XSize x YSize
// 3 - three dimensional (ZSize channels) of size XSize x YSize
XSize, // width of image
YSize, // height of image
ZSize: Word; // number of channels/planes in image (3 for RGB, 4 for RGBA etc.)
PixMin, // Minimum pixel value
PixMax: Cardinal; // Maximum pixel value
Dummy: Cardinal; // ignored
ImageName: array[0..79] of AnsiChar;
ColorMap: Integer; // Colormap ID
// 0 - default, almost all images are stored with this flag
// 1 - dithered, only one channel of data (pixels are packed), obsolete
// 2 - screen (palette) image, obsolete
// 3 - no image data, palette only, not displayable
Dummy2: array[0..403] of Byte; // ignored
end;
//----------------------------------------------------------------------------------------------------------------------
class function TSGIGraphic.CanLoad(Stream: TStream): Boolean;
// returns True if the data in Stream represents a graphic which can be loaded by this class
var
Header: TSGIHeader;
LastPosition: Cardinal;
begin
with Stream do
begin
Result := (Size - Position) > SizeOf(TSGIHeader);
if Result then
begin
LastPosition := Position;
ReadBuffer(Header, SizeOf(Header));
// one number as check is too unreliable, hence we take some more fields into the check
Result := (Swap(Header.Magic) = SGIMagic) and
(Header.BPC in [1, 2]) and
(Swap(Header.Dimension) in [1..3]);
Position := LastPosition;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TSGIGraphic.ReadAndDecode(Red, Green, Blue, Alpha: Pointer; Row, BPC: Cardinal);
var
Count: Cardinal;
RawBuffer: Pointer;
begin
with FStream, FImageProperties do
// compressed image?
if Assigned(FDecoder) then
begin
if Assigned(Red) then
begin
Position := FBasePosition + FRowStart[Row + 0 * Height];
Count := BPC * FRowSize[Row + 0 * Height];
GetMem(RawBuffer, Count);
try
Read(RawBuffer^, Count);
FDecoder.Decode(RawBuffer, Red, Count, Width);
finally
if Assigned(RawBuffer) then FreeMem(RawBuffer);
end;
end;
if Assigned(Green) then
begin
Position := FBasePosition + FRowStart[Row + 1 * Height];
Count := BPC * FRowSize[Row + 1 * Height];
GetMem(RawBuffer, Count);
try
Read(RawBuffer^, Count);
FDecoder.Decode(RawBuffer, Green, Count, Width);
finally
if Assigned(RawBuffer) then FreeMem(RawBuffer);
end;
end;
if Assigned(Blue) then
begin
Position := FBasePosition + FRowStart[Row + 2 * Height];
Count := BPC * FRowSize[Row + 2 * Height];
GetMem(RawBuffer, Count);
try
Read(RawBuffer^, Count);
FDecoder.Decode(RawBuffer, Blue, Count, Width);
finally
if Assigned(RawBuffer) then FreeMem(RawBuffer);
end;
end;
if Assigned(Alpha) then
begin
Position := FBasePosition + FRowStart[Row + 3 * Height];
Count := BPC * FRowSize[Row + 3 * Height];
GetMem(RawBuffer, Count);
try
Read(RawBuffer^, Count);
FDecoder.Decode(RawBuffer, Alpha, Count, Width);
finally
if Assigned(RawBuffer) then FreeMem(RawBuffer);
end;
end;
end
else
begin
if Assigned(Red) then
begin
Position := FBasePosition + 512 + (Row * Width);
Read(Red^, BPC * Width);
end;
if Assigned(Green) then
begin
Position := FBasePosition + 512 + (Row * Width) + (Width * Height);
Read(Green^, BPC * Width);
end;
if Assigned(Blue) then
begin
Position := FBasePosition + 512 + (Row * Width) + (2 * Width * Height);
Read(Blue^, BPC * Width);
end;
if Assigned(Alpha) then
begin
Position := FBasePosition + 512 + (Row * Width) + (3 * Width * Height);
Read(Alpha^, BPC * Width);
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TSGIGraphic.LoadFromStream(Stream: TStream);
var
Y: Cardinal;
RedBuffer,
GreenBuffer,
BlueBuffer,
AlphaBuffer: Pointer;
Header: TSGIHeader;
Count: Cardinal;
begin
// free previous image
Handle := 0;
// keep stream reference and start position for seek operations
FStream := Stream;
FBasePosition := Stream.Position;
if ReadImageProperties(Stream, 0) then
begin
with FImageProperties, Stream do
begin
FProgressRect := Rect(0, 0, Width, 1);
Progress(Self, psStarting, 0, False, FProgressRect, gesPreparing);
Stream.Position := FBasePosition;
// read header again, we need some additional information
ReadBuffer(Header, SizeOf(Header));
// SGI images are always stored in big endian style
ColorManager.SourceOptions := [coNeedByteSwap];
with Header do ColorMap := SwapLong(ColorMap);
if Compression = ctRLE then
begin
Count := Height * SamplesPerPixel;
SetLength(FRowStart, Count);
SetLength(FRowSize, Count);
// read line starts and sizes from stream
Read(FRowStart[0], Count * SizeOf(Cardinal));
SwapLong(@FRowStart[0], Count);
Read(FRowSize[0], Count * SizeOf(Cardinal));
SwapLong(@FRowSize[0], Count);
FDecoder := TSGIRLEDecoder.Create(BitsPerSample);
end
else
begin
FDecoder := nil;
end;
// set pixel format before size to avoid possibly large conversion operation
with ColorManager do
begin
SourceBitsPerSample := BitsPerSample;
TargetBitsPerSample := 8;
SourceSamplesPerPixel := SamplesPerPixel;
TargetSamplesPerPixel := SamplesPerPixel;
SourceColorScheme := ColorScheme;
case ColorScheme of
csRGBA:
TargetColorScheme := csBGRA;
csRGB:
TargetColorScheme := csBGR;
else
TargetColorScheme := csIndexed;
end;
PixelFormat := TargetPixelFormat;
end;
Self.Width := Width;
Self.Height := Height;
RedBuffer := nil;
GreenBuffer := nil;
BlueBuffer := nil;
AlphaBuffer := nil;
Progress(Self, psEnding, 100, True, FProgressRect, '');
Progress(Self, psStarting, 0, False, FProgressRect, gesTransfering);
try
Count := (BitsPerPixel div 8) * Width;
// read lines and put them into the bitmap
case ColorScheme of
csRGBA:
begin
GetMem(RedBuffer, Count);
GetMem(GreenBuffer, Count);
GetMem(BlueBuffer, Count);
GetMem(AlphaBuffer, Count);
for Y := 0 to Height - 1 do
begin
ReadAndDecode(RedBuffer, GreenBuffer, BlueBuffer, AlphaBuffer, Y, Header.BPC);
ColorManager.ConvertRow([RedBuffer, GreenBuffer, BlueBuffer, AlphaBuffer],
ScanLine[Height - Y - 1], Width, $FF);
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
csRGB:
begin
GetMem(RedBuffer, Count);
GetMem(GreenBuffer, Count);
GetMem(BlueBuffer, Count);
for Y := 0 to Height - 1 do
begin
ReadAndDecode(RedBuffer, GreenBuffer, BlueBuffer, nil, Y, Header.BPC);
ColorManager.ConvertRow([RedBuffer, GreenBuffer, BlueBuffer],
ScanLine[Height - Y - 1], Width, $FF);
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
else
// any other format is interpreted as being 256 gray scales
Palette := ColorManager.CreateGrayscalePalette(False);
for Y := 0 to Height - 1 do
begin
ReadAndDecode(ScanLine[Height - Y - 1], nil, nil, nil, Y, Header.BPC);
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
finally
Progress(Self, psEnding, 100, True, FProgressRect, '');
if Assigned(RedBuffer) then FreeMem(RedBuffer);
if Assigned(GreenBuffer) then FreeMem(GreenBuffer);
if Assigned(BlueBuffer) then FreeMem(BlueBuffer);
if Assigned(AlphaBuffer) then FreeMem(AlphaBuffer);
FDecoder.Free;
end;
end;
end
else GraphicExError(gesInvalidImage, ['sgi, bw or rgb(a)']);
end;
//----------------------------------------------------------------------------------------------------------------------
function TSGIGraphic.ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean;
var
Header: TSGIHeader;
begin
Result := inherited ReadImageProperties(Stream, ImageIndex);
with FImageProperties do
begin
Stream.ReadBuffer(Header, SizeOf(Header));
if Swap(Header.Magic) = SGIMagic then
begin
Options := [ioBigEndian];
BitsPerSample := Header.BPC * 8;
Width := Swap(Header.XSize);
Height := Swap(Header.YSize);
SamplesPerPixel := Swap(Header.ZSize);
case SamplesPerPixel of
4:
ColorScheme := csRGBA;
3:
ColorScheme := csRGB;
else
// all other is considered as being 8 bit gray scale
ColorScheme := csIndexed;
end;
BitsPerPixel := BitsPerSample * SamplesPerPixel;
if Header.Storage = SGI_COMPRESSION_RLE then Compression := ctRLE
else Compression := ctNone;
Result := True;
end;
end;
end;
{$endif} // SGIGraphic
//----------------- TTIFFGraphic ---------------------------------------------------------------------------------------
{$ifdef TIFFGraphic}
const // TIFF tags
TIFFTAG_SUBFILETYPE = 254; // subfile data descriptor
FILETYPE_REDUCEDIMAGE = $1; // reduced resolution version
FILETYPE_PAGE = $2; // one page of many
FILETYPE_MASK = $4; // transparency mask
TIFFTAG_OSUBFILETYPE = 255; // kind of data in subfile (obsolete by revision 5.0)
OFILETYPE_IMAGE = 1; // full resolution image data
OFILETYPE_REDUCEDIMAGE = 2; // reduced size image data
OFILETYPE_PAGE = 3; // one page of many
TIFFTAG_IMAGEWIDTH = 256; // image width in pixels
TIFFTAG_IMAGELENGTH = 257; // image height in pixels
TIFFTAG_BITSPERSAMPLE = 258; // bits per channel (sample)
TIFFTAG_COMPRESSION = 259; // data compression technique
COMPRESSION_NONE = 1; // dump mode
COMPRESSION_CCITTRLE = 2; // CCITT modified Huffman RLE
COMPRESSION_CCITTFAX3 = 3; // CCITT Group 3 fax encoding
COMPRESSION_CCITTFAX4 = 4; // CCITT Group 4 fax encoding
COMPRESSION_LZW = 5; // Lempel-Ziv & Welch
COMPRESSION_OJPEG = 6; // 6.0 JPEG (old version)
COMPRESSION_JPEG = 7; // JPEG DCT compression (new version)
COMPRESSION_ADOBE_DEFLATE = 8; // new id but same as COMPRESSION_DEFLATE
COMPRESSION_NEXT = 32766; // next 2-bit RLE
COMPRESSION_CCITTRLEW = 32771; // modified Huffman with word alignment
COMPRESSION_PACKBITS = 32773; // Macintosh RLE
COMPRESSION_THUNDERSCAN = 32809; // ThunderScan RLE
// codes 32895-32898 are reserved for ANSI IT8 TIFF/IT <dkelly@etsinc.com)
COMPRESSION_IT8CTPAD = 32895; // IT8 CT w/padding
COMPRESSION_IT8LW = 32896; // IT8 Linework RLE
COMPRESSION_IT8MP = 32897; // IT8 Monochrome picture
COMPRESSION_IT8BL = 32898; // IT8 Binary line art
// compression codes 32908-32911 are reserved for Pixar
COMPRESSION_PIXARFILM = 32908; // Pixar companded 10bit LZW
COMPRESSION_PIXARLOG = 32909; // Pixar companded 11bit ZIP
COMPRESSION_DEFLATE = 32946; // Deflate compression (LZ77)
// compression code 32947 is reserved for Oceana Matrix <dev@oceana.com>
COMPRESSION_DCS = 32947; // Kodak DCS encoding
COMPRESSION_JBIG = 34661; // ISO JBIG
TIFFTAG_PHOTOMETRIC = 262; // photometric interpretation
PHOTOMETRIC_MINISWHITE = 0; // min value is white
PHOTOMETRIC_MINISBLACK = 1; // min value is black
PHOTOMETRIC_RGB = 2; // RGB color model
PHOTOMETRIC_PALETTE = 3; // color map indexed
PHOTOMETRIC_MASK = 4; // holdout mask
PHOTOMETRIC_SEPARATED = 5; // color separations
PHOTOMETRIC_YCBCR = 6; // CCIR 601
PHOTOMETRIC_CIELAB = 8; // 1976 CIE L*a*b*
TIFFTAG_THRESHHOLDING = 263; // thresholding used on data (obsolete by revision 5.0)
THRESHHOLD_BILEVEL = 1; // b&w art scan
THRESHHOLD_HALFTONE = 2; // or dithered scan
THRESHHOLD_ERRORDIFFUSE = 3; // usually floyd-steinberg
TIFFTAG_CELLWIDTH = 264; // dithering matrix width (obsolete by revision 5.0)
TIFFTAG_CELLLENGTH = 265; // dithering matrix height (obsolete by revision 5.0)
TIFFTAG_FILLORDER = 266; // data order within a Byte
FILLORDER_MSB2LSB = 1; // most significant -> least
FILLORDER_LSB2MSB = 2; // least significant -> most
TIFFTAG_DOCUMENTNAME = 269; // name of doc. image is from
TIFFTAG_IMAGEDESCRIPTION = 270; // info about image
TIFFTAG_MAKE = 271; // scanner manufacturer name
TIFFTAG_MODEL = 272; // scanner model name/number
TIFFTAG_STRIPOFFSETS = 273; // Offsets to data strips
TIFFTAG_ORIENTATION = 274; // image FOrientation (obsolete by revision 5.0)
ORIENTATION_TOPLEFT = 1; // row 0 top, col 0 lhs
ORIENTATION_TOPRIGHT = 2; // row 0 top, col 0 rhs
ORIENTATION_BOTRIGHT = 3; // row 0 bottom, col 0 rhs
ORIENTATION_BOTLEFT = 4; // row 0 bottom, col 0 lhs
ORIENTATION_LEFTTOP = 5; // row 0 lhs, col 0 top
ORIENTATION_RIGHTTOP = 6; // row 0 rhs, col 0 top
ORIENTATION_RIGHTBOT = 7; // row 0 rhs, col 0 bottom
ORIENTATION_LEFTBOT = 8; // row 0 lhs, col 0 bottom
TIFFTAG_SAMPLESPERPIXEL = 277; // samples per pixel
TIFFTAG_ROWSPERSTRIP = 278; // rows per strip of data
TIFFTAG_STRIPBYTECOUNTS = 279; // bytes counts for strips
TIFFTAG_MINSAMPLEVALUE = 280; // minimum sample value (obsolete by revision 5.0)
TIFFTAG_MAXSAMPLEVALUE = 281; // maximum sample value (obsolete by revision 5.0)
TIFFTAG_XRESOLUTION = 282; // pixels/resolution in x
TIFFTAG_YRESOLUTION = 283; // pixels/resolution in y
TIFFTAG_PLANARCONFIG = 284; // storage organization
PLANARCONFIG_CONTIG = 1; // single image plane
PLANARCONFIG_SEPARATE = 2; // separate planes of data
TIFFTAG_PAGENAME = 285; // page name image is from
TIFFTAG_XPOSITION = 286; // x page offset of image lhs
TIFFTAG_YPOSITION = 287; // y page offset of image lhs
TIFFTAG_FREEOFFSETS = 288; // byte offset to free block (obsolete by revision 5.0)
TIFFTAG_FREEBYTECOUNTS = 289; // sizes of free blocks (obsolete by revision 5.0)
TIFFTAG_GRAYRESPONSEUNIT = 290; // gray scale curve accuracy
GRAYRESPONSEUNIT_10S = 1; // tenths of a unit
GRAYRESPONSEUNIT_100S = 2; // hundredths of a unit
GRAYRESPONSEUNIT_1000S = 3; // thousandths of a unit
GRAYRESPONSEUNIT_10000S = 4; // ten-thousandths of a unit
GRAYRESPONSEUNIT_100000S = 5; // hundred-thousandths
TIFFTAG_GRAYRESPONSECURVE = 291; // gray scale response curve
TIFFTAG_GROUP3OPTIONS = 292; // 32 flag bits
GROUP3OPT_2DENCODING = $1; // 2-dimensional coding
GROUP3OPT_UNCOMPRESSED = $2; // data not compressed
GROUP3OPT_FILLBITS = $4; // fill to byte boundary
TIFFTAG_GROUP4OPTIONS = 293; // 32 flag bits
GROUP4OPT_UNCOMPRESSED = $2; // data not compressed
TIFFTAG_RESOLUTIONUNIT = 296; // units of resolutions
RESUNIT_NONE = 1; // no meaningful units
RESUNIT_INCH = 2; // english
RESUNIT_CENTIMETER = 3; // metric
TIFFTAG_PAGENUMBER = 297; // page numbers of multi-page
TIFFTAG_COLORRESPONSEUNIT = 300; // color curve accuracy
COLORRESPONSEUNIT_10S = 1; // tenths of a unit
COLORRESPONSEUNIT_100S = 2; // hundredths of a unit
COLORRESPONSEUNIT_1000S = 3; // thousandths of a unit
COLORRESPONSEUNIT_10000S = 4; // ten-thousandths of a unit
COLORRESPONSEUNIT_100000S = 5; // hundred-thousandths
TIFFTAG_TRANSFERFUNCTION = 301; // colorimetry info
TIFFTAG_SOFTWARE = 305; // name & release
TIFFTAG_DATETIME = 306; // creation date and time
TIFFTAG_ARTIST = 315; // creator of image
TIFFTAG_HOSTCOMPUTER = 316; // machine where created
TIFFTAG_PREDICTOR = 317; // prediction scheme w/ LZW
PREDICTION_NONE = 1; // no prediction scheme used before coding
PREDICTION_HORZ_DIFFERENCING = 2; // horizontal differencing prediction scheme used
TIFFTAG_WHITEPOINT = 318; // image white point
TIFFTAG_PRIMARYCHROMATICITIES = 319; // primary chromaticities
TIFFTAG_COLORMAP = 320; // RGB map for pallette image
TIFFTAG_HALFTONEHINTS = 321; // highlight+shadow info
TIFFTAG_TILEWIDTH = 322; // rows/data tile
TIFFTAG_TILELENGTH = 323; // cols/data tile
TIFFTAG_TILEOFFSETS = 324; // offsets to data tiles
TIFFTAG_TILEBYTECOUNTS = 325; // Byte counts for tiles
TIFFTAG_BADFAXLINES = 326; // lines w/ wrong pixel count
TIFFTAG_CLEANFAXDATA = 327; // regenerated line info
CLEANFAXDATA_CLEAN = 0; // no errors detected
CLEANFAXDATA_REGENERATED = 1; // receiver regenerated lines
CLEANFAXDATA_UNCLEAN = 2; // uncorrected errors exist
TIFFTAG_CONSECUTIVEBADFAXLINES = 328; // max consecutive bad lines
TIFFTAG_SUBIFD = 330; // subimage descriptors
TIFFTAG_INKSET = 332; // inks in separated image
INKSET_CMYK = 1; // cyan-magenta-yellow-black
TIFFTAG_INKNAMES = 333; // ascii names of inks
TIFFTAG_DOTRANGE = 336; // 0% and 100% dot codes
TIFFTAG_TARGETPRINTER = 337; // separation target
TIFFTAG_EXTRASAMPLES = 338; // info about extra samples
EXTRASAMPLE_UNSPECIFIED = 0; // unspecified data
EXTRASAMPLE_ASSOCALPHA = 1; // associated alpha data
EXTRASAMPLE_UNASSALPHA = 2; // unassociated alpha data
TIFFTAG_SAMPLEFORMAT = 339; // data sample format
SAMPLEFORMAT_UINT = 1; // unsigned integer data
SAMPLEFORMAT_INT = 2; // signed integer data
SAMPLEFORMAT_IEEEFP = 3; // IEEE floating point data
SAMPLEFORMAT_VOID = 4; // untyped data
TIFFTAG_SMINSAMPLEVALUE = 340; // variable MinSampleValue
TIFFTAG_SMAXSAMPLEVALUE = 341; // variable MaxSampleValue
TIFFTAG_JPEGTABLES = 347; // JPEG table stream
// Tags 512-521 are obsoleted by Technical Note #2 which specifies a revised JPEG-in-TIFF scheme.
TIFFTAG_JPEGPROC = 512; // JPEG processing algorithm
JPEGPROC_BASELINE = 1; // baseline sequential
JPEGPROC_LOSSLESS = 14; // Huffman coded lossless
TIFFTAG_JPEGIFOFFSET = 513; // Pointer to SOI marker
TIFFTAG_JPEGIFBYTECOUNT = 514; // JFIF stream length
TIFFTAG_JPEGRESTARTINTERVAL = 515; // restart interval length
TIFFTAG_JPEGLOSSLESSPREDICTORS = 517; // lossless proc predictor
TIFFTAG_JPEGPOINTTRANSFORM = 518; // lossless point transform
TIFFTAG_JPEGQTABLES = 519; // Q matrice offsets
TIFFTAG_JPEGDCTABLES = 520; // DCT table offsets
TIFFTAG_JPEGACTABLES = 521; // AC coefficient offsets
TIFFTAG_YCBCRCOEFFICIENTS = 529; // RGB -> YCbCr transform
TIFFTAG_YCBCRSUBSAMPLING = 530; // YCbCr subsampling factors
TIFFTAG_YCBCRPOSITIONING = 531; // subsample positioning
YCBCRPOSITION_CENTERED = 1; // as in PostScript Level 2
YCBCRPOSITION_COSITED = 2; // as in CCIR 601-1
TIFFTAG_REFERENCEBLACKWHITE = 532; // colorimetry info
// tags 32952-32956 are private tags registered to Island Graphics
TIFFTAG_REFPTS = 32953; // image reference points
TIFFTAG_REGIONTACKPOINT = 32954; // region-xform tack point
TIFFTAG_REGIONWARPCORNERS = 32955; // warp quadrilateral
TIFFTAG_REGIONAFFINE = 32956; // affine transformation mat
// tags 32995-32999 are private tags registered to SGI
TIFFTAG_MATTEING = 32995; // use ExtraSamples
TIFFTAG_DATATYPE = 32996; // use SampleFormat
TIFFTAG_IMAGEDEPTH = 32997; // z depth of image
TIFFTAG_TILEDEPTH = 32998; // z depth/data tile
// tags 33300-33309 are private tags registered to Pixar
//
// TIFFTAG_PIXAR_IMAGEFULLWIDTH and TIFFTAG_PIXAR_IMAGEFULLLENGTH
// are set when an image has been cropped out of a larger image.
// They reflect the size of the original uncropped image.
// The TIFFTAG_XPOSITION and TIFFTAG_YPOSITION can be used
// to determine the position of the smaller image in the larger one.
TIFFTAG_PIXAR_IMAGEFULLWIDTH = 33300; // full image size in x
TIFFTAG_PIXAR_IMAGEFULLLENGTH = 33301; // full image size in y
// tag 33405 is a private tag registered to Eastman Kodak
TIFFTAG_WRITERSERIALNUMBER = 33405; // device serial number
// tag 33432 is listed in the 6.0 spec w/ unknown ownership
TIFFTAG_COPYRIGHT = 33432; // copyright string
// 34016-34029 are reserved for ANSI IT8 TIFF/IT <dkelly@etsinc.com)
TIFFTAG_IT8SITE = 34016; // site name
TIFFTAG_IT8COLORSEQUENCE = 34017; // color seq. [RGB,CMYK,etc]
TIFFTAG_IT8HEADER = 34018; // DDES Header
TIFFTAG_IT8RASTERPADDING = 34019; // raster scanline padding
TIFFTAG_IT8BITSPERRUNLENGTH = 34020; // # of bits in short run
TIFFTAG_IT8BITSPEREXTENDEDRUNLENGTH = 34021; // # of bits in long run
TIFFTAG_IT8COLORTABLE = 34022; // LW colortable
TIFFTAG_IT8IMAGECOLORINDICATOR = 34023; // BP/BL image color switch
TIFFTAG_IT8BKGCOLORINDICATOR = 34024; // BP/BL bg color switch
TIFFTAG_IT8IMAGECOLORVALUE = 34025; // BP/BL image color value
TIFFTAG_IT8BKGCOLORVALUE = 34026; // BP/BL bg color value
TIFFTAG_IT8PIXELINTENSITYRANGE = 34027; // MP pixel intensity value
TIFFTAG_IT8TRANSPARENCYINDICATOR = 34028; // HC transparency switch
TIFFTAG_IT8COLORCHARACTERIZATION = 34029; // color character. table
// tags 34232-34236 are private tags registered to Texas Instruments
TIFFTAG_FRAMECOUNT = 34232; // Sequence Frame Count
// tag 34750 is a private tag registered to Pixel Magic
TIFFTAG_JBIGOPTIONS = 34750; // JBIG options
// tags 34908-34914 are private tags registered to SGI
TIFFTAG_FAXRECVPARAMS = 34908; // encoded class 2 ses. parms
TIFFTAG_FAXSUBADDRESS = 34909; // received SubAddr string
TIFFTAG_FAXRECVTIME = 34910; // receive time (secs)
// tag 65535 is an undefined tag used by Eastman Kodak
TIFFTAG_DCSHUESHIFTVALUES = 65535; // hue shift correction data
// The following are 'pseudo tags' that can be used to control codec-specific functionality.
// These tags are not written to file. Note that these values start at $FFFF + 1 so that they'll
// never collide with Aldus-assigned tags.
TIFFTAG_FAXMODE = 65536; // Group 3/4 format control
FAXMODE_CLASSIC = $0000; // default, include RTC
FAXMODE_NORTC = $0001; // no RTC at end of data
FAXMODE_NOEOL = $0002; // no EOL code at end of row
FAXMODE_BYTEALIGN = $0004; // Byte align row
FAXMODE_WORDALIGN = $0008; // Word align row
FAXMODE_CLASSF = FAXMODE_NORTC; // TIFF class F
TIFFTAG_JPEGQUALITY = 65537; // compression quality level
// Note: quality level is on the IJG 0-100 scale. Default value is 75
TIFFTAG_JPEGCOLORMODE = 65538; // Auto RGB<=>YCbCr convert?
JPEGCOLORMODE_RAW = $0000; // no conversion (default)
JPEGCOLORMODE_RGB = $0001; // do auto conversion
TIFFTAG_JPEGTABLESMODE = 65539; // What to put in JPEGTables
JPEGTABLESMODE_QUANT = $0001; // include quantization tbls
JPEGTABLESMODE_HUFF = $0002; // include Huffman tbls
// Note: default is JPEGTABLESMODE_QUANT or JPEGTABLESMODE_HUFF
TIFFTAG_FAXFILLFUNC = 65540; // G3/G4 fill function
TIFFTAG_PIXARLOGDATAFMT = 65549; // PixarLogCodec I/O data sz
PIXARLOGDATAFMT_8BIT = 0; // regular u_char samples
PIXARLOGDATAFMT_8BITABGR = 1; // ABGR-order u_chars
PIXARLOGDATAFMT_11BITLOG = 2; // 11-bit log-encoded (raw)
PIXARLOGDATAFMT_12BITPICIO = 3; // as per PICIO (1.0==2048)
PIXARLOGDATAFMT_16BIT = 4; // signed short samples
PIXARLOGDATAFMT_FLOAT = 5; // IEEE float samples
// 65550-65556 are allocated to Oceana Matrix <dev@oceana.com>
TIFFTAG_DCSIMAGERTYPE = 65550; // imager model & filter
DCSIMAGERMODEL_M3 = 0; // M3 chip (1280 x 1024)
DCSIMAGERMODEL_M5 = 1; // M5 chip (1536 x 1024)
DCSIMAGERMODEL_M6 = 2; // M6 chip (3072 x 2048)
DCSIMAGERFILTER_IR = 0; // infrared filter
DCSIMAGERFILTER_MONO = 1; // monochrome filter
DCSIMAGERFILTER_CFA = 2; // color filter array
DCSIMAGERFILTER_OTHER = 3; // other filter
TIFFTAG_DCSINTERPMODE = 65551; // interpolation mode
DCSINTERPMODE_NORMAL = $0; // whole image, default
DCSINTERPMODE_PREVIEW = $1; // preview of image (384x256)
TIFFTAG_DCSBALANCEARRAY = 65552; // color balance values
TIFFTAG_DCSCORRECTMATRIX = 65553; // color correction values
TIFFTAG_DCSGAMMA = 65554; // gamma value
TIFFTAG_DCSTOESHOULDERPTS = 65555; // toe & shoulder points
TIFFTAG_DCSCALIBRATIONFD = 65556; // calibration file desc
// Note: quality level is on the ZLIB 1-9 scale. Default value is -1
TIFFTAG_ZIPQUALITY = 65557; // compression quality level
TIFFTAG_PIXARLOGQUALITY = 65558; // PixarLog uses same scale
// TIFF data types
TIFF_NOTYPE = 0; // placeholder
TIFF_BYTE = 1; // 8-bit unsigned integer
TIFF_ASCII = 2; // 8-bit bytes w/ last byte null
TIFF_SHORT = 3; // 16-bit unsigned integer
TIFF_LONG = 4; // 32-bit unsigned integer
TIFF_RATIONAL = 5; // 64-bit unsigned fraction
TIFF_SBYTE = 6; // 8-bit signed integer
TIFF_UNDEFINED = 7; // 8-bit untyped data
TIFF_SSHORT = 8; // 16-bit signed integer
TIFF_SLONG = 9; // 32-bit signed integer
TIFF_SRATIONAL = 10; // 64-bit signed fraction
TIFF_FLOAT = 11; // 32-bit IEEE floating point
TIFF_DOUBLE = 12; // 64-bit IEEE floating point
TIFF_BIGENDIAN = $4D4D;
TIFF_LITTLEENDIAN = $4949;
TIFF_VERSION = 42;
type
TTIFFHeader = packed record
ByteOrder: Word;
Version: Word;
FirstIFD: Cardinal;
end;
//----------------------------------------------------------------------------------------------------------------------
class function TTIFFGraphic.CanLoad(Stream: TStream): Boolean;
var
Header: TTIFFHeader;
LastPosition: Cardinal;
begin
with Stream do
begin
Result := (Size - Position) > SizeOf(Header);
if Result then
begin
LastPosition := Position;
Stream.ReadBuffer(Header, SizeOf(Header));
Result := (Header.ByteOrder = TIFF_BIGENDIAN) or
(Header.ByteOrder = TIFF_LITTLEENDIAN);
if Result then
begin
if Header.ByteOrder = TIFF_BIGENDIAN then
begin
Header.Version := Swap(Header.Version);
Header.FirstIFD := SwapLong(Header.FirstIFD);
end;
Result := (Header.Version = TIFF_VERSION) and (Integer(Header.FirstIFD) < (Size - Integer(LastPosition)));
end;
Position := LastPosition;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TTIFFGraphic.FindTag(Tag: Cardinal; var Index: Cardinal): Boolean;
// looks through the currently loaded IFD for the entry indicated by Tag;
// returns True and the index of the entry in Index if the entry is there
// otherwise the result is False and Index undefined
// Note: The IFD is sorted so we can use a binary search here.
var
L, H, I, C: Integer;
begin
Result := False;
L := 0;
H := High(FIFD);
while L <= H do
begin
I := (L + H) shr 1;
C := Integer(FIFD[I].Tag) - Integer(Tag);
if C < 0 then L := I + 1
else
begin
H := I - 1;
if C = 0 then
begin
Result := True;
L := I;
end;
end;
end;
Index := L;
end;
//----------------------------------------------------------------------------------------------------------------------
const
DataTypeToSize: array[TIFF_NOTYPE..TIFF_SLONG] of Byte = (0, 1, 1, 2, 4, 8, 1, 1, 2, 4);
procedure TTIFFGraphic.GetValueList(Stream: TStream; Tag: Cardinal; var Values: TByteArray);
// returns the values of the IFD entry indicated by Tag
var
Index,
Value,
Shift: Cardinal;
I: Integer;
begin
Values := nil;
if FindTag(Tag, Index) and
(FIFD[Index].DataLength > 0) then
begin
// prepare value list
SetLength(Values, FIFD[Index].DataLength);
// determine whether the data fits into 4 bytes
Value := DataTypeToSize[FIFD[Index].DataType] * FIFD[Index].DataLength;
// data fits into one cardinal -> extract it
if Value <= 4 then
begin
Shift := DataTypeToSize[FIFD[Index].DataType] * 8;
Value := FIFD[Index].Offset;
for I := 0 to FIFD[Index].DataLength - 1 do
begin
case FIFD[Index].DataType of
TIFF_BYTE:
Values[I] := Byte(Value);
TIFF_SHORT,
TIFF_SSHORT:
// no byte swap needed here because values in the IFD are already swapped
// (if necessary at all)
Values[I] := Word(Value);
TIFF_LONG,
TIFF_SLONG:
Values[I] := Value;
end;
Value := Value shr Shift;
end;
end
else
begin
// data of this tag does not fit into one 32 bits value
Stream.Position := FBasePosition + FIFD[Index].Offset;
// bytes sized data can be read directly instead of looping through the array
if FIFD[Index].DataType in [TIFF_BYTE, TIFF_ASCII, TIFF_SBYTE, TIFF_UNDEFINED]
then Stream.Read(Values[0], Value)
else
begin
for I := 0 to High(Values) do
begin
Stream.Read(Value, DataTypeToSize[FIFD[Index].DataType]);
case FIFD[Index].DataType of
TIFF_BYTE:
Value := Byte(Value);
TIFF_SHORT,
TIFF_SSHORT:
begin
if ioBigEndian in FImageProperties.Options then Value := Swap(Word(Value))
else Value := Word(Value);
end;
TIFF_LONG,
TIFF_SLONG:
if ioBigEndian in FImageProperties.Options then Value := SwapLong(Value);
end;
Values[I] := Value;
end;
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TTIFFGraphic.GetValueList(Stream: TStream; Tag: Cardinal; var Values: TCardinalArray);
// returns the values of the IFD entry indicated by Tag
var
Index,
Value,
Shift: Cardinal;
I: Integer;
begin
Values := nil;
if FindTag(Tag, Index) and
(FIFD[Index].DataLength > 0) then
begin
// prepare value list
SetLength(Values, FIFD[Index].DataLength);
// determine whether the data fits into 4 bytes
Value := DataTypeToSize[FIFD[Index].DataType] * FIFD[Index].DataLength;
// data fits into one cardinal -> extract it
if Value <= 4 then
begin
Shift := DataTypeToSize[FIFD[Index].DataType] * 8;
Value := FIFD[Index].Offset;
for I := 0 to FIFD[Index].DataLength - 1 do
begin
case FIFD[Index].DataType of
TIFF_BYTE,
TIFF_ASCII,
TIFF_SBYTE,
TIFF_UNDEFINED:
Values[I] := Byte(Value);
TIFF_SHORT,
TIFF_SSHORT:
// no byte swap needed here because values in the IFD are already swapped
// (if necessary at all)
Values[I] := Word(Value);
TIFF_LONG,
TIFF_SLONG:
Values[I] := Value;
end;
Value := Value shr Shift;
end;
end
else
begin
// data of this tag does not fit into one 32 bits value
Stream.Position := FBasePosition + FIFD[Index].Offset;
// even bytes sized data must be read by the loop as it is expanded to cardinals
for I := 0 to High(Values) do
begin
Stream.Read(Value, DataTypeToSize[FIFD[Index].DataType]);
case FIFD[Index].DataType of
TIFF_BYTE:
Value := Byte(Value);
TIFF_SHORT,
TIFF_SSHORT:
begin
if ioBigEndian in FImageProperties.Options then Value := Swap(Word(Value))
else Value := Word(Value);
end;
TIFF_LONG,
TIFF_SLONG:
if ioBigEndian in FImageProperties.Options then Value := SwapLong(Value);
end;
Values[I] := Value;
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TTIFFGraphic.GetValueList(Stream: TStream; Tag: Cardinal; var Values: TFloatArray);
// returns the values of the IFD entry indicated by Tag
var
Index,
Shift,
IntValue: Cardinal;
Value: Single;
I: Integer;
IntNominator,
IntDenominator: Cardinal;
FloatNominator,
FloatDenominator: Cardinal;
begin
Values := nil;
if FindTag(Tag, Index) and
(FIFD[Index].DataLength > 0) then
begin
// prepare value list
SetLength(Values, FIFD[Index].DataLength);
// determine whether the data fits into 4 bytes
Value := DataTypeToSize[FIFD[Index].DataType] * FIFD[Index].DataLength;
// data fits into one cardinal -> extract it
if Value <= 4 then
begin
Shift := DataTypeToSize[FIFD[Index].DataType] * 8;
IntValue := FIFD[Index].Offset;
for I := 0 to FIFD[Index].DataLength - 1 do
begin
case FIFD[Index].DataType of
TIFF_BYTE,
TIFF_ASCII,
TIFF_SBYTE,
TIFF_UNDEFINED:
Values[I] := Byte(IntValue);
TIFF_SHORT,
TIFF_SSHORT:
// no byte swap needed here because values in the IFD are already swapped
// (if necessary at all)
Values[I] := Word(IntValue);
TIFF_LONG,
TIFF_SLONG:
Values[I] := IntValue;
end;
IntValue := IntValue shr Shift;
end;
end
else
begin
// data of this tag does not fit into one 32 bits value
Stream.Position := FBasePosition + FIFD[Index].Offset;
// even bytes sized data must be read by the loop as it is expanded to Single
for I := 0 to High(Values) do
begin
case FIFD[Index].DataType of
TIFF_BYTE:
begin
Stream.Read(IntValue, DataTypeToSize[FIFD[Index].DataType]);
Value := Byte(IntValue);
end;
TIFF_SHORT,
TIFF_SSHORT:
begin
Stream.Read(IntValue, DataTypeToSize[FIFD[Index].DataType]);
if ioBigEndian in FImageProperties.Options then Value := Swap(Word(IntValue))
else Value := Word(IntValue);
end;
TIFF_LONG,
TIFF_SLONG:
begin
Stream.Read(IntValue, DataTypeToSize[FIFD[Index].DataType]);
if ioBigEndian in FImageProperties.Options then Value := SwapLong(IntValue);
end;
TIFF_RATIONAL,
TIFF_SRATIONAL:
begin
Stream.ReadBuffer(FloatNominator, SizeOf(FloatNominator));
Stream.ReadBuffer(FloatDenominator, SizeOf(FloatDenominator));
if ioBigEndian in FImageProperties.Options then
begin
FloatNominator := SwapLong(Cardinal(FloatNominator));
FloatDenominator := SwapLong(Cardinal(FloatDenominator));
end;
Value := FloatNominator / FloatDenominator;
end;
TIFF_FLOAT:
begin
Stream.ReadBuffer(IntNominator, SizeOf(IntNominator));
Stream.ReadBuffer(IntDenominator, SizeOf(IntDenominator));
if ioBigEndian in FImageProperties.Options then
begin
IntNominator := SwapLong(IntNominator);
IntDenominator := SwapLong(IntDenominator);
end;
Value := IntNominator / IntDenominator;
end;
end;
Values[I] := Value;
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TTIFFGraphic.GetValue(Stream: TStream; Tag: Cardinal; Default: Single = 0): Single;
// returns the value of the IFD entry indicated by Tag or the default value if the entry is not there
var
Index: Cardinal;
IntNominator,
IntDenominator: Cardinal;
FloatNominator,
FloatDenominator: Cardinal;
begin
Result := Default;
if FindTag(Tag, Index) then
begin
// if the data length is > 1 then Offset is a real offset into the stream,
// otherwise it is the value itself and must be shortend depending on the data type
if FIFD[Index].DataLength = 1 then
begin
case FIFD[Index].DataType of
TIFF_BYTE:
Result := Byte(FIFD[Index].Offset);
TIFF_SHORT,
TIFF_SSHORT:
Result := Word(FIFD[Index].Offset);
TIFF_LONG,
TIFF_SLONG: // nothing to do
Result := FIFD[Index].Offset;
TIFF_RATIONAL,
TIFF_SRATIONAL:
begin
Stream.Position := FBasePosition + FIFD[Index].Offset;
Stream.ReadBuffer(FloatNominator, SizeOf(FloatNominator));
Stream.ReadBuffer(FloatDenominator, SizeOf(FloatDenominator));
if ioBigEndian in FImageProperties.Options then
begin
FloatNominator := SwapLong(Cardinal(FloatNominator));
FloatDenominator := SwapLong(Cardinal(FloatDenominator));
end;
Result := FloatNominator / FloatDenominator;
end;
TIFF_FLOAT:
begin
Stream.Position := FBasePosition + FIFD[Index].Offset;
Stream.ReadBuffer(IntNominator, SizeOf(IntNominator));
Stream.ReadBuffer(IntDenominator, SizeOf(IntDenominator));
if ioBigEndian in FImageProperties.Options then
begin
IntNominator := SwapLong(IntNominator);
IntDenominator := SwapLong(IntDenominator);
end;
Result := IntNominator / IntDenominator;
end;
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TTIFFGraphic.GetValue(Tag: Cardinal; Default: Cardinal = 0): Cardinal;
// returns the value of the IFD entry indicated by Tag or the default value if the entry is not there
var
Index: Cardinal;
begin
if not FindTag(Tag, Index) then Result := Default
else
begin
Result := FIFD[Index].Offset;
// if the data length is > 1 then Offset is a real offset into the stream,
// otherwise it is the value itself and must be shortend depending on the data type
if FIFD[Index].DataLength = 1 then
begin
case FIFD[Index].DataType of
TIFF_BYTE:
Result := Byte(Result);
TIFF_SHORT,
TIFF_SSHORT:
Result := Word(Result);
TIFF_LONG,
TIFF_SLONG: // nothing to do
;
else
Result := Default;
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TTIFFGraphic.GetValue(Tag: Cardinal; var Size: Cardinal; Default: Cardinal): Cardinal;
// Returns the value of the IFD entry indicated by Tag or the default value if the entry is not there.
// If the tag exists then also the data size is returned.
var
Index: Cardinal;
begin
if not FindTag(Tag, Index) then
begin
Result := Default;
Size := 0;
end
else
begin
Result := FIFD[Index].Offset;
Size := FIFD[Index].DataLength;
// if the data length is > 1 then Offset is a real offset into the stream,
// otherwise it is the value itself and must be shortend depending on the data type
if FIFD[Index].DataLength = 1 then
begin
case FIFD[Index].DataType of
TIFF_BYTE:
Result := Byte(Result);
TIFF_SHORT,
TIFF_SSHORT:
Result := Word(Result);
TIFF_LONG,
TIFF_SLONG: // nothing to do
;
else
Result := Default;
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TTIFFGraphic.SortIFD;
// Although all entries in the IFD should be sorted there are still files where this is not the case.
// Because the lookup for certain tags in the IFD uses binary search it must be made sure the IFD is
// sorted (what we do here).
//--------------- local function --------------------------------------------
procedure QuickSort(L, R: Integer);
var
I, J, M: Integer;
T: TIFDEntry;
begin
repeat
I := L;
J := R;
M := (L + R) shr 1;
repeat
while FIFD[I].Tag < FIFD[M].Tag do Inc(I);
while FIFD[J].Tag > FIFD[M].Tag do Dec(J);
if I <= J then
begin
T := FIFD[I];
FIFD[I] := FIFD[J];
FIFD[J] := T;
Inc(I);
Dec(J);
end;
until I > J;
if L < J then QuickSort(L, J);
L := I;
until I >= R;
end;
//--------------- end local functions ---------------------------------------
begin
QuickSort(0, High(FIFD));
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TTIFFGraphic.SwapIFD;
// swap the member fields of all entries of the currently loaded IFD from big endian to little endian
var
I: Integer;
Size: Cardinal;
begin
for I := 0 to High(FIFD) do
with FIFD[I] do
begin
Tag := Swap(Tag);
DataType := Swap(DataType);
DataLength := SwapLong(DataLength);
// determine whether the data fits into 4 bytes
Size := DataTypeToSize[FIFD[I].DataType] * FIFD[I].DataLength;
if Size >= 4 then Offset := SwapLong(Offset)
else
case DataType of
TIFF_SHORT,
TIFF_SSHORT:
if DataLength > 1 then Offset := SwapLong(Offset)
else Offset := Swap(Word(Offset));
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TTIFFGraphic.LoadFromStream(Stream: TStream);
var
IFDCount: Word;
Buffer: Pointer;
Run: PByte;
Pixels,
EncodedData,
DataPointerCopy: Pointer;
Offsets,
ByteCounts: TCardinalArray;
ColorMap: Cardinal;
StripSize: Cardinal;
Decoder: TDecoder;
// dynamically assigned handler
Deprediction: procedure(P: Pointer; Count: Cardinal);
begin
Handle := 0;
Deprediction := nil;
Decoder := nil;
// we need to keep the current stream position because all position information
// are relative to this one
FBasePosition := Stream.Position;
if ReadImageProperties(Stream, 0) then
begin
with FImageProperties do
try
// tiled images aren't supported
if ioTiled in Options then Exit;
FProgressRect := Rect(0, 0, 0, 1);
Progress(Self, psStarting, 0, False, FProgressRect, gesPreparing);
// read data of the first image file directory (IFD)
Stream.Position := FBasePosition + FirstIFD;
Stream.ReadBuffer(IFDCount, SizeOf(IFDCount));
if ioBigEndian in Options then IFDCount := Swap(IFDCount);
SetLength(FIFD, IFDCount);
Stream.ReadBuffer(FIFD[0], IFDCount * SizeOf(TIFDEntry));
if ioBigEndian in Options then SwapIFD;
SortIFD;
// --- read the data of the directory which are needed to actually load the image:
// data organization
GetValueList(Stream, TIFFTAG_STRIPOFFSETS, Offsets);
GetValueList(Stream, TIFFTAG_STRIPBYTECOUNTS, ByteCounts);
// retrive additional tile data if necessary
if ioTiled in Options then
begin
GetValueList(Stream, TIFFTAG_TILEOFFSETS, Offsets);
GetValueList(Stream, TIFFTAG_TILEBYTECOUNTS, ByteCounts);
end;
// determine pixelformat and setup color conversion
with ColorManager do
begin
if ioBigEndian in Options then SourceOptions := [coNeedByteSwap]
else SourceOptions := [];
SourceBitsPerSample := BitsPerSample;
if SourceBitsPerSample = 16 then TargetBitsPerSample := 8
else TargetBitsPerSample := SourceBitsPerSample;
// the JPEG lib does internally a conversion to RGB
if Compression in [ctOJPEG, ctJPEG] then SourceColorScheme := csBGR
else SourceColorScheme := ColorScheme;
case SourceColorScheme of
csRGBA:
TargetColorScheme := csBGRA;
csRGB:
TargetColorScheme := csBGR;
csCMY,
csCMYK,
csCIELab,
csYCbCr:
TargetColorScheme := csBGR;
csIndexed:
begin
if HasAlpha then SourceColorScheme := csGA; // fake indexed images with alpha (used in EPS)
// as being grayscale with alpha
TargetColorScheme := csIndexed;
end;
else
TargetColorScheme := SourceColorScheme;
end;
SourceSamplesPerPixel := SamplesPerPixel;
if SourceColorScheme = csCMYK then TargetSamplesPerPixel := 3
else TargetSamplesPerPixel := SamplesPerPixel;
if SourceColorScheme = csCIELab then SourceOptions := SourceOptions + [coLabByteRange];
if SourceColorScheme = csGA then PixelFormat := pf8Bit
else PixelFormat := TargetPixelFormat;
end;
// now that the pixel format is set we can also set the (possibly large) image dimensions
Self.Width := Width;
Self.Height := Height;
if (Width = 0) or (Height = 0) then GraphicExError(gesInvalidImage, ['TIF/TIFF']);
FProgressRect.Right := Width;
if ColorManager.TargetColorScheme in [csIndexed, csG, csGA] then
begin
// load palette data and build palette
if ColorManager.TargetColorScheme = csIndexed then
begin
ColorMap := GetValue(TIFFTAG_COLORMAP, StripSize, 0);
if StripSize > 0 then
begin
Stream.Position := FBasePosition + ColorMap;
// number of palette entries is also given by the color map tag
// (3 components each (r,g,b) and two bytes per component)
Stream.ReadBuffer(FPalette[0] , 2 * StripSize);
Palette := ColorManager.CreateColorPalette([@FPalette[0], @FPalette[StripSize div 3],
@FPalette[2 * StripSize div 3]], pfPlane16Triple, StripSize, False);
end;
end
else Palette := ColorManager.CreateGrayScalePalette(ioMinIsWhite in Options);
end
else
if ColorManager.SourceColorScheme = csYCbCr then
ColorManager.SetYCbCrParameters(FYCbCrCoefficients, YCbCrSubSampling[0], YCbCrSubSampling[1]);
// intermediate buffer for data
BytesPerLine := (BitsPerPixel * Width + 7) div 8;
// determine prediction scheme
if Compression <> ctNone then
begin
// Prediction without compression makes no sense at all (as it is to improve
// compression ratios). Appearently there are image which are uncompressed but still
// have a prediction scheme set. Hence we must check for it.
case Predictor of
PREDICTION_HORZ_DIFFERENCING: // currently only one prediction scheme is defined
case SamplesPerPixel of
4:
Deprediction := Depredict4;
3:
Deprediction := Depredict3;
else
Deprediction := Depredict1;
end;
end;
end;
// create decompressor for the image
case Compression of
ctNone:
;
{$ifdef UseLZW}
ctLZW:
Decoder := TTIFFLZWDecoder.Create;
{$endif}
ctPackedBits:
Decoder := TPackbitsRLEDecoder.Create;
ctFaxRLE,
ctFaxRLEW:
Decoder := TCCITTMHDecoder.Create(GetValue(TIFFTAG_GROUP3OPTIONS),
ioReversed in Options,
Compression = ctFaxRLEW,
Width);
ctFax3:
Decoder := TCCITTFax3Decoder.Create(GetValue(TIFFTAG_GROUP3OPTIONS), ioReversed in Options, False, Width);
ctJPEG:
begin
// some extra work is needed for JPEG
GetValueList(Stream, TIFFTAG_JPEGTABLES, JPEGTables);
Decoder := TTIFFJPEGDecoder.Create(@FImageProperties);
end;
ctThunderscan:
Decoder := TThunderDecoder.Create(Width);
ctLZ77:
Decoder := TLZ77Decoder.Create(Z_PARTIAL_FLUSH, True);
else
{
COMPRESSION_OJPEG,
COMPRESSION_CCITTFAX4
COMPRESSION_NEXT
COMPRESSION_IT8CTPAD
COMPRESSION_IT8LW
COMPRESSION_IT8MP
COMPRESSION_IT8BL
COMPRESSION_PIXARFILM
COMPRESSION_PIXARLOG
COMPRESSION_DCS
COMPRESSION_JBIG}
GraphicExError(gesUnsupportedFeature, [gesCompressionScheme, 'TIF/TIFF']);
end;
if Assigned(Decoder) then Decoder.DecodeInit;
Progress(Self, psEnding, 0, False, FProgressRect, '');
Progress(Self, psStarting, 0, False, FProgressRect, gesTransfering);
// go for each strip in the image (which might contain more than one line)
CurrentRow := 0;
CurrentStrip := 0;
StripCount := Length(Offsets);
while CurrentStrip < StripCount do
begin
Stream.Position := FBasePosition + Offsets[CurrentStrip];
if CurrentStrip < Length(RowsPerStrip) then StripSize := BytesPerLine * RowsPerStrip[CurrentStrip]
else StripSize := BytesPerLine * RowsPerStrip[High(RowsPerStrip)];
GetMem(Buffer, StripSize);
Run := Buffer;
try
// decompress strip if necessary
if Assigned(Decoder) then
begin
GetMem(EncodedData, ByteCounts[CurrentStrip]);
try
DataPointerCopy := EncodedData;
Stream.Read(EncodedData^, ByteCounts[CurrentStrip]);
// need pointer copies here because they could get modified
// while decoding
Decoder.Decode(DataPointerCopy, Pointer(Run), ByteCounts[CurrentStrip], StripSize);
finally
if Assigned(EncodedData) then FreeMem(EncodedData);
end;
end
else
begin
Stream.Read(Buffer^, StripSize);
end;
Run := Buffer;
// go for each line (row) in the strip
while (CurrentRow < Height) and ((integer(Run) - integer(Buffer)) < Integer(StripSize)) do
begin
Pixels := ScanLine[CurrentRow];
// depredict strip if necessary
if Assigned(Deprediction) then Deprediction(Run, Width - 1);
// any color conversion comes last
ColorManager.ConvertRow([Run], Pixels, Width, $FF);
Inc(Run, BytesPerLine);
Inc(CurrentRow);
Progress(Self, psRunning, MulDiv(CurrentRow, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
finally
if Assigned(Buffer) then FreeMem(Buffer);
end;
Inc(CurrentStrip);
end;
finally
Progress(Self, psEnding, 0, False, FProgressRect, '');
if Assigned(Decoder) then Decoder.DecodeEnd;
Decoder.Free;
end;
end
else GraphicExError(gesInvalidImage, ['TIF/TIFF']);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TTIFFGraphic.SaveToStream(Stream: TStream);
begin
end;
//----------------------------------------------------------------------------------------------------------------------
function TTIFFGraphic.ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean;
// Reads all relevant TIF properties of the image of index ImageIndex (zero based).
// Returns True if the image ImageIndex could be read, otherwise False.
const
PhotometricToColorScheme: array[PHOTOMETRIC_MINISWHITE..PHOTOMETRIC_CIELAB] of TColorScheme = (
csG,
csG,
csRGBA,
csIndexed,
csUnknown,
csCMYK,
csYCbCr,
csUnknown,
csCIELab
);
var
IFDCount: Word;
ExtraSamples: TCardinalArray;
PhotometricInterpretation: Byte;
TIFCompression: Word;
Index: Cardinal;
IFDOffset: Cardinal;
Header: TTIFFHeader;
LocalBitsPerSample: TCardinalArray;
begin
// clear image properties
Result := inherited ReadImageProperties(Stream, ImageIndex);
with FImageProperties do
begin
// rewind stream to header position
Stream.Position := FBasePosition;
Stream.ReadBuffer(Header, SizeOf(Header));
if Header.ByteOrder = TIFF_BIGENDIAN then
begin
Options := Options + [ioBigEndian];
Header.Version := Swap(Header.Version);
Header.FirstIFD := SwapLong(Header.FirstIFD);
end;
Version := Header.Version;
FirstIFD := Header.FirstIFD;
if Version = TIFF_VERSION then
begin
IFDOffset := Header.FirstIFD;
// advance to next IFD until we have the desired one
repeat
Stream.Position := FBasePosition + IFDOffset;
// number of entries in this directory
Stream.ReadBuffer(IFDCount, SizeOf(IFDCount));
if Header.ByteOrder = TIFF_BIGENDIAN then IFDCount := Swap(IFDCount);
// if we already have the desired image then get out of here
if ImageIndex = 0 then Break;
Dec(ImageIndex);
// advance to offset for next IFD
Stream.Seek(IFDCount * SizeOf(TIFDEntry), soFromCurrent);
Stream.ReadBuffer(IFDOffset, SizeOf(IFDOffset));
// no further image available, but the required index is still not found
if IFDOffset = 0 then Exit;
until False;
SetLength(FIFD, IFDCount);
Stream.ReadBuffer(FIFD[0], IFDCount * SizeOf(TIFDEntry));
if Header.ByteOrder = TIFF_BIGENDIAN then SwapIFD;
SortIFD;
Width := GetValue(TIFFTAG_IMAGEWIDTH);
Height := GetValue(TIFFTAG_IMAGELENGTH);
if (Width = 0) or (Height = 0) then Exit;
// data organization
GetValueList(Stream, TIFFTAG_ROWSPERSTRIP, RowsPerStrip);
// some images rely on the default size ($FFFFFFFF) if only one stripe is in the image,
// make sure there's a valid value also in this case
if (Length(RowsPerStrip) = 0) or (RowsPerStrip[0] = $FFFFFFFF) then
begin
SetLength(RowsPerStrip, 1);
RowsPerStrip[0] := Height;
end;
// number of color components per pixel (1 for b&w, 16 and 256 colors, 3 for RGB, 4 for CMYK etc.)
SamplesPerPixel := GetValue(TIFFTAG_SAMPLESPERPIXEL, 1);
// number of bits per color component
GetValueList(Stream, TIFFTAG_BITSPERSAMPLE, LocalBitsPerSample);
if Length(LocalBitsPerSample) = 0 then BitsPerSample := 1
else BitsPerSample := LocalBitsPerSample[0];
// determine whether image is tiled and retrive tile data if necessary
TileWidth := GetValue(TIFFTAG_TILEWIDTH, 0);
TileLength := GetValue(TIFFTAG_TILELENGTH, 0);
if (TileWidth > 0) and (TileLength > 0) then Include(Options, ioTiled);
// photometric interpretation determines the color space
PhotometricInterpretation := GetValue(TIFFTAG_PHOTOMETRIC);
// type of extra information for additional samples per pixel
GetValueList(Stream, TIFFTAG_EXTRASAMPLES, ExtraSamples);
// determine whether extra samples must be considered
HasAlpha := Length(ExtraSamples) > 0;
// if any of the extra sample contains an invalid value then consider
// it as being not existant to avoid wrong interpretation for badly
// written images
if HasAlpha then
begin
for Index := 0 to High(ExtraSamples) do
if ExtraSamples[Index] > EXTRASAMPLE_UNASSALPHA then
begin
HasAlpha := False;
Break;
end;
end;
// currently all bits per sample values are equal
BitsPerPixel := BitsPerSample * SamplesPerPixel;
// create decompressor for the image
TIFCompression := GetValue(TIFFTAG_COMPRESSION);
case TIFCompression of
COMPRESSION_NONE:
Compression := ctNone;
COMPRESSION_LZW:
Compression := ctLZW;
COMPRESSION_PACKBITS:
Compression := ctPackedBits;
COMPRESSION_CCITTRLE:
Compression := ctFaxRLE;
COMPRESSION_CCITTRLEW:
Compression := ctFaxRLEW;
COMPRESSION_CCITTFAX3:
Compression := ctFax3;
COMPRESSION_OJPEG:
Compression := ctOJPEG;
COMPRESSION_JPEG:
Compression := ctJPEG;
COMPRESSION_CCITTFAX4:
Compression := ctFax4;
COMPRESSION_NEXT:
Compression := ctNext;
COMPRESSION_THUNDERSCAN:
Compression := ctThunderscan;
COMPRESSION_IT8CTPAD:
Compression := ctIT8CTPAD;
COMPRESSION_IT8LW:
Compression := ctIT8LW;
COMPRESSION_IT8MP:
Compression := ctIT8MP;
COMPRESSION_IT8BL:
Compression := ctIT8BL;
COMPRESSION_PIXARFILM:
Compression := ctPixarFilm;
COMPRESSION_PIXARLOG: // also a LZ77 clone
Compression := ctPixarLog;
COMPRESSION_ADOBE_DEFLATE,
COMPRESSION_DEFLATE:
Compression := ctLZ77;
COMPRESSION_DCS:
Compression := ctDCS;
COMPRESSION_JBIG:
Compression := ctJBIG;
else
Compression := ctUnknown;
end;
if PhotometricInterpretation in [PHOTOMETRIC_MINISWHITE..PHOTOMETRIC_CIELAB] then
begin
ColorScheme := PhotometricToColorScheme[PhotometricInterpretation];
if (PhotometricInterpretation = PHOTOMETRIC_RGB) and (SamplesPerPixel < 4) then ColorScheme := csRGB;
if PhotometricInterpretation = PHOTOMETRIC_MINISWHITE then Include(Options, ioMinIsWhite);
// extra work necessary for YCbCr
if PhotometricInterpretation = PHOTOMETRIC_YCBCR then
begin
if FindTag(TIFFTAG_YCBCRSUBSAMPLING, Index)
then GetValueList(Stream, TIFFTAG_YCBCRSUBSAMPLING, YCbCrSubSampling)
else
begin
// initialize default values if nothing is given in the file
SetLength(YCbCrSubSampling, 2);
YCbCrSubSampling[0] := 2;
YCbCrSubSampling[1] := 2;
end;
if FindTag(TIFFTAG_YCBCRPOSITIONING, Index) then FYCbCrPositioning := GetValue(TIFFTAG_YCBCRPOSITIONING)
else FYCbCrPositioning := YCBCRPOSITION_CENTERED;
if FindTag(TIFFTAG_YCBCRCOEFFICIENTS, Index)
then GetValueList(Stream, TIFFTAG_YCBCRCOEFFICIENTS, FYCbCrCoefficients)
else
begin
// defaults are from CCIR recommendation 601-1
SetLength(FYCbCrCoefficients, 3);
FYCbCrCoefficients[0] := 0.299;
FYCbCrCoefficients[1] := 0.587;
FYCbCrCoefficients[2] := 0.114;
end;
end;
end
else ColorScheme := csUnknown;
JPEGColorMode := GetValue(TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RAW);
JPEGTablesMode := GetValue(TIFFTAG_JPEGTABLESMODE, JPEGTABLESMODE_QUANT or JPEGTABLESMODE_HUFF);
PlanarConfig := GetValue(TIFFTAG_PLANARCONFIG);
// other image properties
XResolution := GetValue(Stream, TIFFTAG_XRESOLUTION);
YResolution := GetValue(Stream, TIFFTAG_YRESOLUTION);
if GetValue(TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH) = RESUNIT_CENTIMETER then
begin
// Resolution is given in centimeters.
// Although I personally prefer the metric system over the old english one :-)
// I still convert to inches because it is an unwritten rule to give image resolutions in dpi.
XResolution := XResolution * 2.54;
YResolution := YResolution * 2.54;
end;
// determine prediction scheme
Predictor := GetValue(TIFFTAG_PREDICTOR);
// determine fill order in bytes
if GetValue(TIFFTAG_FILLORDER, FILLORDER_MSB2LSB) = FILLORDER_LSB2MSB then Include(Options, ioReversed);
// finally show that we found and read an image
Result := True;
end;
end;
end;
//----------------- TEPSGraphic ----------------------------------------------------------------------------------------
{$ifdef EPSGraphic}
// Note: This EPS implementation does only read embedded pixel graphics in TIF format (preview).
// Credits to:
// Olaf Stieleke
// Torsten Pohlmeyer
// CPS Krohn GmbH
// for providing the base information about how to read the preview image.
type
TEPSHeader = packed record
Code: Cardinal; // alway $C6D3D0C5, if not there then this is not an EPS or it is not a binary EPS
PSStart, // Offset PostScript-Code
PSLen, // length of PostScript-Code
MetaPos, // position of a WMF
MetaLen, // length of a WMF
TiffPos, // position of TIFF (preview images should be either WMF or TIF but not both)
TiffLen: Integer; // length of the TIFF
Checksum: SmallInt;
end;
//----------------------------------------------------------------------------------------------------------------------
class function TEPSGraphic.CanLoad(Stream: TStream): Boolean;
var
Header: TEPSHeader;
LastPosition: Cardinal;
begin
with Stream do
begin
Result := (Size - Position) > SizeOf(Header);
if Result then
begin
LastPosition := Position;
Stream.ReadBuffer(Header, SizeOf(Header));
Result := (Header.Code = $C6D3D0C5) and
(Header.TiffPos > Integer(LastPosition) + SizeOf(Header)) and
(Header.TiffLen > 0);
Position := LastPosition;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TEPSGraphic.LoadFromStream(Stream: TStream);
var
Header: TEPSHeader;
begin
Stream.ReadBuffer(Header, SizeOf(Header));
if Header.Code <> $C6D3D0C5 then GraphicExError(gesInvalidImage, ['EPS']);
Stream.Seek(Header.TiffPos - SizeOf(Header), soFromCurrent);
inherited;
end;
//----------------------------------------------------------------------------------------------------------------------
function TEPSGraphic.ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean;
begin
Result := inherited ReadImageProperties(Stream, ImageIndex);
end;
{$endif} // EPSGraphic
{$endif} // TIFFGraphic
//----------------- TTargaGraphic --------------------------------------------------------------------------------------
{$ifdef TargaGraphic}
// FILE STRUCTURE FOR THE ORIGINAL TRUEVISION TGA FILE
// FIELD 1: NUMBER OF CHARACTERS IN ID FIELD (1 BYTES)
// FIELD 2: COLOR MAP TYPE (1 BYTES)
// FIELD 3: IMAGE TYPE CODE (1 BYTES)
// = 0 NO IMAGE DATA INCLUDED
// = 1 UNCOMPRESSED, COLOR-MAPPED IMAGE
// = 2 UNCOMPRESSED, TRUE-COLOR IMAGE
// = 3 UNCOMPRESSED, BLACK AND WHITE IMAGE (black and white is actually grayscale)
// = 9 RUN-LENGTH ENCODED COLOR-MAPPED IMAGE
// = 10 RUN-LENGTH ENCODED TRUE-COLOR IMAGE
// = 11 RUN-LENGTH ENCODED BLACK AND WHITE IMAGE
// FIELD 4: COLOR MAP SPECIFICATION (5 BYTES)
// 4.1: COLOR MAP ORIGIN (2 BYTES)
// 4.2: COLOR MAP LENGTH (2 BYTES)
// 4.3: COLOR MAP ENTRY SIZE (1 BYTES)
// FIELD 5:IMAGE SPECIFICATION (10 BYTES)
// 5.1: X-ORIGIN OF IMAGE (2 BYTES)
// 5.2: Y-ORIGIN OF IMAGE (2 BYTES)
// 5.3: WIDTH OF IMAGE (2 BYTES)
// 5.4: HEIGHT OF IMAGE (2 BYTES)
// 5.5: IMAGE PIXEL SIZE (1 BYTE)
// 5.6: IMAGE DESCRIPTOR BYTE (1 BYTE)
// bit 0..3: attribute bits per pixel
// bit 4..5: image orientation:
// 0: bottom left
// 1: bottom right
// 2: top left
// 3: top right
// bit 6..7: interleaved flag
// 0: two way (even-odd) interleave (e.g. IBM Graphics Card Adapter), obsolete
// 1: four way interleave (e.g. AT&T 6300 High Resolution), obsolete
// FIELD 6: IMAGE ID FIELD (LENGTH SPECIFIED BY FIELD 1)
// FIELD 7: COLOR MAP DATA (BIT WIDTH SPECIFIED BY FIELD 4.3 AND
// NUMBER OF COLOR MAP ENTRIES SPECIFIED IN FIELD 4.2)
// FIELD 8: IMAGE DATA FIELD (WIDTH AND HEIGHT SPECIFIED IN FIELD 5.3 AND 5.4)
const
TARGA_NO_COLORMAP = 0;
TARGA_COLORMAP = 1;
TARGA_EMPTY_IMAGE = 0;
TARGA_INDEXED_IMAGE = 1;
TARGA_TRUECOLOR_IMAGE = 2;
TARGA_BW_IMAGE = 3;
TARGA_INDEXED_RLE_IMAGE = 9;
TARGA_TRUECOLOR_RLE_IMAGE = 10;
TARGA_BW_RLE_IMAGE = 11;
type
TTargaHeader = packed record
IDLength,
ColorMapType,
ImageType: Byte;
ColorMapOrigin,
ColorMapSize: Word;
ColorMapEntrySize: Byte;
XOrigin,
YOrigin,
Width,
Height: Word;
PixelSize: Byte;
ImageDescriptor: Byte;
end;
//----------------------------------------------------------------------------------------------------------------------
class function TTargaGraphic.CanLoad(Stream: TStream): Boolean;
var
Header: TTargaHeader;
LastPosition: Cardinal;
begin
with Stream do
begin
LastPosition := Position;
Result := (Size - Position) > SizeOf(Header);
if Result then
begin
ReadBuffer(Header, SizeOf(Header));
// Targa images are hard to determine because there is no magic id or something like that.
// Hence all we can do is to check if all values from the header are within correct limits.
Result := (Header.ImageType in [TARGA_EMPTY_IMAGE, TARGA_INDEXED_IMAGE, TARGA_TRUECOLOR_IMAGE, TARGA_BW_IMAGE,
TARGA_INDEXED_RLE_IMAGE, TARGA_TRUECOLOR_RLE_IMAGE, TARGA_BW_RLE_IMAGE]) and
(Header.ColorMapType in [TARGA_NO_COLORMAP, TARGA_COLORMAP]) and
(Header.ColorMapEntrySize in [15, 16, 24, 32]) and
(Header.PixelSize in [8, 15, 16, 24, 32]);
end;
Position := LastPosition;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TTargaGraphic.LoadFromStream(Stream: TStream);
var
Run,
RLEBuffer: PByte;
I: Integer;
LineSize: Integer;
LineBuffer: Pointer;
ReadLength: Integer;
LogPalette: TMaxLogPalette;
Color16: Word;
Header: TTargaHeader;
FlipV: Boolean;
Decoder: TTargaRLEDecoder;
begin
Handle := 0;
FBasePosition := Stream.Position;
if ReadImageProperties(Stream, 0) then
with Stream, FImageProperties do
begin
Stream.Position := FBasePosition;
FProgressRect := Rect(0, 0, Width, 1);
Progress(Self, psStarting, 0, False, FProgressRect, gesPreparing);
Stream.Read(Header, SizeOf(Header));
FlipV := (Header.ImageDescriptor and $20) <> 0;
Header.ImageDescriptor := Header.ImageDescriptor and $F;
// skip image ID
Seek(Header.IDLength, soFromCurrent);
with ColorManager do
begin
SourceSamplesPerPixel := SamplesPerPixel;
TargetSamplesPerPixel := SamplesPerPixel;
SourceColorScheme := ColorScheme;
SourceOptions := [];
TargetColorScheme := csBGR;
SourceBitsPerSample := BitsPerSample;
TargetBitsPerSample := BitsPerSample;
PixelFormat := TargetPixelFormat;
end;
if (Header.ColorMapType = TARGA_COLORMAP) or
(Header.ImageType in [TARGA_BW_IMAGE, TARGA_BW_RLE_IMAGE]) then
begin
if Header.ImageType in [TARGA_BW_IMAGE, TARGA_BW_RLE_IMAGE] then
Palette := ColorManager.CreateGrayscalePalette(False)
else
begin
LineSize := (Header.ColorMapEntrySize div 8) * Header.ColorMapSize;
GetMem(LineBuffer, LineSize);
try
ReadBuffer(LineBuffer^, LineSize);
case Header.ColorMapEntrySize of
32:
Palette := ColorManager.CreateColorPalette([LineBuffer], pfInterlaced8Quad, Header.ColorMapSize, True);
24:
Palette := ColorManager.CreateColorPalette([LineBuffer], pfInterlaced8Triple, Header.ColorMapSize, True);
else
with LogPalette do
begin
// read palette entries and create a palette
ZeroMemory(@LogPalette, SizeOf(LogPalette));
palVersion := $300;
palNumEntries := Header.ColorMapSize;
// 15 and 16 bits per color map entry (handle both like 555 color format
// but make 8 bit from 5 bit per color component)
for I := 0 to Header.ColorMapSize - 1 do
begin
Stream.Read(Color16, 2);
palPalEntry[I].peBlue := (Color16 and $1F) shl 3;
palPalEntry[I].peGreen := (Color16 and $3E0) shr 2;
palPalEntry[I].peRed := (Color16 and $7C00) shr 7;
end;
Palette := CreatePalette(PLogPalette(@LogPalette)^);
end;
end;
finally
if Assigned(LineBuffer) then FreeMem(LineBuffer);
end;
end;
end;
Self.Width := Header.Width;
Self.Height := Header.Height;
LineSize := Width * (Header.PixelSize div 8);
Progress(Self, psEnding, 0, False, FProgressRect, '');
Progress(Self, psStarting, 0, False, FProgressRect, gesTransfering);
case Header.ImageType of
TARGA_EMPTY_IMAGE: // nothing to do here
;
TARGA_BW_IMAGE,
TARGA_INDEXED_IMAGE,
TARGA_TRUECOLOR_IMAGE:
begin
for I := 0 to Height - 1 do
begin
if FlipV then LineBuffer := ScanLine[I]
else LineBuffer := ScanLine[Header.Height - (I + 1)];
ReadBuffer(LineBuffer^, LineSize);
Progress(Self, psRunning, MulDiv(I, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
TARGA_BW_RLE_IMAGE,
TARGA_INDEXED_RLE_IMAGE,
TARGA_TRUECOLOR_RLE_IMAGE:
begin
RLEBuffer := nil;
Decoder := TTargaRLEDecoder.Create(Header.PixelSize);
try
GetMem(RLEBuffer, 2 * LineSize);
for I := 0 to Height - 1 do
begin
if FlipV then LineBuffer := ScanLine[I]
else LineBuffer := ScanLine[Header.Height - (I + 1)];
ReadLength := Stream.Read(RLEBuffer^, 2 * LineSize);
Run := RLEBuffer;
Decoder.Decode(Pointer(Run), LineBuffer, 2 * LineSize, Width);
Stream.Position := Stream.Position - ReadLength + (Run - RLEBuffer);
Progress(Self, psRunning, MulDiv(I, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
finally
if Assigned(RLEBuffer) then FreeMem(RLEBuffer);
Decoder.Free;
end;
end;
end;
Progress(Self, psEnding, 0, False, FProgressRect, '');
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TTargaGraphic.ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean;
var
Header: TTargaHeader;
begin
inherited ReadImageProperties(Stream, ImageIndex);
with Stream, FImageProperties do
begin
ReadBuffer(Header, SizeOf(Header));
Header.ImageDescriptor := Header.ImageDescriptor and $F;
Width := Header.Width;
Height := Header.Height;
BitsPerSample := 8;
case Header.PixelSize of
8:
begin
if Header.ImageType in [TARGA_BW_IMAGE, TARGA_BW_RLE_IMAGE] then ColorScheme := csG
else ColorScheme := csIndexed;
SamplesPerPixel := 1;
end;
15,
16: // actually, 16 bit are meant being 15 bit
begin
ColorScheme := csRGB;
BitsPerSample := 5;
SamplesPerPixel := 3;
end;
24:
begin
ColorScheme := csRGB;
SamplesPerPixel := 3;
end;
32:
begin
ColorScheme := csRGBA;
SamplesPerPixel := 4;
end;
end;
BitsPerPixel := SamplesPerPixel * BitsPerSample;
if Header.ImageType in [TARGA_BW_RLE_IMAGE, TARGA_INDEXED_RLE_IMAGE, TARGA_TRUECOLOR_RLE_IMAGE]
then Compression := ctRLE
else Compression := ctNone;
Width := Header.Width;
Height := Header.Height;
Result := True;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TTargaGraphic.SaveToStream(Stream: TStream);
begin
SaveToStream(Stream, True);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TTargaGraphic.SaveToStream(Stream: TStream; Compressed: Boolean);
// The format of the image to be saved depends on the current properties of the bitmap not
// on the values which may be set in the header during a former load.
var
RLEBuffer: Pointer;
I: Integer;
LineSize: Integer;
WriteLength: Cardinal;
LogPalette: TMaxLogPalette;
BPP: Byte;
Header: TTargaHeader;
Encoder: TTargaRLEDecoder;
begin
FProgressRect := Rect(0, 0, Width, 1);
Progress(Self, psStarting, 0, False, FProgressRect, gesPreparing);
// prepare color depth
case PixelFormat of
pf1Bit,
pf4Bit: // Note: 1 bit and 4 bits per pixel are not supported in the Targa format, an image
// with one of these pixel formats is implicitly converted to 256 colors.
begin
PixelFormat := pf8Bit;
BPP := 1;
end;
pf8Bit:
BPP := 1;
pf15Bit,
pf16Bit:
BPP := 2;
pf24Bit:
BPP := 3;
pf32Bit:
BPP := 4;
else
BPP := GetDeviceCaps(Canvas.Handle, BITSPIXEL) div 8;
end;
if not Empty then
begin
with Header do
begin
IDLength := 0;
if BPP = 1 then ColorMapType := 1
else ColorMapType := 0;
if not Compressed then
// can't distinct between a B&W and an color indexed image here, so I use always the latter
if BPP = 1 then ImageType := TARGA_INDEXED_IMAGE
else ImageType := TARGA_TRUECOLOR_IMAGE
else
if BPP = 1 then ImageType := TARGA_INDEXED_RLE_IMAGE
else ImageType := TARGA_TRUECOLOR_RLE_IMAGE;
ColorMapOrigin := 0;
// always save entire palette
ColorMapSize := 256;
// always save complete color information
ColorMapEntrySize := 24;
XOrigin := 0;
YOrigin := 0;
Width := Self.Width;
Height := Self.Height;
PixelSize := 8 * BPP;
// if the image is a bottom-up DIB then indicate this in the image descriptor
if Cardinal(Scanline[0]) > Cardinal(Scanline[1]) then ImageDescriptor := $20
else ImageDescriptor := 0;
end;
Stream.Write(Header, SizeOf(Header));
// store color palette if necessary
if Header.ColorMapType = 1 then
with LogPalette do
begin
// read palette entries
GetPaletteEntries(Palette, 0, 256, palPalEntry);
for I := 0 to 255 do
begin
Stream.Write(palPalEntry[I].peBlue, 1);
Stream.Write(palPalEntry[I].peGreen, 1);
Stream.Write(palPalEntry[I].peRed, 1);
end;
end;
LineSize := Width * (Header.PixelSize div 8);
Progress(Self, psEnding, 0, False, FProgressRect, '');
Progress(Self, psStarting, 0, False, FProgressRect, gesTransfering);
// finally write image data
if Compressed then
begin
RLEBuffer := nil;
Encoder := TTargaRLEDecoder.Create(Header.PixelSize);
try
GetMem(RLEBuffer, 2 * LineSize);
for I := 0 to Height - 1 do
begin
Encoder.Encode(ScanLine[I], RLEBuffer, Width, WriteLength);
Stream.WriteBuffer(RLEBuffer^, WriteLength);
Progress(Self, psRunning, 0, False, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
finally
if Assigned(RLEBuffer) then FreeMem(RLEBuffer);
Encoder.Free;
end;
end
else
begin
for I := 0 to Height - 1 do
begin
Stream.WriteBuffer(ScanLine[I]^, LineSize);
Progress(Self, psRunning, 0, False, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
Progress(Self, psEnding, 0, False, FProgressRect, '');
end;
end;
{$endif} // TargaGraphic
//----------------- TPCXGraphic ----------------------------------------------------------------------------------------
{$ifdef PCXGraphic}
type
TPCXHeader = record
FileID: Byte; // $0A for PCX files, $CD for SCR files
Version: Byte; // 0: version 2.5; 2: 2.8 with palette; 3: 2.8 w/o palette; 5: version 3
Encoding: Byte; // 0: uncompressed; 1: RLE encoded
BitsPerPixel: Byte;
XMin,
YMin,
XMax,
YMax, // coordinates of the corners of the image
HRes, // horizontal resolution in dpi
VRes: Word; // vertical resolution in dpi
ColorMap: array[0..15] of TRGB; // color table
Reserved,
ColorPlanes: Byte; // color planes (at most 4)
BytesPerLine, // number of bytes of one line of one plane
PaletteType: Word; // 1: color or b&w; 2: gray scale
Fill: array[0..57] of Byte;
end;
//----------------------------------------------------------------------------------------------------------------------
class function TPCXGraphic.CanLoad(Stream: TStream): Boolean;
var
Header: TPCXHeader;
LastPosition: Cardinal;
begin
with Stream do
begin
LastPosition := Position;
Result := (Size - Position) > SizeOf(Header);
if Result then
begin
Result := (Header.FileID in [$0A, $0C]) and
(Header.Version in [0, 2, 3, 5]) and
(Header.Encoding in [0, 1]);
ReadBuffer(Header, SizeOf(Header));
end;
Position := LastPosition;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TPCXGraphic.LoadFromStream(Stream: TStream);
var
Header: TPCXHeader;
//--------------- local functions -------------------------------------------
procedure MakePalette;
var
PCXPalette: array[0..255] of TRGB;
OldPos: Integer;
Marker: Byte;
begin
if (Header.Version <> 3) or (PixelFormat = pf1Bit) then
begin
case PixelFormat of
pf1Bit:
Palette := ColorManager.CreateGrayScalePalette(False);
pf4Bit:
with Header do
begin
if paletteType = 2 then Palette := ColorManager.CreateGrayScalePalette(False)
else Palette := ColorManager.CreateColorPalette([@ColorMap], pfInterlaced8Triple, 16, False);
end;
pf8Bit:
begin
OldPos := Stream.Position;
// 256 colors with 3 components plus one marker byte
Stream.Position := Stream.Size - 769;
Stream.Read(Marker, 1);
if Marker <> $0C then
begin
// palette ID is wrong, perhaps gray scale?
if Header.PaletteType = 2 then Palette := ColorManager.CreateGrayScalePalette(False)
else ; // ignore palette
end
else
begin
Stream.Read(PCXPalette[0], 768);
Palette := ColorManager.CreateColorPalette([@PCXPalette], pfInterlaced8Triple, 256, False);
end;
Stream.Position := OldPos;
end;
end;
end
else
begin
// version 2.8 without palette information, just use the system palette
// 256 colors will not be correct with this assignment...
Palette := SystemPalette16;
end;
end;
//--------------- end local functions ---------------------------------------
var
PCXSize,
Size: Cardinal;
RawBuffer,
DecodeBuffer: Pointer;
Run: PByte;
Plane1,
Plane2,
Plane3,
Plane4: PByte;
Value,
Mask: Byte;
I, J: Integer;
Line: PByte;
Increment: Cardinal;
NewPixelFormat: TPixelFormat;
begin
Handle := 0;
FBasePosition := Stream.Position;
if ReadImageProperties(Stream, 0) then
begin
Stream.Position := FBasePosition;
FProgressRect := Rect(0, 0, Width, 1);
Progress(Self, psStarting, 0, False, FProgressRect, gesPreparing);
Stream.Read(Header, SizeOf(Header));
PCXSize := Stream.Size - Stream.Position;
with Header, FImageProperties do
begin
if not (FileID in [$0A, $CD]) then GraphicExError(gesInvalidImage, ['PCX or SCR']);
with ColorManager do
begin
SourceColorScheme := ColorScheme;
SourceBitsPerSample := BitsPerSample;
SourceSamplesPerPixel := SamplesPerPixel;
if ColorScheme = csIndexed then TargetColorScheme := csIndexed
else TargetColorScheme := csBGR;
if BitsPerPixel = 2 then TargetBitsPerSample := 4
else TargetBitsPerSample := BitsPerSample;
// Note: pixel depths of 2 and 4 bits may not be used with more than one plane
// otherwise the image will not show up correctly
TargetSamplesPerPixel := SamplesPerPixel;
end;
NewPixelFormat := ColorManager.TargetPixelFormat;
if NewPixelFormat = pfCustom then
begin
// there can be a special case comprising 4 planes each with 1 bit
if (SamplesPerPixel = 4) and (BitsPerPixel = 4) then NewPixelFormat := pf4Bit
else GraphicExError(gesInvalidColorFormat, ['PCX']);
end;
PixelFormat := NewPixelFormat;
// 256 colors palette is appended to the actual PCX data
if PixelFormat = pf8Bit then Dec(PCXSize, 769);
if PixelFormat <> pf24Bit then MakePalette;
Self.Width := Width;
Self.Height := Height;
// adjust alignment of line
Increment := SamplesPerPixel * Header.BytesPerLine;
// allocate pixel data buffer and decode data if necessary
if Compression = ctRLE then
begin
Size := Increment * Height;
GetMem(DecodeBuffer, Size);
GetMem(RawBuffer, PCXSize);
try
Stream.ReadBuffer(RawBuffer^, PCXSize);
with TPCXRLEDecoder.Create do
try
Decode(RawBuffer, DecodeBuffer, PCXSize, Size);
finally
Free;
end;
finally
if Assigned(RawBuffer) then FreeMem(RawBuffer);
end;
end
else
begin
GetMem(DecodeBuffer, PCXSize);
Stream.ReadBuffer(DecodeBuffer^, PCXSize);
end;
Progress(Self, psEnding, 0, False, FProgressRect, '');
Progress(Self, psStarting, 0, False, FProgressRect, gesTransfering);
try
Run := DecodeBuffer;
if (SamplesPerPixel = 4) and (BitsPerPixel = 4) then
begin
// 4 planes with one bit
for I := 0 to Height - 1 do
begin
Plane1 := Run;
Plane2 := @Run[Increment div 4];
Plane3 := @Run[2 * (Increment div 4)];
Plane4 := @Run[3 * (Increment div 4)];
Line := ScanLine[I];
// number of bytes to write
Size := (Width * BitsPerPixel + 7) div 8;
Mask := 0;
while Size > 0 do
begin
Value := 0;
for J := 0 to 1 do
asm
MOV AL, [Value]
MOV EDX, [Plane4] // take the 4 MSBs from the 4 runs and build a nibble
SHL BYTE PTR [EDX], 1 // read MSB and prepare next run at the same time
RCL AL, 1 // MSB from previous shift is in CF -> move it to AL
MOV EDX, [Plane3] // now do the same with the other three runs
SHL BYTE PTR [EDX], 1
RCL AL, 1
MOV EDX, [Plane2]
SHL BYTE PTR [EDX], 1
RCL AL, 1
MOV EDX, [Plane1]
SHL BYTE PTR [EDX], 1
RCL AL, 1
MOV [Value], AL
end;
Line^ := Value;
Inc(Line);
Dec(Size);
// two runs above (to construct two nibbles -> one byte), now update marker
// to know when to switch to next byte in the planes
Mask := (Mask + 2) mod 8;
if Mask = 0 then
begin
Inc(Plane1);
Inc(Plane2);
Inc(Plane3);
Inc(Plane4);
end;
end;
Inc(Run, Increment);
Progress(Self, psRunning, MulDiv(I, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end
else
if PixelFormat = pf24Bit then
begin
// true color
for I := 0 to Height - 1 do
begin
Line := ScanLine[I];
Plane1 := Run;
Plane2 := @Run[Increment div 3];
Plane3 := @Run[2 * (Increment div 3)];
ColorManager.ConvertRow([Plane1, Plane2, Plane3], Line, Width, $FF);
Inc(Run, Increment);
Progress(Self, psRunning, MulDiv(I, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end
end
else
begin
// other indexed formats
for I := 0 to Height - 1 do
begin
Line := ScanLine[I];
ColorManager.ConvertRow([Run], Line, Width, $FF);
Inc(Run, Increment);
Progress(Self, psRunning, MulDiv(I, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
finally
if Assigned(DecodeBuffer) then FreeMem(DecodeBuffer);
end;
Progress(Self, psEnding, 0, False, FProgressRect, '');
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TPCXGraphic.ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean;
var
Header: TPCXHeader;
begin
Result := inherited ReadImageProperties(Stream, 0);
with Stream do
begin
ReadBuffer(Header, SizeOf(Header));
with FImageProperties do
begin
if Header.FileID in [$0A, $CD] then
begin
Width := Header.XMax - Header.XMin + 1;
Height := Header.YMax - Header.YMin + 1;
SamplesPerPixel := Header.ColorPlanes;
BitsPerSample := Header.BitsPerPixel;
BitsPerPixel := BitsPerSample * SamplesPerPixel;
if BitsPerPixel <= 8 then ColorScheme := csIndexed
else ColorScheme := csRGB;
if Header.Encoding = 1 then Compression := ctRLE
else Compression := ctNone;
XResolution := Header.HRes;
YResolution := Header.VRes;
Result := True;
end;
end;
end;
end;
{$endif} // PCXGraphic
//----------------- TPCDGraphic ----------------------------------------------------------------------------------------
{$ifdef PCDGraphic}
const
PCD_BEGIN_BASE16 = 8192;
PCD_BEGIN_BASE4 = 47104;
PCD_BEGIN_BASE = 196608;
PCD_BEGIN_ORIENTATION = 194635;
PCD_BEGIN = 2048;
PCD_MAGIC = 'PCD_IPI';
//----------------------------------------------------------------------------------------------------------------------
class function TPCDGraphic.CanLoad(Stream: TStream): Boolean;
var
Header: array of Byte;
LastPosition: Cardinal;
begin
with Stream do
begin
LastPosition := Position;
Result := (Size - Position) > 3 * $800;
if Result then
begin
SetLength(Header, $803);
ReadBuffer(Header[0], Length(Header));
Result := (StrLComp(PAnsiChar(@Header[0]), 'PCD_OPA', 7) = 0) or
(StrLComp(PAnsiChar(@Header[$800]), 'PCD', 3) = 0);
end;
Position := LastPosition;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TPCDGraphic.LoadFromStream(Stream: TStream);
var
C1, C2, YY: PByte;
YCbCrData: array[0..2] of PByte;
SourceDummy,
DestDummy: Pointer;
Offset, I,
X, Y,
ImageIndex,
Rows,
Columns: Cardinal;
ScanLines: array of Pointer;
LineBuffer: Pointer;
Line,
Run: PBGR;
Decoder: TPCDDecoder;
begin
Handle := 0;
FBasePosition := Stream.Position;
ImageIndex := TPCDGraphic.DefaultResolution; // third image is Base resolution //@@@ SZ
if ReadImageProperties(Stream, ImageIndex) then
begin
with Stream, FImageProperties do
begin
Position := FBasePosition;
FProgressRect := Rect(0, 0, Width, 1);
Progress(Self, psStarting, 0, False, FProgressRect, gesPreparing);
Columns := 192 shl Min(ImageIndex, 2);
Rows := 128 shl Min(ImageIndex, 2);
// since row and columns might be swapped because of rotated images
// we determine the final dimensions once more
Width := 192 shl ImageIndex;
Height := 128 shl ImageIndex;
ZeroMemory(@YCbCrData, SizeOf(YCbCrData));
try
GetMem(YCbCrData[0], Width * Height);
GetMem(YCbCrData[1], Width * Height);
GetMem(YCbCrData[2], Width * Height);
// advance to image data
Offset := 96;
if Overview then Offset := 5
else
if ImageIndex = 1 then Offset := 23
else
if ImageIndex = 0 then Offset := 4;
Seek(Offset * $800 , soFromCurrent);
// color conversion setup
with ColorManager do
begin
SourceColorScheme := csPhotoYCC;
SourceBitsPerSample := 8;
SourceSamplesPerPixel := 3;
TargetColorScheme := csBGR;
TargetBitsPerSample := 8;
TargetSamplesPerPixel := 3;
end;
PixelFormat := pf24Bit;
// PhotoYCC format uses CCIR Recommendation 709 coefficients and is subsampled
// by factor 2 vertically and horizontally
ColorManager.SetYCbCrParameters([0.2125, 0.7154, 0.0721], 2, 2);
Progress(Self, psEnding, 0, False, FProgressRect, '');
if False then
begin
// if Overview then ... no info yet about overview image structure
end
else
begin
YY := YCbCrData[0];
C1 := YCbCrData[1];
C2 := YCbCrData[2];
I := 0;
Progress(Self, psStarting, 0, False, FProgressRect, gesLoadingData);
while I < Rows do
begin
Progress(Self, psRunning, MulDiv(I, 100, Rows), False, FProgressRect, '');
ReadBuffer(YY^, Columns);
Inc(YY, Width);
ReadBuffer(YY^, Columns);
Inc(YY, Width);
ReadBuffer(C1^, Columns shr 1);
Inc(C1, Width);
ReadBuffer(C2^, Columns shr 1);
Inc(C2, Width);
Inc(I, 2);
end;
Progress(Self, psEnding, 0, False, FProgressRect, '');
Progress(Self, psStarting, 0, False, FProgressRect, gesUpsampling);
// Y stands here for maximum number of upsample calls
Y := 5;
if ImageIndex >= 3 then
begin
Inc(Y, 3 * (ImageIndex - 3));
Decoder := TPCDDecoder.Create(Stream);
SourceDummy := @YCbCrData;
DestDummy := nil;
try
// recover luminance deltas for 1536 x 1024 image
Progress(Self, psRunning, MulDiv(0, 100, Y), False, FProgressRect, '');
Upsample(768, 512, Width, YCbCrData[0]);
Progress(Self, psRunning, MulDiv(1, 100, Y), False, FProgressRect, '');
Upsample(384, 256, Width, YCbCrData[1]);
Progress(Self, psRunning, MulDiv(2, 100, Y), False, FProgressRect, '');
Upsample(384, 256, Width, YCbCrData[2]);
Seek(4 * $800, soFromCurrent);
Decoder.Decode(SourceDummy, DestDummy, Width, 1024);
if ImageIndex >= 4 then
begin
// recover luminance deltas for 3072 x 2048 image
Progress(Self, psRunning, MulDiv(3, 100, Y), False, FProgressRect, '');
Upsample(1536, 1024, Width, YCbCrData[0]);
Progress(Self, psRunning, MulDiv(4, 100, Y), False, FProgressRect, '');
Upsample(768, 512, Width, YCbCrData[1]);
Progress(Self, psRunning, MulDiv(5, 100, Y), False, FProgressRect, '');
Upsample(768, 512, Width, YCbCrData[2]);
Offset := (Position - Integer(FBasePosition)) div $800 + 12;
Seek(FBasePosition + Offset * $800, soFromBeginning);
Decoder.Decode(SourceDummy, DestDummy, Width, 2048);
if ImageIndex = 5 then
begin
// recover luminance deltas for 6144 x 4096 image (vaporware)
Progress(Self, psRunning, MulDiv(6, 100, Y), False, FProgressRect, '');
Upsample(3072, 2048, Width, YCbCrData[1]);
Progress(Self, psRunning, MulDiv(7, 100, Y), False, FProgressRect, '');
Upsample(1536, 1024, Width, YCbCrData[1]);
Progress(Self, psRunning, MulDiv(8, 100, Y), False, FProgressRect, '');
Upsample(1536, 1024, Width, YCbCrData[2]);
end;
end;
finally
Decoder.Free;
end;
end;
Progress(Self, psRunning, MulDiv(Y - 1, 100, Y), False, FProgressRect, '');
Upsample(Width shr 1, Height shr 1, Width, YCbCrData[1]);
Progress(Self, psRunning, MulDiv(Y, 100, Y), False, FProgressRect, '');
Upsample(Width shr 1, Height shr 1, Width, YCbCrData[2]);
Progress(Self, psEnding, 0, False, FProgressRect, '');
Progress(Self, psStarting, 0, False, FProgressRect, gesTransfering);
// transfer luminance and chrominance channels
YY := YCbCrData[0];
C1 := YCbCrData[1];
C2 := YCbCrData[2];
// For the rotated mode where we need to turn the image by 90°. We can speed up loading
// the image by factor 2 by using a local copy of the Scanline pointers.
if Rotate in [1, 3] then
begin
Self.Width := Height;
Self.Height := Width;
FProgressRect.Right := Height;
SetLength(ScanLines, Width);
for Y := 0 to Width - 1 do ScanLines[Y] := ScanLine[Y];
GetMem(LineBuffer, 3 * Width);
end
else
begin
ScanLines := nil;
Self.Width := Width;
Self.Height := Height;
LineBuffer := nil;
end;
try
case Rotate of
1: // rotate -90°
begin
for Y := 0 to Height - 1 do
begin
ColorManager.ConvertRow([YY, C1, C2], LineBuffer, Width, $FF);
Inc(YY, Width);
Inc(C1, Width);
Inc(C2, Width);
Run := LineBuffer;
for X := 0 to Width - 1 do
begin
PByte(Line) := @(PByte(ScanLines[Width - X - 1])[Y * 3]);
Line^ := Run^;
Inc(Run);
end;
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
3: // rotate 90°
begin
for Y := 0 to Height - 1 do
begin
ColorManager.ConvertRow([YY, C1, C2], LineBuffer, Width, $FF);
Inc(YY, Width);
Inc(C1, Width);
Inc(C2, Width);
Run := LineBuffer;
for X := 0 to Width - 1 do
begin
PByte(Line) := @(PByte(ScanLines[X])[(Height - Y - 1) * 3]);
Line^ := Run^;
Inc(Run);
end;
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
else
for Y := 0 to Height - 1 do
begin
ColorManager.ConvertRow([YY, C1, C2], ScanLine[Y], Width, $FF);
Inc(YY, Width);
Inc(C1, Width);
Inc(C2, Width);
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
Progress(Self, psEnding, 0, False, FProgressRect, '');
finally
ScanLines := nil;
if Assigned(LineBuffer) then FreeMem(LineBuffer);
end;
end;
finally
if Assigned(YCbCrData[2]) then FreeMem(YCbCrData[2]);
if Assigned(YCbCrData[1]) then FreeMem(YCbCrData[1]);
if Assigned(YCbCrData[0]) then FreeMem(YCbCrData[0]);
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TPCDGraphic.ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean;
var
Header: array of Byte;
Temp: Cardinal;
begin
if ImageIndex > 5 then ImageIndex := 5;
Result := inherited ReadImageProperties(Stream, ImageIndex) and
((Stream.Size - Integer(FBasePosition)) > 3 * $800);
with Stream, FImageProperties do
begin
SetLength(Header, 3 * $800);
ReadBuffer(Header[0], Length(Header));
try
Overview := StrLComp(PAnsiChar(@Header[0]), 'PCD_OPA', 7) = 0;
// determine if image is a PhotoCD image
if Overview or (StrLComp(PAnsiChar(@Header[$800]), 'PCD', 3) = 0) then
begin
Rotate := Header[$0E02] and 3;
// image sizes are fixed, depending on the given image index
if Overview then ImageIndex := 0;
Width := 192 shl ImageIndex;
Height := 128 shl ImageIndex;
if (Rotate = 1) or (Rotate = 3) then
begin
Temp := Width;
Width := Height;
Height := Temp;
end;
ColorScheme := csPhotoYCC;
BitsPerSample := 8;
SamplesPerPixel := 3;
BitsPerPixel := BitsPerSample * SamplesPerPixel;
if ImageIndex > 2 then Compression := ctPCDHuffmann
else Compression := ctNone;
ImageCount := (Header[10] shl 8) or Header[11];
Result := True;
end;
finally
Header := nil;
end;
end;
end;
{$endif} // PCDGraphic
//----------------- TPPMGraphic ----------------------------------------------------------------------------------------
{$ifdef PortableMapGraphic}
class function TPPMGraphic.CanLoad(Stream: TStream): Boolean;
var
Buffer: array[0..9] of Byte;
LastPosition: Cardinal;
begin
with Stream do
begin
LastPosition := Position;
Result := (Size - Position) > 10;
if Result then
begin
ReadBuffer(Buffer, SizeOf(Buffer));
Result := (Buffer[0] = ord('P')) and (Buffer[1] in [ord('1')..ord('6')]);
end;
Position := LastPosition;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TPPMGraphic.CurrentChar: AnsiChar;
begin
if FIndex = SizeOf(FBuffer) then Result := #0
else Result := FBuffer[FIndex];
end;
//----------------------------------------------------------------------------------------------------------------------
function TPPMGraphic.GetChar: AnsiChar;
// buffered I/O
begin
if FIndex = SizeOf(FBuffer) then
begin
if FStream.Position = FStream.Size then GraphicExError(gesStreamReadError, ['PPM']);
FIndex := 0;
FStream.Read(FBuffer, SizeOf(FBuffer));
end;
Result := FBuffer[FIndex];
Inc(FIndex);
end;
//----------------------------------------------------------------------------------------------------------------------
function TPPMGraphic.GetNumber: Cardinal;
// reads the next number from the stream (and skips all characters which are not in 0..9)
var
Ch: AnsiChar;
begin
// skip all non-numbers
repeat
Ch := GetChar;
// skip comments
if Ch = '#' then
begin
ReadLine;
Ch := GetChar;
end;
until Ch in ['0'..'9'];
// read the number characters and convert meanwhile
Result := 0;
repeat
Result := 10 * Result + Ord(Ch) - $30;
Ch := GetChar;
until not (Ch in ['0'..'9']);
end;
//----------------------------------------------------------------------------------------------------------------------
function TPPMGraphic.ReadLine: AnsiString;
// reads one text line from stream and skips comments
var
Ch: AnsiChar;
I: Integer;
begin
Result := '';
repeat
Ch := GetChar;
if Ch in [#13, #10] then Break
else Result := Result + Ch;
until False;
// eat #13#10 combination
if (Ch = #13) and (CurrentChar = #10) then GetChar;
// delete comments
I := Pos(AnsiString('#'), Result);
if I > 0 then Delete(Result, I, MaxInt);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TPPMGraphic.LoadFromStream(Stream: TStream);
var
Buffer: AnsiString;
Line24: PBGR;
Line8: PByte;
X, Y: Integer;
Pixel: Byte;
begin
Handle := 0;
FBasePosition := Stream.Position;
// copy reference for buffered access
FStream := Stream;
if ReadImageProperties(Stream, 0) then
begin
with FImageProperties do
begin
Stream.Position := FBasePosition;
FProgressRect := Rect(0, 0, Width, 1);
Progress(Self, psStarting, 0, False, FProgressRect, gesTransfering);
// set index pointer to end of buffer to cause reload
FIndex := SizeOf(FBuffer);
with Stream do
begin
Buffer := ReadLine;
case StrToInt(String(Buffer[2])) of
1: // PBM ASCII format (black & white)
begin
PixelFormat := pf1Bit;
Self.Width := GetNumber;
Self.Height := GetNumber;
ColorManager.TargetSamplesPerPixel := 1;
ColorManager.TargetBitsPerSample := 1;
Palette := ColorManager.CreateGrayScalePalette(True);
// read image data
for Y := 0 to Height - 1 do
begin
Line8 := ScanLine[Y];
Pixel := 0;
for X := 1 to Width do
begin
Pixel := (Pixel shl 1) or (GetNumber and 1);
if (X mod 8) = 0 then
begin
Line8^ := Pixel;
Inc(Line8);
Pixel := 0;
end;
end;
if (Width mod 8) <> 0 then Line8^ := Pixel shl (8 - (Width mod 8));
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
2: // PGM ASCII form (gray scale)
begin
PixelFormat := pf8Bit;
Self.Width := GetNumber;
Self.Height := GetNumber;
// skip maximum color value
GetNumber;
ColorManager.TargetSamplesPerPixel := 1;
ColorManager.TargetBitsPerSample := 8;
Palette := ColorManager.CreateGrayScalePalette(False);
// read image data
for Y := 0 to Height - 1 do
begin
Line8 := ScanLine[Y];
for X := 0 to Width - 1 do
begin
Line8^ := GetNumber;
Inc(Line8);
end;
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
3: // PPM ASCII form (true color)
begin
PixelFormat := pf24Bit;
Self.Width := GetNumber;
Self.Height := GetNumber;
// skip maximum color value
GetNumber;
for Y := 0 to Height - 1 do
begin
Line24 := ScanLine[Y];
for X := 0 to Width - 1 do
begin
Line24.R := GetNumber;
Line24.G := GetNumber;
Line24.B := GetNumber;
Inc(Line24);
end;
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
4: // PBM binary format (black & white)
begin
PixelFormat := pf1Bit;
Self.Width := GetNumber;
Self.Height := GetNumber;
ColorManager.TargetSamplesPerPixel := 1;
ColorManager.TargetBitsPerSample := 1;
Palette := ColorManager.CreateGrayScalePalette(True);
// read image data
for Y := 0 to Height - 1 do
begin
Line8 := ScanLine[Y];
for X := 0 to (Width div 8) - 1 do
begin
Line8^ := Byte(GetChar);
Inc(Line8);
end;
if (Width mod 8) <> 0 then Line8^ := Byte(GetChar);
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
5: // PGM binary form (gray scale)
begin
PixelFormat := pf8Bit;
Self.Width := GetNumber;
Self.Height := GetNumber;
// skip maximum color value
GetNumber;
ColorManager.TargetSamplesPerPixel := 1;
ColorManager.TargetBitsPerSample := 8;
Palette := ColorManager.CreateGrayScalePalette(False);
// read image data
for Y := 0 to Height - 1 do
begin
Line8 := ScanLine[Y];
for X := 0 to Width - 1 do
begin
Line8^ := Byte(GetChar);
Inc(Line8);
end;
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
6: // PPM binary form (true color)
begin
PixelFormat := pf24Bit;
Self.Width := GetNumber;
Self.Height := GetNumber;
// skip maximum color value
GetNumber;
// Pixel values are store linearly (but RGB instead BGR).
// There's one allowed white space which will automatically be skipped by the first
// GetChar call below
// now read the pixels
for Y := 0 to Height - 1 do
begin
Line24 := ScanLine[Y];
for X := 0 to Width - 1 do
begin
Line24.R := Byte(GetChar);
Line24.G := Byte(GetChar);
Line24.B := Byte(GetChar);
Inc(Line24);
end;
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
end;
end;
Progress(Self, psEnding, 0, False, FProgressRect, '');
end;
end
else GraphicExError(gesInvalidImage, ['PBM, PGM or PPM']);
end;
//----------------------------------------------------------------------------------------------------------------------
function TPPMGraphic.ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean;
var
Buffer: AnsiString;
begin
Result := inherited ReadImageProperties(Stream, ImageIndex);
with Stream, FImageProperties do
begin
// set index pointer to end of buffer to cause reload
FIndex := SizeOf(FBuffer);
Buffer := ReadLine;
Compression := ctNone;
if Buffer[1] = 'P' then
begin
case StrToInt(String(Buffer[2])) of
1: // PBM ASCII format (black & white)
begin
Width := GetNumber;
Height := GetNumber;
SamplesPerPixel := 1;
BitsPerSample := 1;
ColorScheme := csIndexed;
BitsPerPixel := SamplesPerPixel * BitsPerSample;
end;
2: // PGM ASCII form (gray scale)
begin
Width := GetNumber;
Height := GetNumber;
// skip maximum color value
GetNumber;
SamplesPerPixel := 1;
BitsPerSample := 8;
ColorScheme := csIndexed;
BitsPerPixel := SamplesPerPixel * BitsPerSample;
end;
3: // PPM ASCII form (true color)
begin
Width := GetNumber;
Height := GetNumber;
// skip maximum color value
GetNumber;
SamplesPerPixel := 3;
BitsPerSample := 8;
ColorScheme := csRGB;
BitsPerPixel := SamplesPerPixel * BitsPerSample;
end;
4: // PBM binary format (black & white)
begin
Width := GetNumber;
Height := GetNumber;
SamplesPerPixel := 1;
BitsPerSample := 1;
ColorScheme := csIndexed;
BitsPerPixel := SamplesPerPixel * BitsPerSample;
end;
5: // PGM binary form (gray scale)
begin
Width := GetNumber;
Height := GetNumber;
// skip maximum color value
GetNumber;
SamplesPerPixel := 1;
BitsPerSample := 8;
ColorScheme := csIndexed;
BitsPerPixel := SamplesPerPixel * BitsPerSample;
end;
6: // PPM binary form (true color)
begin
Width := GetNumber;
Height := GetNumber;
// skip maximum color value
GetNumber;
SamplesPerPixel := 3;
BitsPerSample := 8;
ColorScheme := csRGB;
BitsPerPixel := SamplesPerPixel * BitsPerSample;
end;
end;
Result := True;
end;
end;
end;
{$endif} // PortableMapGraphic
//----------------- TCUTGraphic ----------------------------------------------------------------------------------------
{$ifdef CUTGraphic}
class function TCUTGraphic.CanLoad(Stream: TStream): Boolean;
// Note: cut files cannot be determined from stream because the only information
// is width and height of the image at stream/image start which is by no means
// enough to identify a cut (or any other) image.
begin
Result := False;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCUTGraphic.LoadFromFile(const FileName: String);
// overridden to extract an implicit palette file name
begin
FPaletteFile := ChangeFileExt(FileName, '.pal');
inherited;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCUTGraphic.LoadFromStream(Stream: TStream);
var
Buffer: PByte;
Run,
Line: Pointer;
Decoder: TCUTRLEDecoder;
CUTSize: Cardinal;
Y: Integer;
begin
Handle := 0;
FBasePosition := Stream.Position;
if ReadImageProperties(Stream, 0) then
begin
with Stream, FImageProperties do
begin
Position := FBasePosition + 6;
FProgressRect := Rect(0, 0, Width, 0);
Progress(Self, psStarting, 0, False, FProgressRect, gesTransfering);
PixelFormat := pf8Bit;
Self.Width := Width;
Self.Height := Height;
LoadPalette;
CutSize := Stream.Size - Stream.Position;
Decoder := TCUTRLEDecoder.Create;
Buffer := nil;
try
GetMem(Buffer, CutSize);
Stream.ReadBuffer(Buffer^, CUTSize);
Run := Buffer;
for Y := 0 to Height - 1 do
begin
Line := ScanLine[Y];
Decoder.Decode(Run, Line, 0, Width);
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
finally
Decoder.Free;
if Assigned(Buffer) then FreeMem(Buffer);
end;
Progress(Self, psEnding, 0, False, FProgressRect, '');
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TCUTGraphic.ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean;
var
Dummy: Word;
begin
inherited ReadImageProperties(Stream, ImageIndex);
with Stream, FImageProperties do
begin
PixelFormat := pf8Bit;
ReadBuffer(Dummy, SizeOf(Dummy));
Width := Dummy;
ReadBuffer(Dummy, SizeOf(Dummy));
Height := Dummy;
ColorScheme := csIndexed;
BitsPerSample := 8;
SamplesPerPixel := 1;
BitsPerPixel := BitsPerSample * SamplesPerPixel;
Compression := ctRLE;
Result := True;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
type
// the palette file header is actually more complex than the
// image file's header, funny...
PHaloPaletteHeader = ^THaloPaletteHeader;
THaloPaletteHeader = packed record
ID: array[0..1] of AnsiChar; // should be 'AH'
Version,
Size: Word;
FileType,
SubType: Byte;
BrdID,
GrMode: Word;
MaxIndex,
MaxRed,
MaxGreen,
MaxBlue: Word; // colors = MaxIndex + 1
Signature: array[0..7] of AnsiChar; // 'Dr. Halo'
Filler: array[0..11] of Byte;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCUTGraphic.LoadPalette;
var
Header: PHaloPaletteHeader;
LogPalette: TMaxLogPalette;
I: Integer;
Buffer: array[0..511] of Byte;
Run: PWord;
begin
LogPalette.palVersion := $300;
if FileExists(FPaletteFile) then
begin
with TFileStream.Create(FPaletteFile, fmOpenRead or fmShareDenyNone) do
try
// quite strange file organization here, we need always to load 512 bytes blocks
// and skip occasionally some bytes
ReadBuffer(Buffer, SizeOf(Buffer));
Header := @Buffer;
LogPalette.palNumEntries := Header.MaxIndex + 1;
Run := @Buffer;
Inc(PByte(Run), SizeOf(Header^));
for I := 0 to LogPalette.palNumEntries - 1 do
begin
// load next 512 bytes buffer if necessary
if (Integer(Run) - Integer(@Buffer)) > 506 then
begin
ReadBuffer(Buffer, SizeOf(Buffer));
Run := @Buffer;
end;
LogPalette.palPalEntry[I].peRed := Run^;
Inc(Run);
LogPalette.palPalEntry[I].peGreen := Run^;
Inc(Run);
LogPalette.palPalEntry[I].peBlue := Run^;
Inc(Run);
end;
finally
Free;
end;
end
else
begin
LogPalette.palNumEntries := 256;
// no external palette so use gray scale
for I := 0 to 255 do
begin
LogPalette.palPalEntry[I].peBlue := I;
LogPalette.palPalEntry[I].peGreen := I;
LogPalette.palPalEntry[I].peRed := I;
end;
end;
// finally create palette
Palette := CreatePalette(PLogPalette(@LogPalette)^);
end;
{$endif} // CUTGraphic
//----------------- TGIFGraphic ----------------------------------------------------------------------------------------
{$ifdef GIFGraphic}
const
// logical screen descriptor packed field masks
GIF_GLOBALCOLORTABLE = $80;
GIF_COLORRESOLUTION = $70;
GIF_GLOBALCOLORTABLESORTED = $08;
GIF_COLORTABLESIZE = $07;
// image flags
GIF_LOCALCOLORTABLE = $80;
GIF_INTERLACED = $40;
GIF_LOCALCOLORTABLESORTED= $20;
// block identifiers
GIF_PLAINTEXT = $01;
GIF_GRAPHICCONTROLEXTENSION = $F9;
GIF_COMMENTEXTENSION = $FE;
GIF_APPLICATIONEXTENSION = $FF;
GIF_IMAGEDESCRIPTOR = Ord(',');
GIF_EXTENSIONINTRODUCER = Ord('!');
GIF_TRAILER = Ord(';');
type
TGIFHeader = packed record
Signature: array[0..2] of AnsiChar; // magic ID 'GIF'
Version: array[0..2] of AnsiChar; // '87a' or '89a'
end;
TLogicalScreenDescriptor = packed record
ScreenWidth: Word;
ScreenHeight: Word;
PackedFields,
BackgroundColorIndex, // index into global color table
AspectRatio: Byte; // actual ratio = (AspectRatio + 15) / 64
end;
TImageDescriptor = packed record
//Separator: Byte; // leave that out since we always read one bye ahead
Left: Word; // X position of image with respect to logical screen
Top: Word; // Y position
Width: Word;
Height: Word;
PackedFields: Byte;
end;
//----------------------------------------------------------------------------------------------------------------------
class function TGIFGraphic.CanLoad(Stream: TStream): Boolean;
var
Header: TGIFHeader;
LastPosition: Cardinal;
begin
with Stream do
begin
LastPosition := Position;
Result := (Size - Position) > (SizeOf(TGIFHeader) + SizeOf(TLogicalScreenDescriptor) + SizeOf(TImageDescriptor));
if Result then
begin
ReadBuffer(Header, SizeOf(Header));
Result := UpperCase(Header.Signature) = 'GIF';
end;
Position := LastPosition;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TGIFGraphic.SkipExtensions: Byte;
// Skips all blocks until an image block has been found in the data stream.
// Result is the image block ID if an image block could be found.
var
Increment: Byte;
begin
with FStream do
begin
// iterate through the blocks until first image is found
repeat
ReadBuffer(Result, 1);
if Result = GIF_EXTENSIONINTRODUCER then
begin
// skip any extension
ReadBuffer(Result, 1);
case Result of
GIF_PLAINTEXT:
begin
// block size of text grid data
ReadBuffer(Increment, 1);
Seek(Increment, soFromCurrent);
// skip variable lengthed text block
repeat
// block size
ReadBuffer(Increment, 1);
if Increment = 0 then Break;
Seek(Increment, soFromCurrent);
until False;
end;
GIF_GRAPHICCONTROLEXTENSION:
begin
// block size
ReadBuffer(Increment, 1);
// skip block and its terminator
Seek(Increment + 1, soFromCurrent);
end;
GIF_COMMENTEXTENSION:
repeat
// block size
ReadBuffer(Increment, 1);
if Increment = 0 then Break;
Seek(Increment, soFromCurrent);
until False;
GIF_APPLICATIONEXTENSION:
begin
// application id and authentication code plus potential application data
repeat
ReadBuffer(Increment, 1);
if Increment = 0 then Break;
Seek(Increment, soFromCurrent);
until False;
end;
end;
end;
until (Result = GIF_IMAGEDESCRIPTOR) or (Result = GIF_TRAILER);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TGIFGraphic.LoadFromStream(Stream: TStream);
var
Header: TGIFHeader;
ScreenDescriptor: TLogicalScreenDescriptor;
ImageDescriptor: TImageDescriptor;
LogPalette: TMaxLogPalette;
I: Cardinal;
BlockID: Byte;
InitCodeSize: Byte;
RawData,
Run: PByte;
TargetBuffer,
TargetRun,
Line: Pointer;
Pass,
Increment,
Marker: Integer;
Decoder: TDecoder;
begin
// release old image
Handle := 0;
FBasePosition := Stream.Position;
FStream := Stream;
if ReadImageProperties(Stream, 0) then
begin
with Stream, FImageProperties do
begin
Position := FBasePosition;
FProgressRect := Rect(0, 0, Width, 1);
Progress(Self, psStarting, 0, False, FProgressRect, gesPreparing);
ReadBuffer(Header, SizeOf(Header));
PixelFormat := pf8Bit;
// general information
ReadBuffer(ScreenDescriptor, SizeOf(ScreenDescriptor));
ZeroMemory(@LogPalette, SizeOf(LogPalette));
LogPalette.palVersion := $300;
// read global color table if given
if (ScreenDescriptor.PackedFields and GIF_GLOBALCOLORTABLE) <> 0 then
begin
// the global color table immediately follows the screen descriptor
LogPalette.palNumEntries := 2 shl (ScreenDescriptor.PackedFields and GIF_COLORTABLESIZE);
for I := 0 to LogPalette.palNumEntries - 1 do
begin
ReadBuffer(LogPalette.palPalEntry[I].peRed, 1);
ReadBuffer(LogPalette.palPalEntry[I].peGreen, 1);
ReadBuffer(LogPalette.palPalEntry[I].peBlue, 1);
end;
// finally create palette
Palette := CreatePalette(PLogPalette(@LogPalette)^);
end;
BlockID := SkipExtensions;
Progress(Self, psEnding, 0, False, FProgressRect, '');
// image found?
if BlockID = GIF_IMAGEDESCRIPTOR then
begin
Progress(Self, psStarting, 0, False, FProgressRect, gesLoadingData);
ReadBuffer(ImageDescriptor, SizeOf(TImageDescriptor));
Self.Width := Width;
Self.Height := Height;
// if there is a local color table then override the already set one
if (ImageDescriptor.PackedFields and GIF_LOCALCOLORTABLE) <> 0 then
begin
// the global color table immediately follows the image descriptor
LogPalette.palNumEntries := 2 shl (ImageDescriptor.PackedFields and GIF_COLORTABLESIZE);
for I := 0 to LogPalette.palNumEntries - 1 do
begin
ReadBuffer(LogPalette.palPalEntry[I].peRed, 1);
ReadBuffer(LogPalette.palPalEntry[I].peGreen, 1);
ReadBuffer(LogPalette.palPalEntry[I].peBlue, 1);
end;
Palette := CreatePalette(PLogPalette(@LogPalette)^);
end;
ReadBuffer(InitCodeSize, 1);
// decompress data in one step
// 1) count data
Marker := Position;
Pass := 0;
Increment := 0;
repeat
if Read(Increment, 1) = 0 then Break;
Inc(Pass, Increment);
Seek(Increment, soFromCurrent);
until Increment = 0;
// 2) allocate enough memory
GetMem(RawData, Pass);
// add one extra line of extra memory for badly coded images
GetMem(TargetBuffer, Width * (Height + 1));
try
// 3) read and decode data
Position := Marker;
Increment := 0;
Run := RawData;
repeat
if Read(Increment, 1) = 0 then Break;
Read(Run^, Increment);
Inc(Run, Increment);
until Increment = 0;
Decoder := TGIFLZWDecoder.Create(InitCodeSize);
try
Run := RawData;
Decoder.Decode(Pointer(Run), TargetBuffer, Pass, Width * Height);
finally
Decoder.Free;
end;
Progress(Self, psEnding, 0, False, FProgressRect, '');
// finally transfer image data
Progress(Self, psStarting, 0, False, FProgressRect, gesTransfering);
if (ImageDescriptor.PackedFields and GIF_INTERLACED) = 0 then
begin
TargetRun := TargetBuffer;
for I := 0 to Height - 1 do
begin
Line := Scanline[I];
Move(TargetRun^, Line^, Width);
Inc(PByte(TargetRun), Width);
Progress(Self, psRunning, MulDiv(I, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end
else
begin
TargetRun := TargetBuffer;
// interlaced image, need to move in four passes
for Pass := 0 to 3 do
begin
// determine start line and increment of the pass
case Pass of
0:
begin
I := 0;
Increment := 8;
end;
1:
begin
I := 4;
Increment := 8;
end;
2:
begin
I := 2;
Increment := 4;
end;
else
I := 1;
Increment := 2;
end;
while I < Height do
begin
Line := Scanline[I];
Move(TargetRun^, Line^, Width);
Inc(PByte(TargetRun), Width);
Inc(I, Increment);
if Pass = 3 then
begin
// progress events only for last (and most expensive) run
Progress(Self, psRunning, MulDiv(I, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
end;
end;
Progress(Self, psEnding, 0, False, FProgressRect, '');
finally
if Assigned(TargetBuffer) then FreeMem(TargetBuffer);
if Assigned(RawData) then FreeMem(RawData);
end;
end;
end;
end
else GraphicExError(gesInvalidImage, ['GIF']);
end;
//----------------------------------------------------------------------------------------------------------------------
function TGIFGraphic.ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean;
var
Header: TGIFHeader;
ScreenDescriptor: TLogicalScreenDescriptor;
ImageDescriptor: TImageDescriptor;
BlockID: Integer;
begin
Result := inherited ReadImageProperties(Stream, ImageIndex);
with Stream, FImageProperties do
begin
ReadBuffer(Header, SizeOf(Header));
if UpperCase(Header.Signature) = 'GIF' then
begin
Version := StrToInt(Copy(Header.Version, 1, 2));
ColorScheme := csIndexed;
SamplesPerPixel := 1;
// might be overwritten
BitsPerSample := 8;
Compression := ctLZW;
// general information
ReadBuffer(ScreenDescriptor, SizeOf(ScreenDescriptor));
// skip global color table if given
if (ScreenDescriptor.PackedFields and GIF_GLOBALCOLORTABLE) <> 0 then
begin
BitsPerSample := (ScreenDescriptor.PackedFields and GIF_COLORTABLESIZE) + 1;
// the global color table immediately follows the screen descriptor
Seek(3 * (1 shl BitsPerSample), soFromCurrent);
end;
BlockID := SkipExtensions;
// image found?
if BlockID = GIF_IMAGEDESCRIPTOR then
begin
ReadBuffer(ImageDescriptor, SizeOf(TImageDescriptor));
Width := ImageDescriptor.Width;
if Width = 0 then Width := ScreenDescriptor.ScreenWidth;
Height := ImageDescriptor.Height;
if Height = 0 then Height := ScreenDescriptor.ScreenHeight;
// if there is a local color table then override the already set one
LocalColorTable := (ImageDescriptor.PackedFields and GIF_LOCALCOLORTABLE) <> 0;
if LocalColorTable then
BitsPerSample := (ImageDescriptor.PackedFields and GIF_LOCALCOLORTABLE) + 1;
Interlaced := (ImageDescriptor.PackedFields and GIF_INTERLACED) <> 0;
end;
BitsPerPixel := SamplesPerPixel * BitsPerSample;
Result := True;
end;
end;
end;
{$endif} // GIFGraphic
//----------------- TRLAGraphic ----------------------------------------------------------------------------------------
{$ifdef RLAGraphic}
// This implementation is based on code from Dipl. Ing. Ingo Neumann (ingo@upstart.de, ingo_n@dialup.nacamar.de).
type
TRLAWindow = packed record
Left,
Right,
Bottom,
Top: SmallInt;
end;
TRLAHeader = packed record
Window, // overall image size
Active_window: TRLAWindow; // size of non-zero portion of image (we use this as actual image size)
Frame, // frame number if part of a sequence
Storage_type, // type of image channels (0 - integer data, 1 - float data)
Num_chan, // samples per pixel (usually 3: r, g, b)
Num_matte, // number of matte channels (usually only 1)
Num_aux, // number of auxiliary channels, usually 0
Revision: SmallInt; // always $FFFE
Gamma: array[0..15] of AnsiChar; // gamma single value used when writing the image
Red_pri: array[0..23] of AnsiChar; // used chromaticity for red channel (typical format: "%7.4f %7.4f")
Green_pri: array[0..23] of AnsiChar; // used chromaticity for green channel
Blue_pri: array[0..23] of AnsiChar;// used chromaticity for blue channel
White_pt: array[0..23] of AnsiChar;// used chromaticity for white point
Job_num: Integer; // rendering speciifc
Name: array[0..127] of AnsiChar; // original file name
Desc: array[0..127] of AnsiChar; // a file description
ProgramName: array[0..63] of AnsiChar; // name of program which created the image
Machine: array[0..31] of AnsiChar; // name of computer on which the image was rendered
User: array[0..31] of AnsiChar; // user who ran the creation program of the image
Date: array[0..19] of AnsiChar; // creation data of image (ex: Sep 30 12:29 1993)
Aspect: array[0..23] of AnsiChar; // aspect format of the file (external resource)
Aspect_ratio: array[0..7] of AnsiChar; // float number Width /Height
Chan: array[0..31] of AnsiChar; // color space (can be: rgb, xyz, sampled or raw)
Field: SmallInt; // 0 - non-field rendered data, 1 - field rendered data
Time: array[0..11] of AnsiChar; // time needed to create the image (used when rendering)
Filter: array[0..31] of AnsiChar; // filter name to post-process image data
Chan_bits, // bits per sample
Matte_type, // type of matte channel (see aux_type)
Matte_bits, // precision of a pixel's matte channel (1..32)
Aux_type, // type of aux channel (0 - integer data; 4 - single (float) data
Aux_bits: SmallInt; // bits precision of the pixel's aux channel (1..32 bits)
Aux: array[0..31] of AnsiChar; // auxiliary channel as either range or depth
Space: array[0..35] of Byte; // unused
Next: Integer; // offset for next header if multi-frame image
end;
//----------------------------------------------------------------------------------------------------------------------
class function TRLAGraphic.CanLoad(Stream: TStream): Boolean;
var
Header: TRLAHeader;
LastPosition: Cardinal;
begin
with Stream do
begin
LastPosition := Position;
Result := (Size - Position) > SizeOf(Header);
if Result then
begin
ReadBuffer(Header, SizeOf(Header));
Result := (Swap(Word(Header.Revision)) = $FFFE) and
((LowerCase(String(Header.Chan)) = 'rgb') or
(LowerCase(String(Header.Chan)) = 'xyz'));
end;
Position := LastPosition;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TRLAGraphic.LoadFromStream(Stream: TStream);
var
Offsets: TCardinalArray;
RLELength: Word;
Line: Pointer;
Y: Integer;
// RLE buffers
RawBuffer,
RedBuffer,
GreenBuffer,
BlueBuffer,
AlphaBuffer: Pointer;
Decoder: TRLADecoder;
begin
// free previous image data
Handle := 0;
FBasePosition := Stream.Position;
if ReadImageProperties(Stream, 0) then
begin
with Stream, FImageProperties do
begin
FProgressRect := Rect(0, 0, Width, 1);
Progress(Self, psStarting, 0, False, FProgressRect, gesTransfering);
with ColorManager do
begin
SourceSamplesPerPixel := SamplesPerPixel;
TargetSamplesPerPixel := SamplesPerPixel;
SourceBitsPerSample := BitsPerSample;
if BitsPerSample > 8 then TargetBitsPerSample := 8
else TargetBitsPerSample := BitsPerSample;
SourceColorScheme := ColorScheme;
if ColorScheme = csRGBA then TargetColorScheme := csBGRA
else TargetColorScheme := csBGR;
PixelFormat := TargetPixelFormat;
if FileGamma <> 1 then
begin
SetGamma(FileGamma);
TargetOptions := TargetOptions + [coApplyGamma];
Include(Options, ioUseGamma);
end;
end;
// dimension of image, top might be larger than bottom denoting a bottom up image
Self.Width := Width;
Self.Height := Height;
// each scanline is organized in RLE compressed strips whose location in the stream
// is determined by the offsets table
SetLength(Offsets, Height);
ReadBuffer(Offsets[0], Height * SizeOf(Cardinal));
SwapLong(@Offsets[0], Height);
// setup intermediate storage
Decoder := TRLADecoder.Create;
RawBuffer := nil;
RedBuffer := nil;
GreenBuffer := nil;
BlueBuffer := nil;
AlphaBuffer := nil;
try
GetMem(RedBuffer, Width);
GetMem(GreenBuffer, Width);
GetMem(BlueBuffer, Width);
GetMem(AlphaBuffer, Width);
// no go for each scanline
for Y := 0 to Height - 1 do
begin
Stream.Position := FBasePosition + Offsets[Y];
if BottomUp then Line := ScanLine[Integer(Height) - Y - 1]
else Line := ScanLine[Y];
// read channel data to decode
// red
ReadBuffer(RLELength, SizeOf(RLELength));
RLELength := Swap(RLELength);
ReallocMem(RawBuffer, RLELength);
ReadBuffer(RawBuffer^, RLELength);
Decoder.Decode(RawBuffer, RedBuffer, RLELength, Width);
// green
ReadBuffer(RLELength, SizeOf(RLELength));
RLELength := Swap(RLELength);
ReallocMem(RawBuffer, RLELength);
ReadBuffer(RawBuffer^, RLELength);
Decoder.Decode(RawBuffer, GreenBuffer, RLELength, Width);
// blue
ReadBuffer(RLELength, SizeOf(RLELength));
RLELength := Swap(RLELength);
ReallocMem(RawBuffer, RLELength);
ReadBuffer(RawBuffer^, RLELength);
Decoder.Decode(RawBuffer, BlueBuffer, RLELength, Width);
if ColorManager.TargetColorScheme = csBGR then
begin
ColorManager.ConvertRow([RedBuffer, GreenBuffer, BlueBuffer], Line, Width, $FF);
end
else
begin
// alpha
ReadBuffer(RLELength, SizeOf(RLELength));
RLELength := Swap(RLELength);
ReallocMem(RawBuffer, RLELength);
ReadBuffer(RawBuffer^, RLELength);
Decoder.Decode(RawBuffer, AlphaBuffer, RLELength, Width);
ColorManager.ConvertRow([RedBuffer, GreenBuffer, BlueBuffer, AlphaBuffer], Line, Width, $FF);
end;
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
finally
if Assigned(RawBuffer) then FreeMem(RawBuffer);
if Assigned(RedBuffer) then FreeMem(RedBuffer);
if Assigned(GreenBuffer) then FreeMem(GreenBuffer);
if Assigned(BlueBuffer) then FreeMem(BlueBuffer);
if Assigned(AlphaBuffer) then FreeMem(AlphaBuffer);
Decoder.Free;
end;
Progress(Self, psEnding, 0, False, FProgressRect, '');
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TRLAGraphic.ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean;
var
Header: TRLAHeader;
begin
Result := inherited ReadImageProperties(Stream, ImageIndex);
with Stream, FImageProperties do
begin
ReadBuffer(Header, SizeOf(Header));
// data is always given in big endian order, so swap data which needs this
SwapHeader(Header);
Options := [ioBigEndian];
SamplesPerPixel := Header.num_chan;
if Header.num_matte = 1 then Inc(SamplesPerPixel);
BitsPerSample := Header.Chan_bits;
BitsPerPixel := SamplesPerPixel * BitsPerSample;
if LowerCase(String(Header.Chan)) = 'rgb' then
begin
if Header.num_matte > 0 then ColorScheme := csRGBA
else ColorScheme := csRGB;
end
else
if LowerCase(String(Header.Chan)) = 'xyz' then Exit;
try
FileGamma := StrToFloat(String(Header.Gamma));
except
end;
Compression := ctRLE;
// dimension of image, top might be larger than bottom denoting a bottom up image
Width := Header.Active_window.Right - Header.Active_window.Left + 1;
Height := Abs(Header.Active_window.Bottom - Header.Active_window.Top) + 1;
BottomUp := (Header.Active_window.Bottom - Header.Active_window.Top) < 0;
Result := True;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TRLAGraphic.SwapHeader(var Header);
// separate swap method to ease reading the main flow of the LoadFromStream method
begin
with TRLAHeader(Header) do
begin
SwapShort(@Window, 4);
SwapShort(@Active_window, 4);
Frame := Swap(Frame);
Storage_type := Swap(Storage_type);
Num_chan := Swap(Num_chan);
Num_matte := Swap(Num_matte);
Num_aux := Swap(Num_aux);
Revision := Swap(Revision);
Job_num := SwapLong(Job_num);
Field := Swap(Field);
Chan_bits := Swap(Chan_bits);
Matte_type := Swap(Matte_type);
Matte_bits := Swap(Matte_bits);
Aux_type := Swap(Aux_type);
Aux_bits := Swap(Aux_bits);
Next := SwapLong(Next);
end;
end;
{$endif} // RLAGraphic
//----------------- TPSDGraphic ----------------------------------------------------------------------------------------
{$ifdef PhotoshopGraphic}
const
// color modes
PSD_BITMAP = 0;
PSD_GRAYSCALE = 1;
PSD_INDEXED = 2;
PSD_RGB = 3;
PSD_CMYK = 4;
PSD_MULTICHANNEL = 7;
PSD_DUOTONE = 8;
PSD_LAB = 9;
PSD_COMPRESSION_NONE = 0;
PSD_COMPRESSION_RLE = 1; // RLE compression (same as TIFF packed bits)
type
TPSDHeader = packed record
Signature: array[0..3] of AnsiChar; // always '8BPS'
Version: Word; // always 1
Reserved: array[0..5] of Byte; // reserved, always 0
Channels: Word; // 1..24, number of channels in the image (including alpha)
Rows,
Columns: Cardinal; // 1..30000, size of image
Depth: Word; // 1, 8, 16 bits per channel
Mode: Word; // color mode (see constants above)
end;
//----------------------------------------------------------------------------------------------------------------------
class function TPSDGraphic.CanLoad(Stream: TStream): Boolean;
var
Header: TPSDHeader;
LastPosition: Cardinal;
begin
with Stream do
begin
LastPosition := Position;
Result := (Size - Position) > SizeOf(Header);
if Result then
begin
ReadBuffer(Header, SizeOf(Header));
Result := (UpperCase(String(Header.Signature)) = '8BPS') and
(Swap(Header.Version) = 1);
end;
Position := LastPosition;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TPSDGraphic.LoadFromStream(Stream: TStream);
var
Header: TPSDHeader;
Count: Cardinal;
Decoder: TDecoder;
RLELength: array of Word;
Y: Integer;
BPS: Cardinal; // bytes per sample either 1 or 2 for 8 bits per channel and 16 bits per channel respectively
ChannelSize: Integer; // size of one channel (taking BPS into account)
Increment: Integer; // pointer increment from one line to next
// RLE buffers
Line,
RawBuffer, // all image data compressed
Buffer: Pointer; // all iamge data uncompressed
Run1, // running pointer in Buffer 1
Run2, // etc.
Run3,
Run4: PByte;
RawPalette: array[0..767] of Byte;
begin
// free previous image data
Handle := 0;
FBasePosition := Stream.Position;
if ReadImageProperties(Stream, 0) then
begin
with Stream, FImageProperties do
begin
Position := FBasePosition;
FProgressRect := Rect(0, 0, Width, 1);
Progress(Self, psStarting, 0, False, FProgressRect, gesPreparing);
ReadBuffer(Header, SizeOf(Header));
// initialize color manager
with ColorManager do
begin
SourceOptions := [coNeedByteSwap];
SourceBitsPerSample := BitsPerSample;
if BitsPerSample = 16 then TargetBitsPerSample := 8
else TargetBitsPerSample := BitsPerSample;
SourceSamplesPerPixel := SamplesPerPixel;
TargetSamplesPerPixel := SamplesPerPixel;
// color space
SourceColorScheme := ColorScheme;
case ColorScheme of
csG,
csIndexed:
TargetColorScheme := ColorScheme;
csRGB:
TargetColorScheme := csBGR;
csRGBA:
TargetColorScheme := csBGRA;
csCMYK:
begin
TargetColorScheme := csBGR;
TargetSamplesPerPixel := 3;
end;
csCIELab:
begin
// PSD uses 0..255 for a and b so we need to convert them to -128..127
SourceOptions := SourceOptions + [coLabByteRange, coLabChromaOffset];
TargetColorScheme := csBGR;
end;
end;
end;
PixelFormat := ColorManager.TargetPixelFormat;
Self.Width := Width;
Self.Height := Height;
// size of palette
ReadBuffer(Count, SizeOf(Count));
Count := SwapLong(Count);
// setup the palette if necessary, color data immediately follows header
case ColorScheme of
csG:
Palette := ColorManager.CreateGrayscalePalette(ioMinIsWhite in Options);
csIndexed:
begin
ReadBuffer(RawPalette, Count);
Count := Count div 3;
Palette := ColorManager.CreateColorPalette([@RawPalette, @RawPalette[Count], @RawPalette[2 * Count]],
pfPlane8Triple, Count, False);
end;
end;
// skip resource and layers section
ReadBuffer(Count, SizeOf(Count));
Count := SwapLong(Count);
Seek(Count, soFromCurrent);
ReadBuffer(Count, SizeOf(Count));
Count := SwapLong(Count);
// +2 in order to skip the following compression value
Seek(Count + 2, soFromCurrent);
// now read out image data
RawBuffer := nil;
if Compression = ctPackedBits then
begin
Decoder := TPackbitsRLEDecoder.Create;
SetLength(RLELength, Height * Channels);
ReadBuffer(RLELength[0], 2 * Length(RLELength));
SwapShort(@RLELength[0], Height * Channels);
end
else Decoder := nil;
Progress(Self, psEnding, 0, False, FProgressRect, '');
try
case ColorScheme of
csG,
csIndexed:
begin
Progress(Self, psStarting, 0, False, FProgressRect, gesTransfering);
// very simple format here, we don't need the color conversion manager
if Assigned(Decoder) then
begin
// determine whole compressed size
Count := 0;
for Y := 0 to Height - 1 do Inc(Count, RLELength[Y]);
GetMem(RawBuffer, Count);
try
ReadBuffer(RawBuffer^, Count);
Run1 := RawBuffer;
for Y := 0 to Height - 1 do
begin
Count := RLELength[Y];
Line := ScanLine[Y];
Decoder.Decode(Pointer(Run1), Line, Count, Width);
Inc(Run1, Count);
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
finally
if Assigned(RawBuffer) then FreeMem(RawBuffer);
end;
end
else // uncompressed data
for Y := 0 to Height - 1 do
begin
ReadBuffer(ScanLine[Y]^, Width);
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
csRGB,
csRGBA,
csCMYK,
csCIELab:
begin
Progress(Self, psStarting, 0, False, FProgressRect, gesLoadingData);
// Data is organized in planes. This means first all red rows, then
// all green and finally all blue rows.
BPS := BitsPerSample div 8;
ChannelSize := BPS * Width * Height;
GetMem(Buffer, Channels * ChannelSize);
try
// first run: load image data and decompress it if necessary
if Assigned(Decoder) then
begin
// determine whole compressed size
Count := 0;
for Y := 0 to High(RLELength) do Inc(Count, RLELength[Y]);
Count := Count * Cardinal(BPS);
GetMem(RawBuffer, Count);
try
ReadBuffer(RawBuffer^, Count);
Decoder.Decode(RawBuffer, Buffer, Count, Channels * ChannelSize);
finally
if Assigned(RawBuffer) then FreeMem(RawBuffer);
end;
end
else
ReadBuffer(Buffer^, Channels * ChannelSize);
Progress(Self, psEnding, 0, False, FProgressRect, '');
Progress(Self, psStarting, 0, False, FProgressRect, gesTransfering);
Increment := BPS * Width;
// second run: put data into image (convert color space if necessary)
case ColorScheme of
csRGB:
begin
Run1 := Buffer;
Run2 := Run1; Inc(Run2, ChannelSize);
Run3 := Run2; Inc(Run3, ChannelSize);
for Y := 0 to Height - 1 do
begin
ColorManager.ConvertRow([Run1, Run2, Run3], ScanLine[Y], Width, $FF);
Inc(Run1, Increment);
Inc(Run2, Increment);
Inc(Run3, Increment);
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
csRGBA:
begin
Run1 := Buffer;
Run2 := Run1; Inc(Run2, ChannelSize);
Run3 := Run2; Inc(Run3, ChannelSize);
Run4 := Run3; Inc(Run4, ChannelSize);
for Y := 0 to Height - 1 do
begin
ColorManager.ConvertRow([Run1, Run2, Run3, Run4], ScanLine[Y], Width, $FF);
Inc(Run1, Increment);
Inc(Run2, Increment);
Inc(Run3, Increment);
Inc(Run4, Increment);
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
csCMYK:
begin
// Photoshop CMYK values are given with 0 for maximum values, but the
// (general) CMYK conversion works with 255 as maxium value. Hence we must reverse
// all entries in the buffer.
Run1 := Buffer;
for Y := 1 to 4 * ChannelSize do
begin
Run1^ := 255 - Run1^;
Inc(Run1);
end;
Run1 := Buffer;
Run2 := Run1; Inc(Run2, ChannelSize);
Run3 := Run2; Inc(Run3, ChannelSize);
Run4 := Run3; Inc(Run4, ChannelSize);
for Y := 0 to Height - 1 do
begin
ColorManager.ConvertRow([Run1, Run2, Run3, Run4], ScanLine[Y], Width, $FF);
Inc(Run1, Increment);
Inc(Run2, Increment);
Inc(Run3, Increment);
Inc(Run4, Increment);
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
csCIELab:
begin
Run1 := Buffer;
Run2 := Run1; Inc(Run2, ChannelSize);
Run3 := Run2; Inc(Run3, ChannelSize);
for Y := 0 to Height - 1 do
begin
ColorManager.ConvertRow([Run1, Run2, Run3], ScanLine[Y], Width, $FF);
Inc(Run1, Increment);
Inc(Run2, Increment);
Inc(Run3, Increment);
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
end;
finally
if Assigned(Buffer) then FreeMem(Buffer);
end;
end;
end;
finally
Decoder.Free;
Progress(Self, psEnding, 0, False, FProgressRect, '');
end;
end;
end
else GraphicExError(gesInvalidImage, ['PSD or PDD']);
end;
//----------------------------------------------------------------------------------------------------------------------
function TPSDGraphic.ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean;
var
Header: TPSDHeader;
Dummy: Word;
Count: Cardinal;
begin
Result := inherited ReadImageProperties(Stream, ImageIndex);
with Stream, FImageProperties do
begin
ReadBuffer(Header, SizeOf(Header));
if Header.Signature = '8BPS' then
begin
with Header do
begin
// PSD files are big endian only
Channels := Swap(Channels);
Rows := SwapLong(Rows);
Columns := SwapLong(Columns);
Depth := Swap(Depth);
Mode := Swap(Mode);
end;
Options := [ioBigEndian];
// initialize color manager
BitsPerSample := Header.Depth;
Channels := Header.Channels;
// 1..24 channels are supported in PSD files, we can only use 4.
// The documentation states that main image data (rgb(a), cmyk etc.) is always
// written as first channels in their component order.
if Channels > 4 then SamplesPerPixel := 4
else SamplesPerPixel := Channels;
BitsPerPixel := SamplesPerPixel * BitsPerSample;
// color space
case Header.Mode of
PSD_DUOTONE, // duo tone should be handled as grayscale
PSD_GRAYSCALE:
ColorScheme := csG;
PSD_BITMAP: // B&W
begin
ColorScheme := csG;
Include(Options, ioMinIsWhite);
end;
PSD_INDEXED: // 8 bits only are assumed because 16 bit wouldn't make sense here
ColorScheme := csIndexed;
PSD_MULTICHANNEL,
PSD_RGB:
if Header.Channels = 3 then ColorScheme := csRGB
else ColorScheme := csRGBA;
PSD_CMYK:
ColorScheme := csCMYK;
PSD_LAB:
ColorScheme := csCIELab;
end;
Width := Header.Columns;
Height := Header.Rows;
// size of palette
ReadBuffer(Count, SizeOf(Count));
Count := SwapLong(Count);
// skip palette (count is always given, might be 0 however, e.g. for RGB)
Seek(Count, soFromCurrent);
// skip resource and layers section
ReadBuffer(Count, SizeOf(Count));
Count := SwapLong(Count);
Seek(Count, soFromCurrent);
ReadBuffer(Count, SizeOf(Count));
Count := SwapLong(Count);
Seek(Count, soFromCurrent);
ReadBuffer(Dummy, SizeOf(Dummy));
if Swap(Dummy) = 1 then Compression := ctPackedBits
else Compression := ctNone;
Result := True;
end;
end;
end;
{$endif} // PhotoshopGraphic
//----------------- TPSPGraphic ----------------------------------------------------------------------------------------
{$ifdef PaintshopProGraphic}
const
// block identifiers
PSP_IMAGE_BLOCK = 0; // General Image Attributes Block (main)
PSP_CREATOR_BLOCK = 1; // Creator Data Block (main)
PSP_COLOR_BLOCK = 2; // Color Palette Block (main and sub)
PSP_LAYER_START_BLOCK = 3; // Layer Bank Block (main)
PSP_LAYER_BLOCK = 4; // Layer Block (sub)
PSP_CHANNEL_BLOCK = 5; // Channel Block (sub)
PSP_SELECTION_BLOCK = 6; // Selection Block (main)
PSP_ALPHA_BANK_BLOCK = 7; // Alpha Bank Block (main)
PSP_ALPHA_CHANNEL_BLOCK = 8; // Alpha Channel Block (sub)
PSP_THUMBNAIL_BLOCK = 9; // Thumbnail Block (main)
PSP_EXTENDED_DATA_BLOCK = 10; // Extended Data Block (main)
PSP_TUBE_BLOCK = 11; // Picture Tube Data Block (main)
PSP_ADJUSTMENT_EXTENSION_BLOCK = 12; // Adjustment Layer Extension Block (sub)
PSP_VECTOR_EXTENSION_BLOCK = 13; // Vector Layer Extension Block (sub)
PSP_SHAPE_BLOCK = 14; // Vector Shape Block (sub)
PSP_PAINTSTYLE_BLOCK = 15; // Paint Style Block (sub)
PSP_COMPOSITE_IMAGE_BANK_BLOCK = 16; // Composite Image Bank (main)
PSP_COMPOSITE_ATTRIBUTES_BLOCK = 17; // Composite Image Attributes (sub)
PSP_JPEG_BLOCK = 18; // JPEG Image Block (sub)
// bitmap types
PSP_DIB_IMAGE = 0; // Layer color bitmap
PSP_DIB_TRANS_MASK = 1; // Layer transparency mask bitmap
PSP_DIB_USER_MASK = 2; // Layer user mask bitmap
PSP_DIB_SELECTION= 3; // Selection mask bitmap
PSP_DIB_ALPHA_MASK = 4; // Alpha channel mask bitmap
PSP_DIB_THUMBNAIL = 5; // Thumbnail bitmap
PSP_DIB_THUMBNAIL_TRANS_MASK = 6; // Thumbnail transparency mask
PSP_DIB_ADJUSTMENT_LAYER = 7; // Adjustment layer bitmap
PSP_DIB_COMPOSITE = 8; // Composite image bitmap
PSP_DIB_COMPOSITE_TRANS_MASK = 9; // Composite image transparency
// composite image type
PSP_IMAGE_COMPOSITE = 0; // Composite Image
PSP_IMAGE_THUMBNAIL = 1; // Thumbnail Image
// graphic contents flags
PSP_GC_RASTERLAYERS = 1; // At least one raster layer
PSP_GC_VectorLayers = 2; // At least one vector layer
PSP_GC_ADJUSTMENTLAYERS = 4; // At least one adjustment layer
// Additional attributes
PSP_GC_THUMBNAIL = $01000000; // Has a thumbnail
PSP_GC_THUMBNAILTRANSPARENCY = $02000000; // Thumbnail transp.
PSP_GC_COMPOSITE = $04000000; // Has a composite image
PSP_GC_COMPOSITETRANSPARENCY = $08000000; // Composite transp.
PSP_GC_FLATIMAGE = $10000000; // Just a background
PSP_GC_SELECTION = $20000000; // Has a selection
PSP_GC_FLOATINGSELECTIONLAYER = $40000000; // Has float. selection
PSP_GC_ALPHACHANNELS = $80000000; // Has alpha channel(s)
// character style flags
PSP_STYLE_ITALIC = 1; // Italic property bit
PSP_STYLE_STRUCK = 2; // Strike-out property bit
PSP_STYLE_UNDERLINED = 4; // Underlined property bit
// layer flags
PSP_LAYER_VISIBLEFLAG = 1; // Layer is visible
PSP_LAYER_MASKPRESENCEFLAG = 2; // Layer has a mask
// Shape property flags
PSP_SHAPE_ANTIALIASED = 1; // Shape is anti-aliased
PSP_SHAPE_Selected = 2; // Shape is selected
PSP_SHAPE_Visible = 4; // Shape is visible
// Polyline node type flags
PSP_NODE_UNCONSTRAINED = 0; // Default node type
PSP_NODE_SMOOTH = 1; // Node is smooth
PSP_NODE_SYMMETRIC = 2; // Node is symmetric
PSP_NODE_ALIGNED = 4; // Node is aligned
PSP_NODE_ACTIVE = 8; // Node is active
PSP_NODE_LOCKED = 16; // Node is locked (PSP doc says 0x16 here, but this seems to be a typo)
PSP_NODE_SELECTED = 32; // Node is selected (PSP doc says 0x32 here)
PSP_NODE_VISIBLE = 64; // Node is visible (PSP doc says 0x64 here)
PSP_NODE_CLOSED = 128; // Node is closed (PSP doc says 0x128 here)
// Blend modes
LAYER_BLEND_NORMAL = 0;
LAYER_BLEND_DARKEN = 1;
LAYER_BLEND_LIGHTEN = 2;
LAYER_BLEND_HUE = 3;
LAYER_BLEND_SATURATION = 4;
LAYER_BLEND_COLOR = 5;
LAYER_BLEND_LUMINOSITY = 6;
LAYER_BLEND_MULTIPLY = 7;
LAYER_BLEND_SCREEN = 8;
LAYER_BLEND_DISSOLVE = 9;
LAYER_BLEND_OVERLAY = 10;
LAYER_BLEND_HARD_LIGHT = 11;
LAYER_BLEND_SOFT_LIGHT = 12;
LAYER_BLEND_DIFFERENCE = 130;
LAYER_BLEND_DODGE = 14;
LAYER_BLEND_BURN = 15;
LAYER_BLEND_EXCLUSION = 16;
LAYER_BLEND_ADJUST = 255;
// Adjustment layer types
PSP_ADJUSTMENT_NONE = 0; // Undefined adjustment layer type
PSP_ADJUSTMENT_LEVEL = 1; // Level adjustment
PSP_ADJUSTMENT_CURVE = 2; // Curve adjustment
PSP_ADJUSTMENT_BRIGHTCONTRAST = 3; // Brightness-contrast adjustment
PSP_ADJUSTMENT_COLORBAL = 4; // Color balance adjustment
PSP_ADJUSTMENT_HSL = 5; // HSL adjustment
PSP_ADJUSTMENT_CHANNELMIXER = 6; // Channel mixer adjustment
PSP_ADJUSTMENT_INVERT = 7; // Invert adjustment
PSP_ADJUSTMENT_THRESHOLD = 8; // Threshold adjustment
PSP_ADJUSTMENT_POSTER = 9; // Posterize adjustment
// Vector shape types
PSP_VST_Unknown = 0; // Undefined vector type
PSP_VST_TEXT = 1; // Shape represents lines of text
PSP_VST_POLYLINE = 2; // Shape represents a multiple segment line
PSP_VST_ELLIPSE = 3; // Shape represents an ellipse (or circle)
PSP_VST_POLYGON = 4; // Shape represents a closed polygon
// Text element types
PSP_TET_UNKNOWN = 0; // Undefined text element type
PSP_TET_CHAR = 1; // A single character code
PSP_TET_CHARSTYLE = 2; // A character style change
PSP_TET_LINESTYLE = 3; // A line style change
// Text alignment types
PSP_TAT_LEFT = 0; // Left text alignment
PSP_TAT_CENTER = 1; // Center text alignment
PSP_TAT_RIGHT = 2; // Right text alignment
// Paint style types
PSP_STYLE_NONE = 0; // Undefined paint style
PSP_STYLE_COLOR = 1; // Paint using color (RGB or palette index)
PSP_STYLE_GRADIENT = 2; // Paint using gradient
// Channel types
PSP_CHANNEL_COMPOSITE = 0; // Channel of single channel bitmap
PSP_CHANNEL_RED = 1; // Red channel of 24 bit bitmap
PSP_CHANNEL_GREEN = 2; // Green channel of 24 bit bitmap
PSP_CHANNEL_BLUE = 3; // Blue channel of 24 bit bitmap
// Resolution metrics
PSP_METRIC_UNDEFINED = 0; // Metric unknown
PSP_METRIC_INCH = 1; // Resolution is in inches
PSP_METRIC_CM = 2; // Resolution is in centimeters
// Compression types
PSP_COMP_NONE = 0; // No compression
PSP_COMP_RLE = 1; // RLE compression
PSP_COMP_LZ77 = 2; // LZ77 compression
PSP_COMP_JPEG = 3; // JPEG compression (only used by thumbnail and composite image)
// Picture tube placement mode
PSP_TPM_Random = 0; // Place tube images in random intervals
PSPS_TPM_Constant = 1; // Place tube images in constant intervals
// Tube selection mode
PSP_TSM_RANDOM =0; // Randomly select the next image in tube to display
PSP_TSM_INCREMENTAL = 1; // Select each tube image in turn
PSP_TSM_ANGULAR = 2; // Select image based on cursor direction
PSP_TSM_PRESSURE = 3; // Select image based on pressure (from pressure-sensitive pad)
PSP_TSM_VELOCITY = 4; // Select image based on cursor speed
// Extended data field types
PSP_XDATA_TRNS_INDEX = 0; // Transparency index field
// Creator field types
PSP_CRTR_FLD_TITLE = 0; // Image document title field
PSP_CRTR_FLD_CRT_DATE = 1; // Creation date field
PSP_CRTR_FLD_MOD_DATE = 2; // Modification date field
PSP_CRTR_FLD_ARTIST = 3; // Artist name field
PSP_CRTR_FLD_CPYRGHT = 4; // Copyright holder name field
PSP_CRTR_FLD_DESC = 5; // Image document description field
PSP_CRTR_FLD_APP_ID = 6; // Creating app id field
PSP_CRTR_FLD_APP_VER = 7; // Creating app version field
// Creator application identifier
PSP_CREATOR_APP_UNKNOWN = 0; // Creator application unknown
PSP_CREATOR_APP_PAINT_SHOP_PRO = 1; // Creator is Paint Shop Pro
// Layer types (file version 3)
PSP_LAYER_NORMAL = 0; // Normal layer
PSP_LAYER_FLOATING_SELECTION = 1; // Floating selection layer
// Layer types (file version 4)
PSP_LAYER_UNDEFINED = 0; // Undefined layer type
PSP_LAYER_RASTER = 1; // Standard raster layer
PSP_LAYER_FLOATINGRASTERSELECTION = 2; // Floating selection (raster layer)
PSP_LAYER_Vector = 3; // Vector layer
PSP_LAYER_ADJUSTMENT = 4; // Adjustment layer
MagicID = 'Paint Shop Pro Image File';
type
// These block header structures are here for informational purposes only because the data of those
// headers is read member by member to generalize code for the different file versions
TPSPBlockHeader3 = packed record // block header file version 3
HeaderIdentifier: array[0..3] of AnsiChar; // i.e. "~BK" followed by a zero byte
BlockIdentifier: Word; // one of the block identifiers
InitialChunkLength, // length of the first sub chunk header or similar
TotalBlockLength: Cardinal; // length of this block excluding this header
end;
TPSPBlockHeader4 = packed record // block header file version 4
HeaderIdentifier: array[0..3] of AnsiChar; // i.e. "~BK" followed by a zero byte
BlockIdentifier: Word; // one of the block identifiers
TotalBlockLength: Cardinal; // length of this block excluding this header
end;
TPSPColorPaletteInfoChunk = packed record
EntryCount: Cardinal; // number of entries in the palette
end;
TPSPColorPaletteChunk = array[0..255] of TRGBQuad; // might actually be shorter
TPSPChannelInfoChunk = packed record
CompressedSize,
UncompressedSize: Cardinal;
BitmapType, // one of the bitmap types
ChannelType: Word; // one of the channel types
end;
// PSP defines a channel content chunk which is just a bunch of bytes (size is CompressedSize).
// There is no sense to define this record type here.
TPSPFileHeader = packed record
Signature: array[0..31] of AnsiChar; // the string "Paint Shop Pro Image File\n\x1a", padded with zeroes
MajorVersion,
MinorVersion: Word;
end;
TPSPImageAttributes = packed record
Width,
Height: Integer;
Resolution: Double; // Number of pixels per metric
ResolutionMetric: Byte; // Metric used for resolution (one of the metric constants)
Compression, // compression type of image (not thumbnail, it has its own compression)
BitDepth, // The bit depth of the color bitmap in each Layer of the image document
// (must be 1, 4, 8 or 24).
PlaneCount: Word; // Number of planes in each layer of the image document (usually 1)
ColorCount: Cardinal; // number of colors in each layer (2^bit depth)
GreyscaleFlag: Boolean; // Indicates whether the color bitmap in each layer of image document is a
// greyscale (False = not greyscale, True = greyscale).
TotalImageSize: Cardinal; // Sum of the sizes of all layer color bitmaps.
ActiveLayer: Integer; // Identifies the layer that was active when the image document was saved.
LayerCount: Word; // Number of layers in the document.
GraphicContents: Cardinal; // A series of flags that helps define the image's graphic contents.
end;
TPSPLayerInfoChunk = packed record
//LayerName: array[0..255] of AnsiChar; // Name of layer (in ASCII text). Has been replaced in version 4
// by a Delphi like short string (length word and variable length string)
LayerType: Byte; // Type of layer.
ImageRectangle, // Rectangle defining image border.
SavedImageRectangle: TRect; // Rectangle within image rectangle that contains "significant" data
// (only the contents of this rectangle are saved to the file).
LayerOpacity: Byte; // Overall layer opacity.
BlendingMode: Byte; // Mode to use when blending layer.
Visible: Boolean; // TRUE if layer was visible at time of save, FALSE otherwise.
TransparencyProtected: Boolean; // TRUE if transparency is protected.
LinkGroupIdentifier: Byte; // Identifies group to which this layer belongs.
MaskRectangle, // Rectangle defining user mask border.
SavedMaskRectangle: TRect; // Rectangle within mask rectangle that contains "significant" data
// (only the contents of this rectangle are saved to the file).
MaskLinked: Boolean; // TRUE if mask linked to layer (i.e., mask moves relative to layer)
MaskDisabled: Boolean; // TRUE if mask is disabled, FALSE otherwise.
InvertMask: Boolean; // TRUE if mask should be inverted when the layer is merged, FALSE otherwise.
BlendRangeCount: Word; // Number of valid source-destination field pairs to follow (note, there are
// currently always 5 such pairs, but they are not necessarily all valid).
SourceBlendRange1, // First source blend range value.
DestinationBlendRange1, // First destination blend range value.
SourceBlendRange2,
DestinationBlendRange2,
SourceBlendRange3,
DestinationBlendRange3,
SourceBlendRange4,
DestinationBlendRange4,
SourceBlendRange5,
DestinationBlendRange5: array[0..3] of Byte;
// these fields are obsolete since file version 4 because there's an own chunk for them
// BitmapCount: Word; // Number of bitmaps to follow.
// ChannelCount: Word; // Number of channels to follow.
end;
//----------------------------------------------------------------------------------------------------------------------
class function TPSPGraphic.CanLoad(Stream: TStream): Boolean;
var
Header: TPSPFileHeader;
LastPosition: Cardinal;
begin
with Stream do
begin
LastPosition := Position;
Result := (Size - Position) > SizeOf(Header);
if Result then
begin
ReadBuffer(Header, SizeOf(Header));
Result := (StrLIComp(Header.Signature, MagicID, Length(MagicID)) = 0) and
(Header.MajorVersion >= 3);
end;
Position := LastPosition;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TPSPGraphic.LoadFromStream(Stream: TStream);
var
Header: TPSPFileHeader;
Image: TPSPImageAttributes;
// to use the code below for file 3 and 4 I read the parts of the block header
// separately instead as a structure
HeaderIdentifier: array[0..3] of AnsiChar; // i.e. "~BK" followed by a zero byte
BlockIdentifier: Word; // one of the block identifiers
InitialChunkLength, // length of the first sub chunk header or similar
TotalBlockLength: Cardinal; // length of this block excluding this header
LastPosition,
ChunkSize: Cardinal;
LayerInfo: TPSPLayerInfoChunk;
ChannelInfo: TPSPChannelInfoChunk;
LayerName: AnsiString;
NameLength: Word;
NextLayerPosition,
NextMainBlock: Integer;
// file version 4 specific data
BitmapCount,
ChannelCount: Word;
// load and decoding of image data
R, G, B, C: PByte;
RedBuffer,
GreenBuffer,
BlueBuffer,
CompBuffer: Pointer;
X, Y,
Index,
RowSize: Integer; // size in bytes of one scanline
// other data
RawPalette: array[0..4 * 256 - 1] of Byte;
//--------------- local functions -------------------------------------------
function ReadBlockHeader: Boolean;
// Fills in the block header variables according to the file version.
// Returns True if a block header could be read otherwise False (stream end).
begin
with Stream do
begin
Result := Position < Size;
if Result then
begin
ReadBuffer(HeaderIdentifier, SizeOf(HeaderIdentifier));
ReadBuffer(BlockIdentifier, SizeOf(BlockIdentifier));
if Header.MajorVersion = 3 then ReadBuffer(InitialChunkLength, SizeOf(InitialChunkLength));
ReadBuffer(TotalBlockLength, SizeOf(TotalBlockLength));
end;
end;
end;
//---------------------------------------------------------------------------
procedure ReadAndDecompress(Target: Pointer);
// reads a stream of data from file stream and decompresses it into Target
var
RawBuffer: Pointer;
Decoder: TDecoder;
Source: Pointer;
begin
Decoder := nil;
GetMem(RawBuffer, ChannelInfo.CompressedSize);
try
Stream.ReadBuffer(RawBuffer^, ChannelInfo.CompressedSize);
// pointer might be advanced while decoding, so use a copy
Source := RawBuffer;
case Image.Compression of
PSP_COMP_RLE:
begin
Decoder := TPSPRLEDecoder.Create;
Decoder.Decode(Source, Target, ChannelInfo.CompressedSize, ChannelInfo.UncompressedSize);
end;
PSP_COMP_LZ77:
begin
Decoder := TLZ77Decoder.Create(Z_FINISH, False);
Decoder.DecodeInit;
Decoder.Decode(Source, Target, ChannelInfo.CompressedSize, ChannelInfo.UncompressedSize);
end;
PSP_COMP_JPEG: // here just for completeness, used only in thumbnails and composite images
;
end;
Decoder.DecodeEnd;
finally
if Assigned(RawBuffer) then FreeMem(RawBuffer);
Decoder.Free;
end;
end;
//---------------------------------------------------------------------------
procedure ReadChannelData;
// Reads the actual data of one channel from the current stream position.
// Decompression is done by the way.
begin
with Stream do
begin
ReadBlockHeader;
if Header.MajorVersion > 3 then ReadBuffer(ChunkSize, SizeOf(ChunkSize));
ReadBuffer(ChannelInfo, SizeOf(ChannelInfo));
case ChannelInfo.ChannelType of
PSP_CHANNEL_COMPOSITE: // single channel bitmap (indexed or transparency mask)
begin
GetMem(CompBuffer, ChannelInfo.UncompressedSize);
if Image.Compression <> PSP_COMP_NONE then ReadAndDecompress(CompBuffer)
else ReadBuffer(CompBuffer^, ChannelInfo.CompressedSize);
end;
PSP_CHANNEL_RED: // red channel of 24 bit bitmap
begin
GetMem(RedBuffer, ChannelInfo.UncompressedSize);
if Image.Compression <> PSP_COMP_NONE then ReadAndDecompress(RedBuffer)
else ReadBuffer(RedBuffer^, ChannelInfo.CompressedSize);
end;
PSP_CHANNEL_GREEN:
begin
GetMem(GreenBuffer, ChannelInfo.UncompressedSize);
if Image.Compression <> PSP_COMP_NONE then ReadAndDecompress(GreenBuffer)
else ReadBuffer(GreenBuffer^, ChannelInfo.CompressedSize);
end;
PSP_CHANNEL_BLUE:
begin
GetMem(BlueBuffer, ChannelInfo.UncompressedSize);
if Image.Compression <> PSP_COMP_NONE then ReadAndDecompress(BlueBuffer)
else ReadBuffer(BlueBuffer^, ChannelInfo.CompressedSize);
end;
end;
end;
end;
//--------------- end local functions ---------------------------------------
begin
// free previous image data
Handle := 0;
FBasePosition := Stream.Position;
if ReadImageProperties(Stream, 0) then
begin
Stream.Position := FBasePosition;
RedBuffer := nil;
GreenBuffer := nil;
BlueBuffer := nil;
with Stream, FImageProperties do
try
FProgressRect := Rect(0, 0, Width, 1);
Progress(Self, psStarting, 0, False, FProgressRect, gesPreparing);
// Note: To be robust with future PSP images any reader must be able to skip data
// which it doesn't know instead of relying on the size of known structures.
// Hence there's some extra work needed with the stream (mainly to keep the
// current position before a chunk is read and advancing the stream using the
// chunk size field).
ReadBuffer(Header, SizeOf(Header));
// read general image attribute block
ReadBlockHeader;
LastPosition := Position;
if Version > 3 then ReadBuffer(ChunkSize, SizeOf(ChunkSize));
ReadBuffer(Image, SizeOf(Image));
Position := LastPosition + TotalBlockLength;
with ColorManager, Image do
begin
SourceOptions := [];
SourceBitsPerSample := BitsPerSample;
TargetBitsPerSample := BitsPerSample;
SourceSamplesPerPixel := SamplesPerPixel;
TargetSamplesPerPixel := SamplesPerPixel;
SourceColorScheme := ColorScheme;
if ColorScheme = csRGB then TargetColorScheme := csBGR
else TargetColorScheme := ColorScheme;
PixelFormat := TargetPixelFormat;
end;
// set bitmap properties
RowSize := 0; // make compiler quiet
case BitsPerSample of
1:
RowSize := (Image.Width + 7) div 8;
4:
RowSize := Image.Width div 2 + 1;
8:
RowSize := Image.Width;
else
GraphicExError(gesInvalidColorFormat, ['PSP']);
end;
Self.Width := Width;
Self.Height := Height;
Progress(Self, psEnding, 0, False, FProgressRect, '');
// go through main blocks and read what is needed
repeat
if not ReadBlockHeader then Break;
NextMainBlock := Position + Integer(TotalBlockLength);
// no more blocks?
if HeaderIdentifier[0] <> '~' then Break;
case BlockIdentifier of
PSP_COMPOSITE_IMAGE_BANK_BLOCK:
begin
// composite image block, if present then it must appear before the layer start block
// and represents a composition of several layers
// do not need to read anything further
//Break;
end;
PSP_LAYER_START_BLOCK:
repeat
if not ReadBlockHeader then Break;
Progress(Self, psStarting, 0, False, FProgressRect, gesLoadingData);
// calculate start of next (layer) block in case we need to skip this one
NextLayerPosition := Position + Integer(TotalBlockLength);
// if all layers have been considered the break loop to continue with other blocks if necessary
if BlockIdentifier <> PSP_LAYER_BLOCK then Break;
// layer information chunk
if Version > 3 then
begin
LastPosition := Position;
ReadBuffer(ChunkSize, SizeOf(ChunkSize));
ReadBuffer(NameLength, SizeOf(NameLength));
SetLength(LayerName, NameLength);
if NameLength > 0 then ReadBuffer(LayerName[1], NameLength);
ReadBuffer(LayerInfo, SizeOf(LayerInfo));
Position := LastPosition + ChunkSize;
// continue only with undefined or raster chunks
if not (LayerInfo.LayerType in [PSP_LAYER_UNDEFINED, PSP_LAYER_RASTER]) then
begin
Position := NextLayerPosition;
Continue;
end;
// in file version 4 there's also an additional bitmap chunk which replaces
// two fields formerly located in the LayerInfo chunk
LastPosition := Position;
ReadBuffer(ChunkSize, SizeOf(ChunkSize));
end
else
begin
SetLength(LayerName, 256);
ReadBuffer(LayerName[1], 256);
ReadBuffer(LayerInfo, SizeOf(LayerInfo));
// continue only with normal (raster) chunks
if LayerInfo.LayerType <> PSP_LAYER_NORMAL then
begin
Position := NextLayerPosition;
Continue;
end;
end;
ReadBuffer(BitmapCount, SizeOf(BitmapCount));
ReadBuffer(ChannelCount, SizeOf(ChannelCount));
// But now we can reliably say whether we have an alpha channel or not.
// This kind of information can only be read very late and causes us to
// possibly reallocate the entire image (because it is copied by the VCL
// when changing the pixel format).
// I don't know another way (preferably before the size of the image is set).
if ChannelCount > 3 then
begin
ColorManager.TargetColorScheme := csBGRA;
PixelFormat := pf32Bit;
end;
if Version > 3 then Position := LastPosition + ChunkSize;
// allocate memory for all channels and read raw data
for X := 0 to ChannelCount - 1 do ReadChannelData;
Progress(Self, psEnding, 0, False, FProgressRect, '');
Progress(Self, psStarting, 0, False, FProgressRect, gesTransfering);
R := RedBuffer;
G := GreenBuffer;
B := BlueBuffer;
C := CompBuffer;
with ColorManager do
begin
if TargetColorScheme in [csIndexed, csG] then
begin
for Y := 0 to Height - 1 do
begin
ColorManager.ConvertRow([C], ScanLine[Y], Width, $FF);
Inc(C, RowSize);
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end
else
begin
for Y := 0 to Height - 1 do
begin
ColorManager.ConvertRow([R, G, B, C], ScanLine[Y], Width, $FF);
Inc(R, RowSize);
Inc(G, RowSize);
Inc(B, RowSize);
Inc(C, RowSize);
Progress(Self, psRunning, MulDiv(Y, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
end;
Progress(Self, psEnding, 0, False, FProgressRect, '');
// after the raster layer has been read there's no need to loop further
Break;
until False; // layer loop
PSP_COLOR_BLOCK: // color palette block (this is also present for gray scale and b&w images)
begin
if Version > 3 then ReadBuffer(ChunkSize, SizeOf(ChunkSize));
ReadBuffer(Index, SizeOf(Index));
ReadBuffer(RawPalette, Index * SizeOf(TRGBQuad));
Palette := ColorManager.CreateColorPalette([@RawPalette], pfInterlaced8Quad, Index, True);
end;
end;
// explicitly set stream position to next main block as we might have read a block only partially
Position := NextMainBlock;
until False; // main block loop
finally
if Assigned(RedBuffer) then FreeMem(RedBuffer);
if Assigned(GreenBuffer) then FreeMem(GreenBuffer);
if Assigned(BlueBuffer) then FreeMem(BlueBuffer);
end;
end
else GraphicExError(gesInvalidImage, ['PSP']);
end;
//----------------------------------------------------------------------------------------------------------------------
function TPSPGraphic.ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean;
var
Header: TPSPFileHeader;
Image: TPSPImageAttributes;
// to use the code below for file 3 and 4 I read the parts of the block header
// separately instead as a structure
HeaderIdentifier: array[0..3] of AnsiChar; // i.e. "~BK" followed by a zero byte
BlockIdentifier: Word; // one of the block identifiers
InitialChunkLength, // length of the first sub chunk header or similar
TotalBlockLength: Cardinal; // length of this block excluding this header
LastPosition,
ChunkSize: Cardinal;
//--------------- local functions -------------------------------------------
function ReadBlockHeader: Boolean;
// Fills in the block header variables according to the file version.
// Returns True if a block header could be read otherwise False (stream end).
begin
with Stream do
begin
Result := Position < Size;
if Result then
begin
ReadBuffer(HeaderIdentifier, SizeOf(HeaderIdentifier));
ReadBuffer(BlockIdentifier, SizeOf(BlockIdentifier));
if Header.MajorVersion = 3 then ReadBuffer(InitialChunkLength, SizeOf(InitialChunkLength));
ReadBuffer(TotalBlockLength, SizeOf(TotalBlockLength));
end;
end;
end;
//--------------- end local functions ---------------------------------------
begin
Result := inherited ReadImageProperties(Stream, ImageIndex);
with Stream, FImageProperties do
begin
ReadBuffer(Header, SizeOf(Header));
if (StrLIComp(Header.Signature, MagicID, Length(MagicID)) = 0) and
(Header.MajorVersion >= 3) then
begin
Version := Header.MajorVersion;
// read general image attribute block
ReadBlockHeader;
LastPosition := Position;
if Header.MajorVersion > 3 then ReadBuffer(ChunkSize, SizeOf(ChunkSize));
ReadBuffer(Image, SizeOf(Image));
Position := LastPosition + TotalBlockLength;
if Image.BitDepth = 24 then
begin
BitsPerSample := 8;
SamplesPerPixel := 3;
ColorScheme := csRGB; // an alpha channel might exist, this is determined by the layer's channel count
end
else
begin
BitsPerSample := Image.BitDepth;
SamplesPerPixel := 1;
if Image.GreyscaleFlag then ColorScheme := csG
else ColorScheme := csIndexed;
end;
BitsPerPixel := BitsPerSample * SamplesPerPixel;
Width := Image.Width;
Height := Image.Height;
case Image.Compression of
PSP_COMP_NONE:
Compression := ctNone;
PSP_COMP_RLE:
Compression := ctRLE;
PSP_COMP_LZ77:
Compression := ctLZ77;
PSP_COMP_JPEG:
Compression := ctJPEG;
else
Compression := ctUnknown;
end;
XResolution := Image.Resolution;
if Image.ResolutionMetric = PSP_METRIC_CM then XResolution := XResolution * 2.54;
YResolution := XResolution;
Result := True;
end;
end;
end;
{$endif} // PaintshopProGraphic
//----------------- TPNGGraphic ----------------------------------------------------------------------------------------
{$ifdef PortableNetworkGraphic}
const
PNGMagic: array[0..7] of Byte = (137, 80, 78, 71, 13, 10, 26, 10);
// recognized and handled chunk types
IHDR: TChunkType = 'IHDR';
IDAT: TChunkType = 'IDAT';
IEND: TChunkType = 'IEND';
PLTE: TChunkType = 'PLTE';
gAMA: TChunkType = 'gAMA';
tRNS: TChunkType = 'tRNS';
bKGD: TChunkType = 'bKGD';
CHUNKMASK = $20; // used to check bit 5 in chunk types
type
// The following chunks structures are those which appear in the data field of the general chunk structure
// given above.
// chunk type: 'IHDR'
PIHDRChunk = ^TIHDRChunk;
TIHDRChunk = packed record
Width,
Height: Cardinal;
BitDepth, // bits per sample (allowed are 1, 2, 4, 8 and 16)
ColorType, // combination of:
// 1 - palette used
// 2 - colors used
// 4 - alpha channel used
// allowed values are:
// 0 - gray scale (allowed bit depths are: 1, 2, 4, 8, 16)
// 2 - RGB (8, 16)
// 3 - palette (1, 2, 4, 8)
// 4 - gray scale with alpha (8, 16)
// 6 - RGB with alpha (8, 16)
Compression, // 0 - LZ77, others are not yet defined
Filter, // filter mode 0 is the only one currently defined
Interlaced: Byte; // 0 - not interlaced, 1 - Adam7 interlaced
end;
//----------------------------------------------------------------------------------------------------------------------
class function TPNGGraphic.CanLoad(Stream: TStream): Boolean;
var
Magic: array[0..7] of Byte;
LastPosition: Cardinal;
begin
with Stream do
begin
LastPosition := Position;
Result := (Size - Position) > SizeOf(Magic);
if Result then
begin
ReadBuffer(Magic, SizeOf(Magic));
Result := CompareMem(@Magic, @PNGMagic, 8);
end;
Position := LastPosition;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TPNGGraphic.IsChunk(ChunkType: TChunkType): Boolean;
// determines, independant of the cruxial 5ths bits in each "letter", whether the
// current chunk type in the header is the same as the given chunk type
const
Mask = not $20202020;
begin
Result := (FHeader.Mask and Mask) = (PDWORD(@ChunkType)^ and Mask);
end;
//----------------------------------------------------------------------------------------------------------------------
function TPNGGraphic.LoadAndSwapHeader: Cardinal;
// read next chunk header and swap fields to little endian,
// returns the intial CRC value for following checks
begin
FStream.ReadBuffer(FHeader, SizeOf(FHeader));
Result := CRC32(0, @FHeader.ChunkType, 4);
FHeader.Length := SwapLong(FHeader.Length);
end;
//----------------------------------------------------------------------------------------------------------------------
function PaethPredictor(a, b, c: Byte): Byte;
var
p, pa, pb, pc: Integer;
begin
// a = left, b = above, c = upper left
p := a + b - c; // initial estimate
pa := Abs(p - a); // distances to a, b, c
pb := Abs(p - b);
pc := Abs(p - c);
// return nearest of a, b, c, breaking ties in order a, b, c
if (pa <= pb) and (pa <= pc) then Result := a
else
if pb <= pc then Result := b
else Result := c;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TPNGGraphic.ApplyFilter(Filter: Byte; Line, PrevLine, Target: PByte; BPP, BytesPerRow: Integer);
// Applies the filter given in Filter to all bytes in Line (eventually using PrevLine).
// Note: The filter type is assumed to be of filter mode 0, as this is the only one currently
// defined in PNG.
// In opposition to the PNG documentation different identifiers are used here.
// Raw refers to the current, not yet decoded value. Decoded refers to the current, already
// decoded value (this one is called "raw" in the docs) and Prior is the current value in the
// previous line. For the Paeth prediction scheme a fourth pointer is used (PriorDecoded) to describe
// the value in the previous line but less the BPP value (Prior[x - BPP]).
var
I: Integer;
Raw,
Decoded,
Prior,
PriorDecoded,
TargetRun: PByte;
begin
case Filter of
0: // no filter, just copy data
Move(Line^, Target^, BytesPerRow);
1: // subtraction filter
begin
Raw := Line;
TargetRun := Target;
// Transfer BPP bytes without filtering. This mimics the effect of bytes left to the
// scanline being zero.
Move(Raw^, TargetRun^, BPP);
// now do rest of the line
Decoded := TargetRun;
Inc(Raw, BPP);
Inc(TargetRun, BPP);
Dec(BytesPerRow, BPP);
while BytesPerRow > 0 do
begin
TargetRun^ := Byte(Raw^ + Decoded^);
Inc(Raw);
Inc(Decoded);
Inc(TargetRun);
Dec(BytesPerRow);
end;
end;
2: // Up filter
begin
Raw := Line;
Prior := PrevLine;
TargetRun := Target;
while BytesPerRow > 0 do
begin
TargetRun^ := Byte(Raw^ + Prior^);
Inc(Raw);
Inc(Prior);
Inc(TargetRun);
Dec(BytesPerRow);
end;
end;
3: // average filter
begin
// first handle BPP virtual pixels to the left
Raw := Line;
Decoded := Line;
Prior := PrevLine;
TargetRun := Target;
for I := 0 to BPP - 1 do
begin
TargetRun^ := Byte(Raw^ + Floor(Prior^ / 2));
Inc(Raw);
Inc(Prior);
Inc(TargetRun);
end;
Dec(BytesPerRow, BPP);
// now do rest of line
while BytesPerRow > 0 do
begin
TargetRun^ := Byte(Raw^ + Floor((Decoded^ + Prior^) / 2));
Inc(Raw);
Inc(Decoded);
Inc(Prior);
Inc(TargetRun);
Dec(BytesPerRow);
end;
end;
4: // paeth prediction
begin
// again, start with first BPP pixel which would refer to non-existing pixels to the left
Raw := Line;
Decoded := Target;
Prior := PrevLine;
PriorDecoded := PrevLine;
TargetRun := Target;
for I := 0 to BPP - 1 do
begin
TargetRun^ := Byte(Raw^ + PaethPredictor(0, Prior^, 0));
Inc(Raw);
Inc(Prior);
Inc(TargetRun);
end;
Dec(BytesPerRow, BPP);
// finally do rest of line
while BytesPerRow > 0 do
begin
TargetRun^ := Byte(Raw^ + PaethPredictor(Decoded^, Prior^, PriorDecoded^));
Inc(Raw);
Inc(Decoded);
Inc(Prior);
Inc(PriorDecoded);
Inc(TargetRun);
Dec(BytesPerRow);
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TPNGGraphic.LoadFromStream(Stream: TStream);
var
Description: TIHDRChunk;
begin
// free previous image data
Handle := 0;
FBasePosition := Stream.Position;
FDecoder := nil;
FStream := Stream;
if ReadImageProperties(Stream, 0) then
begin
with Stream, FImageProperties do
begin
Position := FBasePosition + 8; // skip magic
FProgressRect := Rect(0, 0, Width, 1);
Progress(Self, psStarting, 0, False, FProgressRect, gesPreparing);
FPalette := 0;
FTransparency := nil;
FBackgroundColor := clWhite;
FTransparentColor := clNone;
// first chunk must be an IHDR chunk
FCurrentCRC := LoadAndSwapHeader;
FRawBuffer := nil;
ColorManager.SourceOptions := [coNeedByteSwap];
try
// read IHDR chunk
ReadDataAndCheckCRC;
Move(FRawBuffer^, Description, SizeOf(Description));
SwapLong(@Description, 2);
// currently only one compression type is supported by PNG (LZ77)
if Compression = ctLZ77 then
begin
FDecoder := TLZ77Decoder.Create(Z_PARTIAL_FLUSH, False);
FDecoder.DecodeInit;
end
else
GraphicExError(gesUnsupportedFeature, [gesCompressionScheme, 'PNG']);
// setup is done, now go for the chunks
repeat
FCurrentCRC := LoadAndSwapHeader;
if IsChunk(IDAT) then
begin
Progress(Self, psEnding, 0, False, FProgressRect, '');
LoadIDAT(Description);
// After reading the image data the next chunk header has already been loaded
// so continue with code below instead trying to load a new chunk header.
end
else
if IsChunk(PLTE) then
begin
// palette chunk
if (FHeader.Length mod 3) <> 0 then GraphicExError(gesInvalidPalette, ['PNG']);
ReadDataAndCheckCRC;
// load palette only if the image is indexed colors
if Description.ColorType = 3 then
begin
// first setup pixel format before actually creating a palette
FSourceBPP := SetupColorDepth(Description.ColorType, Description.BitDepth);
FPalette := ColorManager.CreateColorPalette([FRawBuffer], pfInterlaced8Triple, FHeader.Length div 3, False);
end;
Continue;
end
else
if IsChunk(gAMA) then
begin
ReadDataAndCheckCRC;
// the file gamme given here is a scaled cardinal (e.g. 0.45 is expressed as 45000)
ColorManager.SetGamma(SwapLong(PCardinal(FRawBuffer)^) / 100000);
ColorManager.TargetOptions := ColorManager.TargetOptions + [coApplyGamma];
Include(Options, ioUseGamma);
Continue;
end
else
if IsChunk(bKGD) then
begin
LoadBackgroundColor(Description);
Continue;
end
else
if IsChunk(tRNS) then
begin
LoadTransparency(Description);
Continue;
end;
// Skip unknown or unsupported chunks (+4 because of always present CRC).
// IEND will be skipped as well, but this chunk is empty, so the stream will correctly
// end on the first byte after the IEND chunk.
Seek(FHeader.Length + 4, soFromCurrent);
if IsChunk(IEND) then Break;
// Note: According to the specs an unknown, but as critical marked chunk is a fatal error.
if (Byte(FHeader.ChunkType[0]) and CHUNKMASK) = 0 then GraphicExError(gesUnknownCriticalChunk);
until False;
finally
if Assigned(FDecoder) then
begin
FDecoder.DecodeEnd;
FDecoder.Free;
end;
if Assigned(FRawBuffer) then FreeMem(FRawBuffer);
Progress(Self, psEnding, 0, False, FProgressRect, '');
end;
end;
end
else GraphicExError(gesInvalidImage, ['PNG']);
end;
//----------------------------------------------------------------------------------------------------------------------
function TPNGGraphic.ReadImageProperties(Stream: TStream; ImageIndex: Cardinal): Boolean;
var
Magic: array[0..7] of Byte;
Description: TIHDRChunk;
begin
Result := inherited ReadImageProperties(Stream, ImageIndex);
FStream := Stream;
with Stream, FImageProperties do
begin
ReadBuffer(Magic, 8);
if CompareMem(@Magic, @PNGMagic, 8) then
begin
// first chunk must be an IHDR chunk
FCurrentCRC := LoadAndSwapHeader;
if IsChunk(IHDR) then
begin
Include(Options, ioBigEndian);
// read IHDR chunk
ReadDataAndCheckCRC;
Move(FRawBuffer^, Description, SizeOf(Description));
SwapLong(@Description, 2);
if (Description.Width = 0) or (Description.Height = 0) then Exit;
Width := Description.Width;
Height := Description.Height;
if Description.Compression = 0 then Compression := ctLZ77
else Compression := ctUnknown;
BitsPerSample := Description.BitDepth;
SamplesPerPixel := 1;
case Description.ColorType of
0:
ColorScheme := csG;
2:
begin
ColorScheme := csRGB;
SamplesPerPixel := 3;
end;
3:
ColorScheme := csIndexed;
4:
ColorScheme := csGA;
6:
begin
ColorScheme := csRGBA;
SamplesPerPixel := 4;
end;
else
ColorScheme := csUnknown;
end;
BitsPerPixel := SamplesPerPixel * BitsPerSample;
FilterMode := Description.Filter;
Interlaced := Description.Interlaced <> 0;
HasAlpha := ColorScheme in [csGA, csRGBA, csBGRA];
// find gamma
repeat
FCurrentCRC := LoadAndSwapHeader;
if IsChunk(gAMA) then
begin
ReadDataAndCheckCRC;
// the file gamme given here is a scaled cardinal (e.g. 0.45 is expressed as 45000)
FileGamma := SwapLong(PCardinal(FRawBuffer)^) / 100000;
Break;
end;
Seek(FHeader.Length + 4, soFromCurrent);
if IsChunk(IEND) then Break;
until False;
Result := True;
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TPNGGraphic.LoadBackgroundColor(const Description);
// loads the data from the current chunk (must be a bKGD chunk) and fills the bitmpap with that color
var
Run: PWord;
R, G, B: Byte;
begin
ReadDataAndCheckCRC;
with TIHDRChunk(Description) do
begin
case ColorType of
0, 4: // G(A)
begin
case BitDepth of
2:
FBackgroundColor := MulDiv16(Swap(PWord(FRawBuffer)^), 15, 3);
16:
FBackgroundColor := MulDiv16(Swap(PWord(FRawBuffer)^), 255, 65535);
else // 1, 4, 8 bits gray scale
FBackgroundColor := Byte(Swap(PWord(FRawBuffer)^));
end;
end;
2, 6: // RGB(A)
begin
Run := FRawBuffer;
if BitDepth = 16 then
begin
R := MulDiv16(Swap(Run^), 255, 65535); Inc(Run);
G := MulDiv16(Swap(Run^), 255, 65535); Inc(Run);
B := MulDiv16(Swap(Run^), 255, 65535);
end
else
begin
R := Byte(Swap(Run^)); Inc(Run);
G := Byte(Swap(Run^)); Inc(Run);
B := Byte(Swap(Run^));
end;
FBackgroundColor := RGB(R, G, B);
end;
else // indexed color scheme (3)
FBackgroundColor := PByte(FRawBuffer)^;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TPNGGraphic.LoadIDAT(const Description);
// loads image data from the current position of the stream
const
// interlace start and offsets
RowStart: array[0..6] of Integer = (0, 0, 4, 0, 2, 0, 1);
ColumnStart: array[0..6] of Integer = (0, 4, 0, 2, 0, 1, 0);
RowIncrement: array[0..6] of Integer = (8, 8, 8, 4, 4, 2, 2);
ColumnIncrement: array[0..6] of Integer = (8, 8, 4, 4, 2, 2, 1);
PassMask: array[0..6] of Byte = ($80, $08, $88, $22, $AA, $55, $FF);
var
Row: Integer;
TargetBPP: Integer;
RowBuffer: array[Boolean] of PAnsiChar; // I use PChar here instead of simple pointer to ease pointer math below
EvenRow: Boolean; // distincts between the two rows we need to hold for filtering
Pass: Integer;
BytesPerRow,
InterlaceRowBytes,
InterlaceWidth: Integer;
begin
Progress(Self, psStarting, 0, False, FProgressRect, gesTransfering);
RowBuffer[False] := nil;
RowBuffer[True] := nil;
try
// adjust pixel format etc. if not yet done
if PixelFormat = pfDevice then
FSourceBPP := SetupColorDepth(TIHDRChunk(Description).ColorType, TIHDRChunk(Description).BitDepth);
if TIHDRChunk(Description).BitDepth = 16 then TargetBPP := FSourceBPP div 2
else TargetBPP := FSourceBPP;
if FPalette <> 0 then Palette := FPalette;
// after setting the pixel format we can set the dimensions too without
// initiating color conversions
Width := TIHDRChunk(Description).Width;
Height := TIHDRChunk(Description).Height;
// set background and transparency color, these values must be set after the
// bitmap is actually valid (although, not filled)
Canvas.Lock;
try
Canvas.Brush.Color := FBackgroundColor;
Canvas.FillRect(Rect(0, 0, Width, Height));
finally
Canvas.Unlock;
end;
if FTransparentColor <> clNone then
begin
TransparentColor := FTransparentColor;
Transparent := True;
end;
// determine maximum number of bytes per row and consider there's one filter byte at the start of each row
BytesPerRow := TargetBPP * ((Width * TIHDRChunk(Description).BitDepth + 7) div 8) + 1;
RowBuffer[True] := AllocMem(BytesPerRow);
RowBuffer[False] := AllocMem(BytesPerRow);
// there can be more than one IDAT chunk in the file but then they must directly
// follow each other (handled in ReadRow)
EvenRow := True;
// prepare interlaced images
if TIHDRChunk(Description).Interlaced = 1 then
begin
for Pass := 0 to 6 do
begin
// prepare next interlace run
if Width <= ColumnStart[Pass] then Continue;
InterlaceWidth := (Width + ColumnIncrement[Pass] - 1 - ColumnStart[Pass]) div ColumnIncrement[Pass];
InterlaceRowBytes := TargetBPP * ((InterlaceWidth * TIHDRChunk(Description).BitDepth + 7) div 8) + 1;
Row := RowStart[Pass];
while Row < Height do
begin
ReadRow(RowBuffer[EvenRow], InterlaceRowBytes);
ApplyFilter(Byte(RowBuffer[EvenRow]^),
Pointer(RowBuffer[EvenRow] + 1),
Pointer(RowBuffer[not EvenRow] + 1),
Pointer(RowBuffer[EvenRow] + 1),
FSourceBPP,
InterlaceRowBytes - 1);
ColorManager.ConvertRow([Pointer(RowBuffer[EvenRow] + 1)], ScanLine[Row], Width, PassMask[Pass]);
EvenRow := not EvenRow;
// continue with next row in interlaced order
Inc(Row, RowIncrement[Pass]);
if Pass = 6 then
begin
// progress event only for last (and most expensive) pass
Progress(Self, psRunning, MulDiv(Row, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
end;
end
else
begin
for Row := 0 to Height - 1 do
begin
ReadRow(RowBuffer[EvenRow], BytesPerRow);
ApplyFilter(Byte(RowBuffer[EvenRow]^),
Pointer(RowBuffer[EvenRow] + 1),
Pointer(RowBuffer[not EvenRow] + 1),
Pointer(RowBuffer[EvenRow] + 1),
FSourceBPP,
BytesPerRow - 1);
ColorManager.ConvertRow([Pointer(RowBuffer[EvenRow] + 1)], ScanLine[Row], Width, $FF);
EvenRow := not EvenRow;
Progress(Self, psRunning, MulDiv(Row, 100, Height), True, FProgressRect, '');
OffsetRect(FProgressRect, 0, 1);
end;
end;
// in order to improve safe failness we read all remaining but not read IDAT chunks here
while IsChunk(IDAT) do
begin
ReadDataAndCheckCRC;
FCurrentCRC := LoadAndSwapHeader;
end;
finally
if Assigned(RowBuffer[True]) then FreeMem(RowBuffer[True]);
if Assigned(RowBuffer[False]) then FreeMem(RowBuffer[False]);
end;
// ending progress event is issued in main method
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TPNGGraphic.LoadTransparency(const Description);
// reads the data of the current transparency chunk
var
Run: PWord;
R, G, B: Byte;
begin
ReadDataAndCheckCRC;
with TIHDRChunk(Description) do
begin
case ColorType of
0: // gray
begin
case BitDepth of
2:
R := MulDiv16(Swap(PWord(FRawBuffer)^), 15, 3);
16:
R := MulDiv16(Swap(PWord(FRawBuffer)^), 255, 65535);
else // 1, 4, 8 bits gray scale
R := Byte(Swap(PWord(FRawBuffer)^));
end;
FTransparentColor := RGB(R, R, R);
end;
2: // RGB
begin
Run := FRawBuffer;
if BitDepth = 16 then
begin
R := MulDiv16(Swap(Run^), 255, 65535); Inc(Run);
G := MulDiv16(Swap(Run^), 255, 65535); Inc(Run);
B := MulDiv16(Swap(Run^), 255, 65535);
end
else
begin
R := Byte(Swap(Run^)); Inc(Run);
G := Byte(Swap(Run^)); Inc(Run);
B := Byte(Swap(Run^));
end;
FTransparentColor := RGB(R, G, B);
end;
4, 6:
// formats with full alpha channel, they shouldn't have a transparent color
else
// Indexed color scheme (3), with at most 256 alpha values (for each palette entry).
SetLength(FTransparency, 255);
// read the values (at most 256)...
Move(FRawBuffer^, FTransparency[0], Max(FHeader.Length, 256));
// ...and set default values (255, fully opaque) for non-supplied values
if FHeader.Length < 256 then FillChar(FTransparency[FHeader.Length], 256 - FHeader.Length, $FF);
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TPNGGraphic.ReadDataAndCheckCRC;
// Allocates memory in FRawBuffer and reads the next Header.Length bytes from Stream.
// Furthermore, the CRC value following the data is read as well and compared with
// the CRC value which is calculated here.
var
FileCRC: Cardinal;
begin
ReallocMem(FRawBuffer, FHeader.Length);
FStream.ReadBuffer(FRawBuffer^, FHeader.Length);
FStream.ReadBuffer(FileCRC, SizeOf(FileCRC));
FileCRC := SwapLong(FileCRC);
// The type field of a chunk is included in the CRC, this serves as initial value
// for the calculation here and is determined in LoadAndSwapHeader.
FCurrentCRC := CRC32(FCurrentCRC, FRawBuffer, FHeader.Length);
if FCurrentCRC <> FileCRC then GraphicExError(gesInvalidCRC, ['PNG']);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TPNGGraphic.ReadRow(RowBuffer: Pointer; BytesPerRow: Integer);
// reads and decodes one scanline
var
LocalBuffer: Pointer;
PendingOutput: Integer;
begin
LocalBuffer := RowBuffer;
PendingOutput := BytesPerRow;
repeat
// read pending chunk data if available input has dropped to zero
if FDecoder.AvailableInput = 0 then
begin
FIDATSize := 0;
// read all following chunks until enough data is available or there is no further IDAT chunk
while FIDATSize = 0 do
begin
// finish if the current chunk is not an IDAT chunk
if not IsChunk(IDAT) then Exit;
ReadDataAndCheckCRC;
FCurrentSource := FRawBuffer;
FIDATSize := FHeader.Length;
// prepare next chunk (plus CRC)
FCurrentCRC := LoadAndSwapHeader;
end;
end;
// this decode call will advance Source and Target accordingly
FDecoder.Decode(FCurrentSource,
LocalBuffer,
FIDATSize - (Integer(FCurrentSource) - Integer(FRawBuffer)),
PendingOutput);
if FDecoder.ZLibResult = Z_STREAM_END then
begin
if (FDecoder.AvailableOutput <> 0) or
(FDecoder.AvailableInput <> 0) then GraphicExError(gesExtraCompressedData, ['PNG']);
Break;
end;
if FDecoder.ZLibResult <> Z_OK then GraphicExError(gesCompression, ['PNG']);
PendingOutput := BytesPerRow - (Integer(LocalBuffer) - Integer(RowBuffer));
until PendingOutput = 0;
end;
//----------------------------------------------------------------------------------------------------------------------
function TPNGGraphic.SetupColorDepth(ColorType, BitDepth: Integer): Integer;
begin
Result := 0;
// determine color scheme and setup related stuff,
// Note: The calculated BPP value is always at least 1 even for 1 bits per pixel etc. formats
// and used in filter calculation.
case ColorType of
0: // gray scale (allowed bit depths are: 1, 2, 4, 8, 16 bits)
if BitDepth in [1, 2, 4, 8, 16] then
with ColorManager do
begin
SourceColorScheme := csG;
TargetColorScheme := csG;
SourceSamplesPerPixel := 1;
TargetSamplesPerPixel := 1;
SourceBitsPerSample := BitDepth;
// 2 bits values are converted to 4 bits values because DIBs don't know the former variant
case BitDepth of
2:
TargetBitsPerSample := 4;
16:
TargetBitsPerSample := 8;
else
TargetBitsPerSample := BitDepth;
end;
PixelFormat := TargetPixelFormat;
FPalette := CreateGrayscalePalette(False);
Result := (BitDepth + 7) div 8;
end
else GraphicExError(gesInvalidColorFormat, ['PNG']);
2: // RGB
if BitDepth in [8, 16] then
with ColorManager do
begin
SourceSamplesPerPixel := 3;
TargetSamplesPerPixel := 3;
SourceColorScheme := csRGB;
TargetColorScheme := csBGR;
SourceBitsPerSample := BitDepth;
TargetBitsPerSample := 8;
PixelFormat := pf24Bit;
Result := BitDepth * 3 div 8;
end
else GraphicExError(gesInvalidColorFormat, ['PNG']);
3: // palette
if BitDepth in [1, 2, 4, 8] then
with ColorManager do
begin
SourceColorScheme := csIndexed;
TargetColorScheme := csIndexed;
SourceSamplesPerPixel := 1;
TargetSamplesPerPixel := 1;
SourceBitsPerSample := BitDepth;
// 2 bits values are converted to 4 bits values because DIBs don't know the former variant
if BitDepth = 2 then TargetBitsPerSample := 4
else TargetBitsPerSample := BitDepth;
PixelFormat := TargetPixelFormat;
Result := 1;
end
else GraphicExError(gesInvalidColorFormat, ['PNG']);
4: // gray scale with alpha,
// For the moment this format is handled without alpha, but might later be converted
// to RGBA with gray pixels or use a totally different approach.
if BitDepth in [8, 16] then
with ColorManager do
begin
SourceSamplesPerPixel := 1;
TargetSamplesPerPixel := 1;
SourceBitsPerSample := BitDepth;
TargetBitsPerSample := 8;
SourceColorScheme := csGA;
TargetColorScheme := csIndexed;
PixelFormat := pf8Bit;
FPalette := CreateGrayScalePalette(False);
Result := 2 * BitDepth div 8;
end
else GraphicExError(gesInvalidColorFormat, ['PNG']);
6: // RGB with alpha (8, 16)
if BitDepth in [8, 16] then
with ColorManager do
begin
SourceSamplesPerPixel := 4;
TargetSamplesPerPixel := 4;
SourceColorScheme := csRGBA;
TargetColorScheme := csBGRA;
SourceBitsPerSample := BitDepth;
TargetBitsPerSample := 8;
PixelFormat := pf32Bit;
Result := BitDepth * 4 div 8;
end
else GraphicExError(gesInvalidColorFormat, ['PNG']);
else
GraphicExError(gesInvalidColorFormat, ['PNG']);
end;
end;
{$endif} // PortableNetworkGraphic
//----------------- TFileFormatList ------------------------------------------------------------------------------------
type
PClassEntry = ^TClassEntry;
TClassEntry = record
GraphicClass: TGraphicClass;
Description: String;
Count: Cardinal;
end;
PExtensionEntry = ^TExtensionEntry;
TExtensionEntry = record
Extension,
Description: String;
FormatTypes: TFormatTypes;
ClassReference: PClassEntry;
end;
constructor TFileFormatList.Create;
begin
FClassList := TList.Create;
FExtensionList := TList.Create;
end;
//----------------------------------------------------------------------------------------------------------------------
destructor TFileFormatList.Destroy;
begin
Clear;
FClassList.Free;
FExtensionList.Free;
inherited;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TFileFormatList.Clear;
var
I: Integer;
begin
for I := 0 to FClassList.Count - 1 do
begin
TPicture.UnregisterGraphicClass(PClassEntry(FClassList[I]).GraphicClass);
Dispose(PClassEntry(FClassList[I])); // need Dispose with type casting to free strings too
end;
FClassList.Clear;
for I := 0 to FExtensionList.Count - 1 do
Dispose(PExtensionEntry(FExtensionList[I]));
FExtensionList.Clear;
end;
//----------------------------------------------------------------------------------------------------------------------
function TFileFormatList.FindExtension(const Extension: String): Integer;
// Returns the entry which belongs to the given extension string or -1 if there's nothing in the list for this ext.
var
I: Integer;
begin
Result := -1;
if Extension <> '' then
for I := 0 to FExtensionList.Count - 1 do
if CompareText(PExtensionEntry(FExtensionList[I]).Extension, Extension) = 0 then
begin
Result := I;
Break;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TFileFormatList.FindGraphicClass(GraphicClass: TGraphicClass): Integer;
// returns the entry index which belongs to the given graphic class or -1
var
I: Integer;
begin
Result := -1;
for I := 0 to FClassList.Count - 1 do
if PClassEntry(FClassList[I]).GraphicClass = GraphicClass then
begin
Result := I;
Break;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TFileFormatList.GetDescription(Graphic: TGraphicClass): String;
// returns the registered description string for the given class
var
I: Integer;
begin
Result := '';
I := FindGraphicClass(Graphic);
if I > -1 then Result := PClassEntry(FClassList[I]).Description;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TFileFormatList.GetExtensionList(List: TStrings);
// returns a list of registered extensions (letters only, no *. part)
var
I: Integer;
ExtEntry: PExtensionEntry;
begin
List.Clear;
for I := 0 to FExtensionList.Count - 1 do
begin
ExtEntry := FExtensionList[I];
List.Add(ExtEntry.Extension);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TFileFormatList.GetGraphicFilter(Formats: TFormatTypes; SortType: TFilterSortType;
Options: TFilterOptions; GraphicClass: TGraphicClass): String;
// Creates a string which can directly be used in an open or save dialog's filter property.
// Formats may be used to limit the number of formats to return.
// SortType determines how to sort the entries.
// Compact determines whether to group extensions (= True) or to put every extension on a separate line.
// AllImages finally determines whether to include the 'All image file' entry which includes all allowed extensions
// which qualify by the other properties.
// Usually all these options determine quite nicely which formats are well suited for a particular task
// but sometimes you may find it better to specify a graphic class to limit returned formats further.
// In this case set GraphicClass to the particular class otherwise set it nil.
var
I, J: Integer;
DL, EL, All: TStringList;
ExtEntry: PExtensionEntry;
ClassEntry: PClassEntry;
S,
DescriptionFormat: String;
begin
Result := '';
if Formats = [] then Formats := [ftAnimation..ftVector];
DL := TStringList.Create;
DL.Sorted := SortType in [fstDescription, fstBoth];
// dl.Duplicates := dupAccept;
EL := TStringList.Create;
EL.Sorted := SortType in [fstExtension, fstBoth];
// this string list is used to hold the (possibly sorted) list of all allowed extensions
All := TStringList.Create;
All.Sorted := SortType in [fstExtension, fstBoth];
try
// using an adjusted format string makes the code below easier for different options
DescriptionFormat := '%s';
if foIncludeExtension in Options then DescriptionFormat := DescriptionFormat + '%s';
if foCompact in Options then
begin
// all extension for a particular image class on one line
for I := 0 to FClassList.Count - 1 do
begin
ClassEntry := FClassList[I];
if (GraphicClass = nil) or (GraphicClass = ClassEntry.GraphicClass) then
begin
EL.Clear;
// collect allowed extensions for the current graphic class,
// this will automatically sort the entries if wanted
for J := 0 to FExtensionList.Count - 1 do
begin
ExtEntry := FExtensionList[J];
if (ExtEntry.ClassReference = ClassEntry) and ((ExtEntry.FormatTypes * Formats) <> []) then
EL.Add(ExtEntry.Extension);
end;
// build the extension list and an description entry
if foIncludeAll in Options then All.AddStrings(EL);
S := '';
for J := 0 to EL.Count - 1 do S := S + '*.' + EL[J] + '; ';
// remove last semicolon and space
SetLength(S, Length(S) - 2);
if S <> '' then DL.AddObject(ClassEntry.Description, Pointer(StrNew(PChar(S))));
end;
end;
end
else
begin
// list each extension separately
for I := 0 to FExtensionList.Count - 1 do
begin
ExtEntry := FExtensionList[I];
if ((GraphicClass = nil) or (ExtEntry.ClassReference.GraphicClass = GraphicClass)) and
((ExtEntry.FormatTypes * Formats) <> []) then
begin
S := ExtEntry.Description;
if S = '' then S := ExtEntry.ClassReference.Description;
if DL.IndexOf(S) = -1 then //@@@ SZ Patched to avoid Memory Leak
DL.AddObject(S, Pointer(StrNew(PChar('*.' + ExtEntry.Extension))));
if foIncludeAll in Options then All.Add(ExtEntry.Extension);
end;
end;
end;
// build final filter string out of the collected sub strings
if (foIncludeAll in Options) and (All.Count > 0) then
begin
// first include the general entry if wanted (this entry is never taken into sort order
S := '';
for J := 0 to All.Count - 1 do S := S + '*.' + All[J] + '; ';
SetLength(S, Length(S) - 2);
Result := gesAllImages + '|' + S + '|';
end;
for I := 0 to DL.Count - 1 do
begin
S := PChar(DL.Objects[I]);
StrDispose(PChar(DL.Objects[I]));
Result := Result + Format(DescriptionFormat, [DL[I], ' (' + S + ')']) + '|' + S + '|';
end;
// remove last separator in string
if Length(Result) > 0 then SetLength(Result, Length(Result) - 1);
finally
All.Free;
EL.Free;
DL.Free;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TFileFormatList.GraphicFromExtension(S: String): TGraphicClass;
// Returns the class which belongs to the extension given in S or nil if there's non registered.
// S may contain a regular file name (also UNC is allowed), a string returned from ExtractFileExt (with period) or just
// an extension string.
var
Index: Integer;
begin
Result := nil;
Index := Pos('.', S);
if Index > 0 then Delete(S, 1, Index);
Index := FindExtension(S);
if Index > -1 then Result := PExtensionEntry(FExtensionList[Index]).ClassReference.GraphicClass;
end;
//----------------------------------------------------------------------------------------------------------------------
function TFileFormatList.GraphicFromContent(const FileName: String): TGraphicExGraphicClass;
// description see other overloaded version
var
Stream: TFileStream;
begin
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Result := GraphicFromContent(Stream);
finally
Stream.Free;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TFileFormatList.GraphicFromContent(Stream: TStream): TGraphicExGraphicClass;
// Determines the type of image in the stream. This test is only available for TGraphicExGraphic
// classes (this excludes TBitmap, TIcon, TMetaFile etc.).
// Note: Not all image types can be found using this code because they are not
// uniquely identifyable (e.g. Dr. Halo *.cut images).
var
I: Integer;
T: TGraphicExGraphicClass;
begin
Result := nil;
for I := 0 to FClassList.Count - 1 do
begin
if PClassEntry(FClassList[I]).GraphicClass.InheritsFrom(TGraphicExGraphic) then
begin
T := TGraphicExGraphicClass(PClassEntry(FClassList[I]).GraphicClass);
if T.CanLoad(Stream) then
begin
Result := T;
Break;
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TFileFormatList.RegisterFileFormat(const Extension, Common, Individual: String; FormatTypes: TFormatTypes;
Replace, RegisterDefault: Boolean; GraphicClass: TGraphicClass);
// Registers the given graphic class with the passed extension string. If there's already a class registered with this
// extension then either the registration of the older entry is replaced by the new one (Replace = True) or an exception
// is raised.
// This method takes also care to register the new extension with TPicture to make the default handling work too
// if RegisterDefault is True.
// Further parameters are:
// - Extension: the new extension to be registered (not necessarily with only 3 characters, but without a period).
// - Common: a description string for all extensions registered with the same class used when several extensions are
// listed on one filter line. Pass '' to avoid changing a previously set value if there's one.
// - Individual: a description string used when each extension is listed separately.
// - FormatTypes: classifies the given file type as being a raster or vector file, with single or multiple images etc.
// - GraphicClass: the TGraphic descentant to be used to load and save the particular file.
var
ExtIndex,
ClassIndex: Integer;
ExtEntry: PExtensionEntry;
ClassEntry,
OldReference: PClassEntry;
//--------------- local functions -------------------------------------------
procedure UpdateClassEntry;
// updates a class entry (creates one if necessary)
begin
if ClassIndex = -1 then
begin
New(ClassEntry);
ClassEntry.GraphicClass := GraphicClass;
ClassEntry.Count := 0;
FClassList.Add(ClassEntry);
end
else
ClassEntry := FClassList[ClassIndex];
if Common <> '' then ClassEntry.Description := Common;
Inc(ClassEntry.Count);
ExtEntry.ClassReference := ClassEntry;
end;
//--------------- end local functions ---------------------------------------
var
S: String;
begin
if Extension <> '' then
begin
ExtIndex := FindExtension(Extension);
ClassIndex := FindGraphicClass(GraphicClass);
if ExtIndex = -1 then
begin
// extension not yet registered
New(ExtEntry);
ExtEntry.Extension := Extension;
ExtEntry.Description := Individual;
ExtEntry.FormatTypes := FormatTypes;
FExtensionList.Add(ExtEntry);
UpdateClassEntry;
end
else
if Replace then
begin
// replace current extension entry with new one
ExtEntry := FExtensionList[ExtIndex];
if ExtEntry.ClassReference.GraphicClass <> GraphicClass then
begin
// assign existing extension to new graphic class
OldReference := ExtEntry.ClassReference;
UpdateClassEntry;
Dec(OldReference.Count);
// remove the graphic class entry if no longer used
if OldReference.Count = 0 then FClassList.Remove(OldReference);
end;
// otherwise do nothing
end
else
GraphicExError(gesRegistration, [Extension]);
// finally make TPicture work
S := Individual;
if S = '' then S := ClassEntry.Description;
TPicture.RegisterFileFormat(Extension, S, GraphicClass);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TFileFormatList.UnregisterFileFormat(const Extension: String; GraphicClass: TGraphicClass);
// Removes the entry for the given extension from the internal list.
// If Extension is '' then all associations for the given GraphicClass are removed otherwise the class is ignored and
// only the one particular extension is removed.
// Unregistration from TPicture is done here too, if necessary.
var
ExtIndex,
ClassIndex: Integer;
ExtEntry: PExtensionEntry;
ClassEntry: PClassEntry;
begin
ExtIndex := FindExtension(Extension);
// make sure we don't try to remove a non-registered extension
if (Extension = '') or (ExtIndex > -1) then
begin
if ExtIndex > -1 then
begin
// there's an entry for the extension
ExtEntry := FExtensionList[ExtIndex];
Dec(ExtEntry.ClassReference.Count);
// unregister graphic class too if necessary
if ExtEntry.ClassReference.Count = 0 then
begin
TPicture.UnregisterGraphicClass(ExtEntry.ClassReference.GraphicClass);
Dispose(ExtEntry.ClassReference);
FClassList.Remove(ExtEntry.ClassReference);
end;
// finally delete extension entry
Dispose(ExtEntry);
FExtensionList.Delete(ExtIndex);
end
else
begin
// all entries for the given graphic class must be removed
ClassIndex := FindGraphicClass(GraphicClass);
ClassEntry := FClassList[ClassIndex];
for ExtIndex := FExtensionList.Count - 1 downto 0 do
begin
if PExtensionEntry(FExtensionList[ExtIndex]).ClassReference.GraphicClass = GraphicClass then
begin
Dec(ClassEntry.Count);
Dispose(PExtensionEntry(FExtensionList[ExtIndex]));
FExtensionList.Delete(ExtIndex);
// no need to run through further entries if all references are done
if ClassEntry.Count = 0 then Break;
end;
end;
Dispose(ClassEntry);
FClassList.Delete(ClassIndex);
TPicture.UnregisterGraphicClass(GraphicClass);
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
initialization
FileFormatList := TFileFormatList.Create;
with FileFormatList do
begin
// internally register Delphi's "built in" formats, these will not be unregistered on exit and
// also not registered with TPicture (because they are already or will soon be)
RegisterFileFormat('bmp', gesBitmaps, '', [ftRaster], False, False, TBitmap);
RegisterFileFormat('ico', gesIcons, '', [ftRaster], False, False, TIcon);
RegisterFileFormat('wmf', gesMetaFiles, '', [ftVector], False, False, TMetafile);
RegisterFileFormat('emf', gesMetaFiles, gesEnhancedMetaFiles, [ftVector], False, False, TMetafile);
RegisterFileFormat('jfif', gesJPGImages, gesJFIFImages, [ftRaster], False, False, TJPEGImage);
RegisterFileFormat('jpg', '', gesJPGImages, [ftRaster], False, False, TJPEGImage);
RegisterFileFormat('jpe', '', gesJPEImages, [ftRaster], False, False, TJPEGImage);
RegisterFileFormat('jpeg', '', gesJPEGImages, [ftRaster], False, False, TJPEGImage);
// register our own formats
RegisterFileFormat('rle', gesBitmaps, gesRLEBitmaps, [ftRaster], False, True, TBitmap);
RegisterFileFormat('dib', '', gesDIBs, [ftRaster], False, True, TBitmap);
{$ifdef TargaGraphic}
RegisterFileFormat('win', gesTruevision, '', [ftRaster], False, True, TTargaGraphic);
RegisterFileFormat('vst', '', '', [ftRaster], False, True, TTargaGraphic);
RegisterFileFormat('vda', '', '', [ftRaster], False, True, TTargaGraphic);
RegisterFileFormat('tga', '', '', [ftRaster], False, True, TTargaGraphic);
RegisterFileFormat('icb', '', '', [ftRaster], False, True, TTargaGraphic);
{$endif}
{$ifdef TIFFGraphic}
RegisterFileFormat('tiff', gesTIFF, gesMacTIFF, [ftRaster, ftMultiImage], False,
True, TTIFFGraphic);
RegisterFileFormat('tif', '', gesPCTIF, [ftRaster, ftMultiImage], False, True, TTIFFGraphic);
RegisterFileFormat('fax', '', gesGFIFax, [ftRaster, ftMultiImage], False, True, TTIFFGraphic);
{$ifdef EPSGraphic}
RegisterFileFormat('eps', gesEPS, '', [ftRaster], False, True, TEPSGraphic);
{$endif}
{$endif}
{$ifdef PCXGraphic}
RegisterFileFormat('pcx', gesZSoft, '', [ftRaster], False, True, TPCXGraphic);
RegisterFileFormat('pcc', '', '', [ftRaster], False, True, TPCXGraphic);
RegisterFileFormat('scr', '', gesZSoftWord, [ftRaster], False, True, TPCXGraphic);
{$endif}
{$ifdef RLAGraphic}
RegisterFileFormat('rpf', gesAliasWaveFront, '', [ftRaster], False, True, TRLAGraphic);
RegisterFileFormat('rla', '', '', [ftRaster], False, True, TRLAGraphic);
{$endif}
{$ifdef SGIGraphic}
RegisterFileFormat('sgi', gesSGI, gesSGITrueColor, [ftRaster], False, True, TSGIGraphic);
RegisterFileFormat('rgba', '', gesSGITrueColorAlpha, [ftRaster], False, True, TSGIGraphic);
RegisterFileFormat('rgb', '', gesSGITrueColor, [ftRaster], False, True, TSGIGraphic);
RegisterFileFormat('bw', '', gesSGIMono, [ftRaster], False, True, TSGIGraphic);
{$endif}
{$ifdef PhotoshopGraphic}
RegisterFileFormat('psd', gesPhotoshop, '', [ftRaster, ftLayered], False, True, TPSDGraphic);
RegisterFileFormat('pdd', '', '', [ftRaster, ftLayered], False, True, TPSDGraphic);
{$endif}
{$ifdef PortableMapGraphic}
RegisterFileFormat('ppm', gesPortable, gesPortablePixel, [ftRaster], False, True, TPPMGraphic);
RegisterFileFormat('pgm', '', gesPortableGray, [ftRaster], False, True, TPPMGraphic);
RegisterFileFormat('pbm', '', gesPortableMono, [ftRaster], False, True, TPPMGraphic);
{$endif}
{$ifdef AutodeskGraphic}
RegisterFileFormat('cel', gesAutodesk, '', [ftRaster], False, True, TAutodeskGraphic);
RegisterFileFormat('pic', gesAutodesk, '', [ftRaster], False, True, TAutodeskGraphic);
{$endif}
{$ifdef PCDGraphic}
TPCDGraphic.DefaultResolution := 2;
RegisterFileFormat('pcd', gesKodakPhotoCD, '', [ftRaster], False, True, TPCDGraphic);
{$endif}
{$ifdef GIFGraphic}
RegisterFileFormat('gif', gesCompuserve, '', [ftRaster, ftMultiImage, ftAnimation], False, True, TGIFGraphic);
{$endif}
{$ifdef CUTGraphic}
RegisterFileFormat('cut', gesHalo, '', [ftRaster], False, True, TCUTGraphic);
{$endif}
{$ifdef PaintshopProGraphic}
RegisterFileFormat('psp', gesPaintshopPro, '', [ftRaster, ftVector], False, True, TPSPGraphic);
{$endif}
{$ifdef PortableNetworkGraphic}
RegisterFileFormat('png', gesPortableNetworkGraphic, '', [ftRaster], False, True, TPNGGraphic);
{$endif}
end;
finalization
with FileFormatList do
begin
{$ifdef PaintshopProGraphic} UnregisterFileFormat('', TPSPGraphic); {$endif}
{$ifdef PhotoshopGraphic} UnregisterFileFormat('', TPSDGraphic); {$endif}
{$ifdef TargaGraphic} UnregisterFileFormat('', TTargaGraphic); {$endif}
{$ifdef TIFFGraphic} UnregisterFileFormat('', TTIFFGraphic); {$endif}
{$ifdef SGIGraphic} UnregisterFileFormat('', TSGIGraphic); {$endif}
{$ifdef PCXGraphic} UnregisterFileFormat('', TPCXGraphic); {$endif}
{$ifdef AutodeskGraphic} UnregisterFileFormat('', TAutodeskGraphic); {$endif}
{$ifdef PCDGraphic} UnregisterFileFormat('', TPCDGraphic); {$endif}
{$ifdef PortableMapGraphic} UnregisterFileFormat('', TPPMGraphic); {$endif}
{$ifdef CUTGraphic} UnregisterFileFormat('', TCUTGraphic); {$endif}
{$ifdef GIFGraphic} UnregisterFileFormat('', TGIFGraphic); {$endif}
{$ifdef RLAGraphic} UnregisterFileFormat('', TRLAGraphic); {$endif}
UnregisterFileFormat('rle', TBitmap);
UnregisterFileFormat('dib', TBitmap);
{$ifdef PortableNetworkGraphic} UnregisterFileFormat('', TPNGGraphic); {$endif}
Free;
end;
end.