kolmck/Addons/KOLGraphicEx.pas

6436 lines
255 KiB
ObjectPascal

//{$DEFINE NOCLASSES}
{$IFDEF FPC}
{$DEFINE NOT_USE_KOL_ERR}
{$MODE Delphi}
{$ASMMODE intel}
{$GOTO ON}
{$ENDIF}
unit KOLGraphicEx;
// (c) Copyright 1999, 2000 Dipl. Ing. Mike Lischke (public@lischke-online.de). All rights reserved.
//
// This package is freeware for non-commercial use only.
// Contact author for licenses (shareware@lischke-online.de) and see License.txt which comes with the package.
//
// 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
//
// 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.
//
//
// (c) Copyright 1999, 2000 Dipl. Ing. Mike Lischke (public@lischke-online.de). All rights reserved.
//
// This package is freeware for non-commercial use only!
// Contact author for licenses (shareware@lischke-online.de) and see License.txt which comes with the package.
//////////////////////////////////////////////////
// Converted to KOL by Dimaxx (dimaxx@atnet.ru) //
//////////////////////////////////////////////////
interface
{$ALIGN OFF}
{$I KOLDEF.INC}
{$O+}
uses Windows, KOL, KOLGraphicCompression, KOLGraphicColor, Errors, {$IFDEF NOT_USE_KOL_ERR}sysutils {$ELSE}Err {$ENDIF} ;
type
TCardinalArray = array of cardinal;
PDWORDArray = ^TDWORDArray;
TDWORDArray = array[0..65535] 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, // Huffman 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 = packed 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.
PGraphicExGraphic = ^TGraphicExGraphic;
TGraphicExGraphic = {$IFDEF NOCLASSES} object(TObj) {$ELSE} class {$ENDIF}
private
FColorManager: PColorManager;
FImageProperties: TImageProperties;
FBasePosition: cardinal; // stream start position
FStream: PStream; // used for local references of the stream the class is currently loading from
FBitmap: PBitmap;
FCorrupted: Boolean;
protected
{$IFNDEF USE_GLOBALS}
CurrentLineR: array of integer;
CurrentLineG: array of integer;
CurrentLineB: array of integer;
{$ENDIF}
protected
function GetWidth: Integer;
function GetHeight: Integer;
public
constructor Create;
destructor Destroy; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
{$IFNDEF NOCLASSES}
class function CanLoad(const Filename: string): boolean; overload; virtual;
{$ENDIF}
{$IFNDEF NOCLASSES} class {$ENDIF}
function CanLoad(Stream: PStream): boolean;
{$IFNDEF NOCLASSES} overload; {$ENDIF} virtual;
function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean; virtual;
property Bitmap: PBitmap read FBitmap;
property ColorManager: PColorManager read FColorManager;
property ImageProperties: TImageProperties read FImageProperties write FImageProperties;
property Width: Integer read GetWidth;
property Height: Integer read GetHeight;
property Corrupted: Boolean read FCorrupted;
end;
{$IFNDEF NOCLASSES}
TGraphicExGraphicClass = class of TGraphicExGraphic;
{$ENDIF}
// *.bw, *.rgb, *.rgba, *.sgi images
TSGIGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic)
{$ELSE} class(TGraphicExGraphic) {$ENDIF}
private
FRowStart,
FRowSize: PDWORDArray; // start and length of a line (if compressed)
{$IFDEF NOCLASSES} FDecoder: PSGIRLEDecoder;
{$ELSE} FDecoder: TDecoder; {$ENDIF} // ...same applies here
procedure ReadAndDecode(Red,Green,Blue,Alpha: pointer; Row,BPC: cardinal);
public
{$IFNDEF NOCLASSES} class {$ENDIF}
function CanLoad(Stream: PStream): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
procedure LoadFromStream(Stream: PStream);
function ReadImageProperties(Stream: PStream; ImageIndex: cardinal):
boolean; {$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
end;
// *.cel, *.pic images
TAutodeskGraphic = {$IFDEF NOCLASSES} object( TGraphicExGraphic )
{$ELSE} class(TGraphicExGraphic) {$ENDIF}
public
{$IFNDEF NOCLASSES} class {$ENDIF}
function CanLoad(Stream: PStream): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
procedure LoadFromStream(Stream: PStream);
function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
end;
// *.tif, *.tiff images
// one entry in a an IFD (image file directory)
TIFDEntry = packed record
ATag: word;
DataType: word;
DataLength: cardinal;
Offset: cardinal;
end;
TTIFFPalette = array[0..787] of word;
PTIFFGraphic = ^TTIFFGraphic;
TTIFFGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic)
{$ELSE} class(TGraphicExGraphic) {$ENDIF}
private
FIFD: array of TIFDEntry; // the tags of one image file directory
FPalette: TTIFFPalette;
FYCbCrPositioning: cardinal;
FYCbCrCoefficients: TFloatArray;
function FindTag(ATag: cardinal; var Index: cardinal): boolean;
procedure GetValueList(Stream: PStream; ATag: cardinal; var Values: TByteArray); overload;
procedure GetValueList(Stream: PStream; ATag: cardinal; var Values: TCardinalArray); overload;
procedure GetValueList(Stream: PStream; ATag: cardinal; var Values: TFloatArray); overload;
function GetValue(Stream: PStream; ATag: cardinal; Default: single = 0): single; overload;
function GetValue(ATag: cardinal; Default: cardinal = 0): Cardinal; overload;
function GetValue(ATag: cardinal; var Size: cardinal; Default: cardinal = 0): cardinal; overload;
procedure SortIFD;
procedure SwapIFD;
public
{$IFNDEF NOCLASSES} class {$ENDIF}
function CanLoad(Stream: PStream): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
procedure LoadFromStream(Stream: PStream);
function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
end;
TEPSGraphic = {$IFDEF NOCLASSES} object(TTIFFGraphic)
{$ELSE} class(TTIFFGraphic) {$ENDIF}
public
{$IFNDEF NOCLASSES} class {$ENDIF}
function CanLoad(Stream: PStream): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
procedure LoadFromStream(Stream: PStream);
function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
end;
// *.tga; *.vst; *.icb; *.vda; *.win images
TTGAGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic)
{$ELSE} class(TGraphicExGraphic) {$ENDIF}
public
{$IFNDEF NOCLASSES} class {$ENDIF}
function CanLoad(Stream: PStream): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
procedure LoadFromStream(Stream: PStream);
function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
end;
// *.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.
PPCXGraphic = ^TPCXGraphic;
TPCXGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic)
{$ELSE} class(TGraphicExGraphic) {$ENDIF}
public
{$IFNDEF NOCLASSES} class {$ENDIF}
function CanLoad(Stream: PStream): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
procedure LoadFromStream(Stream: PStream);
function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
end;
// *.pcd images
// Note: By default the BASE resolution of a PCD image is loaded with LoadFromStream.
TPCDGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic)
{$ELSE} class(TGraphicExGraphic) {$ENDIF}
public
{$IFNDEF NOCLASSES} class {$ENDIF}
function CanLoad(Stream: PStream): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
procedure LoadFromStream(Stream: PStream);
function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
end;
// *.ppm, *.pgm, *.pbm images
PPPMGraphic = ^TPPMGraphic;
TPPMGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic)
{$ELSE} class(TGraphicExGraphic) {$ENDIF}
private
FBuffer: array[0..4095] of Char;
FIndex: integer;
function CurrentChar: Char;
function GetChar: Char;
function GetNumber: cardinal;
function ReadLine: string;
public
{$IFNDEF NOCLASSES} class {$ENDIF}
function CanLoad(Stream: PStream): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
procedure LoadFromStream(Stream: PStream);
function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
end;
// *.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 = {$IFDEF NOCLASSES} object(TGraphicExGraphic)
{$ELSE} class(TGraphicExGraphic) {$ENDIF}
private
FPaletteFile: string;
protected
procedure LoadPalette;
public
{$IFNDEF NOCLASSES} class {$ENDIF}
function CanLoad(Stream: PStream): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
procedure LoadFromStream(Stream: PStream);
function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
property PaletteFile: string read FPaletteFile write FPaletteFile;
end;
// *.gif images
{$IFDEF NOCLASSES} PGifGraphic = ^TGIFGraphic; {$ENDIF}
TGIFGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic)
{$ELSE} class(TGraphicExGraphic) {$ENDIF}
private
function SkipExtensions: byte;
public
{$IFNDEF NOCLASSES} class {$ENDIF}
function CanLoad(Stream: PStream): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
procedure LoadFromStream(Stream: PStream);
function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
function Transparent: Boolean;
function Count: Integer;
end;
// *.rla, *.rpf images
// implementation based on code from Dipl. Ing. Ingo Neumann (ingo@upstart.de, ingo_n@dialup.nacamar.de)
TRLAGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic)
{$ELSE} class(TGraphicExGraphic) {$ENDIF}
private
procedure SwapHeader(var Header); // start position of the image header in the stream
public
{$IFNDEF NOCLASSES} class {$ENDIF}
function CanLoad(Stream: PStream): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
procedure LoadFromStream(Stream: PStream);
function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
end;
// *.psd, *.pdd images
PPSDGraphic = ^TPSDGraphic;
TPSDGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic)
{$ELSE} class(TGraphicExGraphic) {$ENDIF}
public
{$IFNDEF NOCLASSES} class {$ENDIF}
function CanLoad(Stream: PStream): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
procedure LoadFromStream(Stream: PStream);
function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
end;
PPSPGraphic = ^TPSPGraphic;
// *.psp images (file version 3 and 4)
TPSPGraphic = {$IFDEF NOCLASSES} object( TGraphicExGraphic )
{$ELSE} class(TGraphicExGraphic) {$ENDIF}
public
{$IFNDEF NOCLASSES} class {$ENDIF}
function CanLoad(Stream: PStream): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
procedure LoadFromStream(Stream: PStream);
function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
end;
// *.png images
TChunkType = array[0..3] of Char;
// 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)
ChunkType: TChunkType;
end;
TPNGGraphic = {$IFDEF NOCLASSES} object(TGraphicExGraphic)
{$ELSE} class(TGraphicExGraphic) {$ENDIF}
private
{$IFDEF NOCLASSES}
FDecoder: PLZ77Decoder;
{$ELSE} FDecoder: TLZ77Decoder; {$ENDIF}
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
{$IFNDEF NOCLASSES} class {$ENDIF}
function CanLoad(Stream: PStream): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
procedure LoadFromStream(Stream: PStream);
function ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
{$IFDEF NOCLASSES} virtual; {$ELSE} override; {$ENDIF}
property BackgroundColor: TColor read FBackgroundColor;
property Transparency: TByteArray read FTransparency;
end;
// ---------- 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;
// 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: PBitmap;G: PGraphicExGraphic); overload;
procedure Stretch(NewWidth,NewHeight: cardinal; Filter: TResamplingFilter; Radius: single; Source: PBitmap; G: PGraphicExGraphic); overload;
var
Comp2Str: array[TCompressionType] of string = (
'Unknown','None','RLE','PackedBits','LZW','CCITT Fax3',
'Huffman RLE','CCITT Fax4','Huffman RLE word align',
'LZ77','JPEG','Old JPEG','Thunderscan','Next',
'IT8CTPAD','IT8LW','IT8MP','IT8BL','PixarFilm','PixarLog','DCS','JBIG',
'PCDHuffmann');
//----------------------------------------------------------------------------------------------------------------------
implementation
uses {$IFDEF NOT_USE_KOL_ERR}Math, {$ELSE}KOLMath, {$ENDIF} KolZLibBzip;
const
PNG = 'PNG';
TIF = 'TIF/TIFF';
type
// resampling support types
TRGBInt = packed record
R,G,B: integer;
end;
PRGBWord = ^TRGBWord;
TRGBWord = packed record
R,G,B: word;
end;
PRGBAWord = ^TRGBAWord;
TRGBAWord = packed 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 = packed record
Weight: integer; // Pixel Weight
Pixel: integer; // Source Pixel
end;
TContributors = array of TContributor;
// list of source pixels contributing to a destination pixel
TContributorEntry = packed 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);
{$IFDEF USE_GLOBALS}
threadvar // globally used cache for current image (speeds up resampling about 10%)
CurrentLineR: array of integer;
CurrentLineG: array of integer;
CurrentLineB: array of integer;
{$ENDIF}
function Rect(ALeft,ATop,ARight,ABottom: integer): TRect;
begin
with Result do
begin
Left:=ALeft;
Top:=ATop;
Right:=ARight;
Bottom:=ABottom;
end;
end;
function StrLIComp(const Str1,Str2: PChar; MaxLen: cardinal): integer; assembler;
asm
PUSH EDI
PUSH ESI
PUSH EBX
MOV EDI,EDX
MOV ESI,EAX
MOV EBX,ECX
XOR EAX,EAX
OR ECX,ECX
JE @@4
REPNE SCASB
SUB EBX,ECX
MOV ECX,EBX
MOV EDI,EDX
XOR EDX,EDX
@@1: REPE CMPSB
JE @@4
MOV AL,[ESI-1]
CMP AL,'a'
JB @@2
CMP AL,'z'
JA @@2
SUB AL,20H
@@2: MOV DL,[EDI-1]
CMP DL,'a'
JB @@3
CMP DL,'z'
JA @@3
SUB DL,20H
@@3: SUB EAX,EDX
JE @@1
@@4: POP EBX
POP ESI
POP EDI
end;
//----------------------------------------------------------------------------------------------------------------------
{$IFDEF NOT_USE_KOL_ERR}
procedure GraphicExError(Code: integer); overload;
var E: Exception;
begin
E:=Exception.Create(int2str(Code));
//E.ErrorCode:=Code;
raise E;
end;
{$ELSE}
procedure GraphicExError(Code: integer); overload;
var E: Exception;
begin
E:=Exception.Create(e_Custom,ErrorMsg[Code]);
E.ErrorCode:=Code;
raise E;
end;
{$ENDIF}
//----------------------------------------------------------------------------------------------------------------------
{$IFDEF NOT_USE_KOL_ERR}
procedure GraphicExError(Code: integer; Args: array of const); overload;
var E: Exception;
begin
E:=Exception.CreateFmt(int2str(Code),Args);
//E.ErrorCode:=Code;
raise E;
end;
{$ELSE}
procedure GraphicExError(Code: integer; Args: array of const); overload;
var E: Exception;
begin
E:=Exception.CreateFmt(e_Custom,ErrorMsg[Code],Args);
E.ErrorCode:=Code;
raise E;
end;
{$ENDIF}
//----------------------------------------------------------------------------------------------------------------------
procedure Upsample(Width,Height,ScaledWidth: cardinal; Pixels: PChar);
// Creates a new image that is a integral size greater than an existing one.
var X,Y: cardinal;
P,Q,R: PChar;
begin
for Y:=0 to pred(Height) 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 pred(Width) do
begin
Dec(P);
Dec(Q,2);
Q^:=P^;
(Q+1)^:=Char((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^:=Char((Word(P^)+Word(R^)+1) shr 1);
(Q+1)^:=Char((Word(P^)+Word((P+2)^)+Word(R^)+Word((R+2)^)+2) shr 2);
Inc(Q,2);
Inc(P,2);
Inc(R,2);
end;
Q^:=Char((Word(P^)+Word(R^)+1) shr 1);
Inc(P);
Inc(Q);
Q^:=Char((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;
var Temp,B,C: single;
begin
B:=1/3;
C:=1/3;
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; G: PGraphicExGraphic);
var I: integer;
Run: PBGR;
begin
Run:=Line;
for I:=0 to pred(N) do
begin
G.CurrentLineR[I]:=Run.R;
G.CurrentLineG[I]:=Run.G;
G.CurrentLineB[I]:=Run.B;
Inc(PByte(Run),Delta);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function ApplyContributors(N: integer; Contributors: TContributors; G: PGraphicExGraphic): TBGR;
var RGB: TRGBInt;
J,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 pred(N) do
begin
Weight:=Contr.Weight;
Inc(Total,Weight);
Pixel:=Contr.Pixel;
Inc(RGB.R,G.CurrentLineR[Pixel]*Weight);
Inc(RGB.G,G.CurrentLineG[Pixel]*Weight);
Inc(RGB.B,G.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: PBitmap;
G: PGraphicExGraphic);
// 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: PBitmap;
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:=NewBitmap(0,0);
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 pred(TargetWidth) 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 pred(TargetWidth) 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(G.CurrentLineR,SourceWidth);
SetLength(G.CurrentLineG,SourceWidth);
SetLength(G.CurrentLineB,SourceWidth);
for K:=0 to pred(SourceHeight) do
begin
SourceLine:=Source.ScanLine[K];
FillLineChache(SourceWidth,3,SourceLine,G);
DestPixel:=Work.ScanLine[K];
for I:=0 to pred(TargetWidth) do
with ContributorList[I] do
begin
DestPixel^:=ApplyContributors(N,ContributorList[I].Contributors,G);
// 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 pred(TargetWidth) 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 pred(TargetHeight) 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 pred(TargetHeight) 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(G.CurrentLineR,SourceHeight);
SetLength(G.CurrentLineG,SourceHeight);
SetLength(G.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 pred(TargetWidth) do
begin
DestPixel:=Pointer(DestLine);
FillLineChache(SourceHeight,Delta,SourceLine,G);
for I:=0 to pred(TargetHeight) do
with ContributorList[I] do
begin
DestPixel^:=ApplyContributors(N,ContributorList[I].Contributors,G);
Inc(Integer(DestPixel),DestDelta);
end;
Inc(SourceLine);
Inc(DestLine);
end;
// free the memory allocated for vertical filter weights
for I:=0 to pred(TargetHeight) do ContributorList[I].Contributors:=nil;
// this one is done automatically on exit, but is here for completeness
ContributorList:=nil;
finally
Work.Free;
G.CurrentLineR:=nil;
G.CurrentLineG:=nil;
G.CurrentLineB:=nil;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure Stretch(NewWidth,NewHeight: cardinal; Filter: TResamplingFilter; Radius: single; Source,Target: PBitmap;
G: PGraphicExGraphic);
// 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,G);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure Stretch(NewWidth,NewHeight: cardinal; Filter: TResamplingFilter;
Radius: single; Source: PBitmap; G: PGraphicExGraphic);
var Target: PBitmap;
begin
if Radius=0 then Radius:=DefaultFilterRadius[Filter];
Target:=NewBitmap(0,0);
try
Target.PixelFormat:=pf24Bit;
Target.Width:=NewWidth;
Target.Height:=NewHeight;
Source.PixelFormat:=pf24Bit;
DoStretch(FilterList[Filter],Radius,Source,Target,G);
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:=NewColorManager;
end;
function TGraphicExGraphic.GetWidth: Integer;
begin
Result := ImageProperties.Width;
end;
function TGraphicExGraphic.GetHeight: Integer;
begin
Result := ImageProperties.Height;
end;
//----------------------------------------------------------------------------------------------------------------------
destructor TGraphicExGraphic.Destroy;
begin
FColorManager.Free;
if Assigned(FBitmap) then FBitmap.Free;
inherited;
end;
//----------------------------------------------------------------------------------------------------------------------
{$IFNDEF NOCLASSES} class
function TGraphicExGraphic.CanLoad(const Filename: string): boolean;
var Stream: PStream;
begin
Stream:=NewReadFileStream(Filename);
try
Result:=CanLoad(Stream);
finally
Stream.Free;
end;
end;
{$ENDIF}
//----------------------------------------------------------------------------------------------------------------------
{$IFNDEF NOCLASSES} class {$ENDIF}
function TGraphicExGraphic.CanLoad(Stream: PStream): 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;
//----------------------------------------------------------------------------------------------------------------------
function TGraphicExGraphic.ReadImageProperties(Stream: PStream; 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 -----------------------------------------------------------------------------------
type
TAutodeskHeader = packed record
Width,Height,XCoord,YCoord: word;
Depth,Compression: byte;
DataSize: cardinal;
Reserved: array[0..15] of byte;
end;
//----------------------------------------------------------------------------------------------------------------------
{$IFNDEF NOCLASSES} class {$ENDIF}
function TAutodeskGraphic.CanLoad(Stream: PStream): boolean;
var FileID: word;
Header: TAutodeskHeader;
LastPosition: cardinal;
begin
Result:=(Stream.Size-Stream.Position)>(sizeof(FileID)+sizeof(Header));
if Result then
begin
LastPosition:=Stream.Position;
Stream.Read(FileID,sizeof(FileID));
Result:=FileID=$9119;
if Result then
begin
// read image dimensions
Stream.Read(Header,sizeof(Header));
Result:=(Header.Depth=8) and (Header.Compression=0);
end;
Stream.Position:=LastPosition;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TAutodeskGraphic.LoadFromStream(Stream: PStream);
var FileID: word;
FileHeader: TAutodeskHeader;
I: integer;
Pal: array[0..767] of byte;
begin
// free previous image
if Assigned(FBitmap) then FBitmap.Free;
FBasePosition:=Stream.Position;
if ReadImageProperties(Stream,0) then
begin
Stream.Position:=FBasePosition;
Stream.Read(FileID,2);
// read image dimensions
Stream.Read(FileHeader,sizeof(FileHeader));
// read palette data
Stream.Read(Pal,sizeof(Pal));
// setup bitmap properties
FBitmap:=NewBitmap(FileHeader.Width,FileHeader.Height);
FBitmap.PixelFormat:=pf8Bit;
// assign palette data
for I:=0 to 255 do
FBitmap.DIBPalEntries[I]:=RGB(Pal[I*3+2] shl 2,Pal[I*3+1] shl 2,Pal[I*3] shl 2);
// finally read image data
for I:=0 to pred(FBitmap.Height) do Stream.Read(FBitmap.ScanLine[I]^,FileHeader.Width);
end
else GraphicExError(1{gesInvalidImage},['Autodesk']);
end;
//----------------------------------------------------------------------------------------------------------------------
function TAutodeskGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
var FileID: word;
Header: TAutodeskHeader;
begin
Result:=inherited ReadImageProperties(Stream,ImageIndex);
with FImageProperties do
begin
Stream.Read(FileID,2);
if FileID=$9119 then
begin
// read image dimensions
Stream.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;
//----------------- TSGIGraphic ----------------------------------------------------------------------------------------
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 Char;
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;
//----------------------------------------------------------------------------------------------------------------------
{$IFNDEF NOCLASSES} class {$ENDIF}
function TSGIGraphic.CanLoad(Stream: PStream): boolean;
// returns True if the data in Stream represents a graphic which can be loaded by this class
var Header: TSGIHeader;
LastPosition: cardinal;
begin
Result:=(Stream.Size-Stream.Position)>sizeof(TSGIHeader);
if Result then
begin
LastPosition:=Stream.Position;
Stream.Read(Header,sizeof(Header));
// one number as check is too unreliable, hence we take some more fields into the check
Result:=(System.Swap(Header.Magic)=SGIMagic) and (Header.BPC in [1,2]) and (System.Swap(Header.Dimension) in [1..3]);
Stream.Position:=LastPosition;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TSGIGraphic.ReadAndDecode(Red,Green,Blue,Alpha: pointer; Row,BPC: cardinal);
var Count: cardinal;
RawBuffer: pointer;
begin
with FImageProperties do
// compressed image?
if Assigned(FDecoder) then
begin
if Assigned(Red) then
begin
FStream.Position:=FBasePosition+FRowStart[Row+0*Height];
Count:=BPC*FRowSize[Row+0*Height];
GetMem(RawBuffer,Count);
try
FStream.Read(RawBuffer^,Count);
FDecoder.Decode(RawBuffer,Red,Count,Width);
finally
if Assigned(RawBuffer) then FreeMem(RawBuffer);
end;
end;
if Assigned(Green) then
begin
FStream.Position:=FBasePosition+FRowStart[Row+1*Height];
Count:=BPC*FRowSize[Row+1*Height];
GetMem(RawBuffer,Count);
try
FStream.Read(RawBuffer^,Count);
FDecoder.Decode(RawBuffer,Green,Count,Width);
finally
if Assigned(RawBuffer) then FreeMem(RawBuffer);
end;
end;
if Assigned(Blue) then
begin
FStream.Position:=FBasePosition+FRowStart[Row+2*Height];
Count:=BPC*FRowSize[Row+2*Height];
GetMem(RawBuffer,Count);
try
FStream.Read(RawBuffer^,Count);
FDecoder.Decode(RawBuffer,Blue,Count,Width);
finally
if Assigned(RawBuffer) then FreeMem(RawBuffer);
end;
end;
if Assigned(Alpha) then
begin
FStream.Position:=FBasePosition+FRowStart[Row+3*Height];
Count:=BPC*FRowSize[Row+3*Height];
GetMem(RawBuffer,Count);
try
FStream.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
FStream.Position:=FBasePosition+512+(Row*Width);
FStream.Read(Red^,BPC*Width);
end;
if Assigned(Green) then
begin
FStream.Position:=FBasePosition+512+(Row*Width)+(Width*Height);
FStream.Read(Green^,BPC*Width);
end;
if Assigned(Blue) then
begin
FStream.Position:=FBasePosition+512+(Row*Width)+(2*Width*Height);
FStream.Read(Blue^,BPC*Width);
end;
if Assigned(Alpha) then
begin
FStream.Position:=FBasePosition+512+(Row*Width)+(3*Width*Height);
FStream.Read(Alpha^,BPC*Width);
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TSGIGraphic.LoadFromStream(Stream: PStream);
var Y,Count: cardinal;
RedBuffer,GreenBuffer,BlueBuffer,AlphaBuffer: pointer;
Header: TSGIHeader;
begin
// free previous image
if Assigned(FBitmap) then FBitmap.Free;
// keep stream reference and start position for seek operations
FStream:=Stream;
FBasePosition:=Stream.Position;
if ReadImageProperties(Stream,0) then
begin
with FImageProperties do
begin
Stream.Position:=FBasePosition;
// read header again, we need some additional information
Stream.Read(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;
GetMem(FRowStart,Count*4);
GetMem(FRowSize,Count*4);
// read line starts and sizes from stream
Stream.Read(FRowStart^,Count*sizeof(Cardinal));
SwapLong(Pointer(FRowStart),Count);
Stream.Read(FRowSize^,Count*sizeof(Cardinal));
SwapLong(Pointer(FRowSize),Count);
{$IFDEF NOCLASSES} new( FDecoder, Create(BitsPerSample) );
{$ELSE} FDecoder:=TSGIRLEDecoder.Create(BitsPerSample); {$ENDIF}
end
else
begin
FDecoder:=nil;
end;
// set pixel format before size to avoid possibly large conversion operation
ColorManager.SourceBitsPerSample:=BitsPerSample;
ColorManager.TargetBitsPerSample:=8;
ColorManager.SourceSamplesPerPixel:=SamplesPerPixel;
ColorManager.TargetSamplesPerPixel:=SamplesPerPixel;
ColorManager.SourceColorScheme:=ColorScheme;
case ColorScheme of
csRGBA: ColorManager.TargetColorScheme:=csBGRA;
csRGB: ColorManager.TargetColorScheme:=csBGR;
else ColorManager.TargetColorScheme:=csIndexed;
end;
FBitmap:=NewBitmap(Width,Height);
FBitmap.PixelFormat:=ColorManager.TargetPixelFormat;
RedBuffer:=nil;
GreenBuffer:=nil;
BlueBuffer:=nil;
AlphaBuffer:=nil;
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 pred(Height) do
begin
ReadAndDecode(RedBuffer,GreenBuffer,BlueBuffer,AlphaBuffer,Y,Header.BPC);
ColorManager.ConvertRow([RedBuffer,GreenBuffer,BlueBuffer,AlphaBuffer],FBitmap.ScanLine[Height-Y-1],Width,$FF);
end;
end;
csRGB:
begin
GetMem(RedBuffer,Count);
GetMem(GreenBuffer,Count);
GetMem(BlueBuffer,Count);
for Y:=0 to pred(Height) do
begin
ReadAndDecode(RedBuffer,GreenBuffer,BlueBuffer,nil,Y,Header.BPC);
ColorManager.ConvertRow([RedBuffer,GreenBuffer,BlueBuffer],FBitmap.ScanLine[Height-Y-1],Width,$FF);
end;
end;
else
// any other format is interpreted as being 256 gray scales
ColorManager.CreateGrayscalePalette(FBitmap,False);
for Y:=0 to pred(Height) do
begin
ReadAndDecode(FBitmap.ScanLine[Height-Y-1],nil,nil,nil,Y,Header.BPC);
end;
end;
finally
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(1{gesInvalidImage},['SGI, BW or RGB(A)']);
FreeMem(FRowStart {,Count*4} );
FreeMem(FRowSize {,Count*4} );
end;
//----------------------------------------------------------------------------------------------------------------------
function TSGIGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
var Header: TSGIHeader;
begin
Result:=inherited ReadImageProperties(Stream,ImageIndex);
with FImageProperties do
begin
Stream.Read(Header,sizeof(Header));
if System.Swap(Header.Magic)=SGIMagic then
begin
Options:=[ioBigEndian];
BitsPerSample:=Header.BPC*8;
Width:=System.Swap(Header.XSize);
Height:=System.Swap(Header.YSize);
SamplesPerPixel:=System.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;
//----------------- TTIFFGraphic ---------------------------------------------------------------------------------------
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;
//----------------------------------------------------------------------------------------------------------------------
{$IFNDEF NOCLASSES} class {$ENDIF}
function TTIFFGraphic.CanLoad(Stream: PStream): boolean;
var Header: TTIFFHeader;
LastPosition: Cardinal;
begin
Result:=(Stream.Size-Stream.Position)>sizeof(Header);
if Result then
begin
LastPosition:=Stream.Position;
Stream.Read(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:=System.Swap(Header.Version);
Header.FirstIFD:=SwapLong(Header.FirstIFD);
end;
Result:=(Header.Version=TIFF_VERSION) and
(Integer(Header.FirstIFD)<Integer(Stream.Size-LastPosition));
end;
Stream.Position:=LastPosition;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TTIFFGraphic.FindTag(ATag: 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].ATag)-Integer(ATag);
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: PStream; ATag: cardinal; var Values: TByteArray);
// returns the values of the IFD entry indicated by Tag
var Index,Value,Shift: cardinal;
I: Integer;
begin
if FindTag(ATag,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 pred(FIFD[Index].DataLength) 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:=System.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: PStream; ATag: 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(ATag,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 pred(FIFD[Index].DataLength) 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:=System.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: PStream; ATag: 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,FloatNominator,FloatDenominator: cardinal;
begin
// Values:=nil;
if FindTag(ATag,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 pred(FIFD[Index].DataLength) 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:=System.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.Read(FloatNominator,sizeof(FloatNominator));
Stream.Read(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.Read(IntNominator,sizeof(IntNominator));
Stream.Read(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: PStream; ATag: 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(ATag,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.Read(FloatNominator,sizeof(FloatNominator));
Stream.Read(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.Read(IntNominator,sizeof(IntNominator));
Stream.Read(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(ATag: 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(ATag, 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(ATag: 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(ATag,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].ATag<FIFD[M].ATag do Inc(I);
while FIFD[J].ATag>FIFD[M].ATag 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
ATag:=System.Swap(ATag);
DataType:=System.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:=System.Swap(Word(Offset));
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TTIFFGraphic.LoadFromStream(Stream: PStream);
var IFDCount: word;
Buffer: pointer;
Run: PChar;
Pixels,EncodedData,DataPointerCopy: pointer;
Offsets,ByteCounts: TCardinalArray;
ColorMap: cardinal;
StripSize: cardinal;
{$IFDEF NOCLASSES} Decoder: PDecoder; {$ELSE} Decoder: TDecoder; {$ENDIF}
{$IFDEF NOCLASSES}
TIFFLZWDecoder: PTiffLzwDecoder;
PackBitsRLEDecoder: PPackBitsRLEDecoder;
CCITTMHDecoder: PCCITTMHDecoder;
CCITTFax3Decoder: PCCITTFax3Decoder;
CCITTFax4Decoder: PCCITTFax4Decoder;
LZ77Decoder: PLZ77Decoder;
{$ENDIF}
// dynamically assigned handler
Deprediction: procedure(P: pointer; Count: cardinal);
begin
// free previous image
if Assigned(FBitmap) then FBitmap.Free;
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 FImageProperties.Options then Exit;
// read data of the first image file directory (IFD)
Stream.Position:=FBasePosition+FirstIFD;
Stream.Read(IFDCount,sizeof(IFDCount));
if ioBigEndian in Options then IFDCount:=System.Swap(IFDCount);
SetLength(FIFD,IFDCount);
Stream.Read(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
if ioBigEndian in Options then
ColorManager.SourceOptions:=[coNeedByteSwap]
else
ColorManager.SourceOptions:=[];
ColorManager.SourceBitsPerSample:=FImageProperties.BitsPerSample;
if ColorManager.SourceBitsPerSample=16 then
ColorManager.TargetBitsPerSample:=8
else
ColorManager.TargetBitsPerSample:=ColorManager.SourceBitsPerSample;
// the JPEG lib does internally a conversion to RGB
if FImageProperties.Compression in [ctOJPEG,ctJPEG] then
ColorManager.SourceColorScheme:=csBGR
else
ColorManager.SourceColorScheme:=FImageProperties.ColorScheme;
case ColorManager.SourceColorScheme of
csRGBA: ColorManager.TargetColorScheme:=csBGRA;
csRGB: ColorManager.TargetColorScheme:=csBGR;
csCMY,csCMYK,csCIELab,csYCbCr: ColorManager.TargetColorScheme:=csBGR;
csIndexed:
begin
if HasAlpha then
ColorManager.SourceColorScheme:=csGA; // fake indexed images with alpha (used in EPS)
// as being grayscale with alpha
ColorManager.TargetColorScheme:=csIndexed;
end;
else
ColorManager.TargetColorScheme:=ColorManager.SourceColorScheme;
end;
ColorManager.SourceSamplesPerPixel:=FImageProperties.SamplesPerPixel;
// now that the pixel format is set we can also set the (possibly large) image dimensions
FBitmap:=NewBitmap(FImageProperties.Width,FImageProperties.Height);
if ColorManager.SourceColorScheme=csCMYK then
ColorManager.TargetSamplesPerPixel:=3
else
ColorManager.TargetSamplesPerPixel:=FImageProperties.SamplesPerPixel;
if ColorManager.SourceColorScheme=csCIELab then
ColorManager.SourceOptions:=ColorManager.SourceOptions+[coLabByteRange];
if ColorManager.SourceColorScheme=csGA then
FBitmap.PixelFormat:=pf8Bit
else
FBitmap.PixelFormat:=ColorManager.TargetPixelFormat;
if (FImageProperties.Width=0) or (FImageProperties.Height=0) then
GraphicExError(1{gesInvalidImage},[TIF]);
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.Read(FPalette[0],2*StripSize);
ColorManager.CreateColorPalette(FBitmap,[@FPalette[0],
@FPalette[StripSize div 3],
@FPalette[2*StripSize div 3]],
pfPlane16Triple,StripSize div 3,False);
end;
end
else ColorManager.CreateGrayScalePalette(FBitmap,ioMinIsWhite in Options);
end
else
if ColorManager.SourceColorScheme=csYCbCr then
ColorManager.SetYCbCrParameters(
FYCbCrCoefficients,YCbCrSubSampling[0],YCbCrSubSampling[1]);
// intermediate buffer for data
FImageProperties.BytesPerLine:=(FImageProperties.BitsPerPixel*FImageProperties.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 FImageProperties.SamplesPerPixel of
4: Deprediction:=Depredict4;
3: Deprediction:=Depredict3;
else Deprediction:=Depredict1;
end;
end;
end;
// create decompressor for the image
CASE FImageProperties.Compression of
ctNone: ;
ctLZW: begin
{$IFDEF NOCLASSES} new( TIFFLZWDecoder, Create );
Decoder := TIFFLZWDecoder;
{$ELSE} Decoder:=TTIFFLZWDecoder.Create; {$ENDIF}
end;
ctPackedBits: begin
{$IFDEF NOCLASSES} new( PackBitsRLEDecoder, Create );
Decoder := PackBitsRLEDecoder;
{$ELSE} Decoder:=TPackbitsRLEDecoder.Create; {$ENDIF}
end;
ctFaxRLE,
ctFaxRLEW: begin
{$IFDEF NOCLASSES}
new( CCITTMHDecoder, Create(GetValue(TIFFTAG_GROUP3OPTIONS),
ioReversed in Options,Compression=ctFaxRLEW,Width) );
Decoder := CCITTMHDecoder;
{$ELSE} Decoder:=
TCCITTMHDecoder.Create(GetValue(TIFFTAG_GROUP3OPTIONS),
ioReversed in Options,Compression=ctFaxRLEW,Width);
{$ENDIF}
end;
ctFax3: begin
{$IFDEF NOCLASSES}
new( CCITTFax3Decoder, Create(GetValue(TIFFTAG_GROUP3OPTIONS),ioReversed in Options,False,
Width) ); Decoder := CCITTFax3Decoder;
{$ELSE}
Decoder:=TCCITTFax3Decoder.Create(
GetValue(TIFFTAG_GROUP3OPTIONS),ioReversed in Options,False,
Width);
{$ENDIF}
end;
ctFax4: begin
{$IFDEF NOCLASSES}
new( CCITTFax4Decoder, Create(GetValue(TIFFTAG_GROUP4OPTIONS),ioReversed in Options,False,
Width) ); Decoder := CCITTFax4Decoder;
{$ELSE}
Decoder:=TCCITTFax4Decoder.Create(
GetValue(TIFFTAG_GROUP4OPTIONS),ioReversed in Options,False,
Width);
{$ENDIF}
end;
ctThunderscan: begin
{$IFDEF NOCLASSES}
new( LZ77Decoder, Create(Z_PARTIAL_FLUSH,True) );
Decoder := LZ77Decoder;
{$ELSE}
Decoder:=TThunderDecoder.Create(Width);
{$ENDIF}
end;
ctLZ77: begin
{$IFDEF NOCLASSES}
new( LZ77Decoder, Create(Z_PARTIAL_FLUSH,True) );
Decoder := LZ77Decoder;
{$ELSE}
Decoder:=TLZ77Decoder.Create(Z_PARTIAL_FLUSH,True);
{$ENDIF}
end;
else
{
COMPRESSION_OJPEG,
COMPRESSION_CCITTFAX4
COMPRESSION_NEXT
COMPRESSION_IT8CTPAD
COMPRESSION_IT8LW
COMPRESSION_IT8MP
COMPRESSION_IT8BL
COMPRESSION_PIXARFILM
COMPRESSION_PIXARLOG
COMPRESSION_DCS
COMPRESSION_JBIG}
GraphicExError(5{gesUnsupportedFeature},[ErrorMsg[11]{gesCompressionScheme},'compression',TIF]);
end;
if Assigned(Decoder) then Decoder.DecodeInit;
// go for each strip in the image (which might contain more than one line)
FImageProperties.CurrentRow:=0;
FImageProperties.CurrentStrip:=0;
StripCount:=Length(Offsets);
while FImageProperties.CurrentStrip<FImageProperties.StripCount do
begin
Stream.Position:=FBasePosition+Offsets[CurrentStrip];
if CurrentStrip<Length(RowsPerStrip) then
StripSize:=FImageProperties.BytesPerLine*RowsPerStrip[FImageProperties.CurrentStrip]
else
StripSize:=FImageProperties.BytesPerLine*RowsPerStrip[High(RowsPerStrip)];
GetMem(Buffer,StripSize+{!reserve}100);
Run:=Buffer;
try
// decompress strip if necessary
if Assigned(Decoder) then
begin
GetMem(EncodedData,ByteCounts[FImageProperties.CurrentStrip]{reserve!}+100);
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 (FImageProperties.CurrentRow<FImageProperties.Height) and
(Run-Buffer<Integer(StripSize)) do
begin
Pixels:=FBitmap.ScanLine[FImageProperties.CurrentRow];
// depredict strip if necessary
if Assigned(Deprediction) then Deprediction(Run,Width-1);
// any color conversion comes last
ColorManager.ConvertRow([Run],Pixels,FImageProperties.Width,$FF);
Inc(PChar(Run),BytesPerLine);
Inc(CurrentRow);
end;
finally
if Assigned(Buffer) then FreeMem(Buffer);
end;
Inc(CurrentStrip);
end;
finally
if Assigned(Decoder) then Decoder.DecodeEnd;
Decoder.Free;
end;
end
else GraphicExError(1{gesInvalidImage},[TIF]);
end;
//----------------------------------------------------------------------------------------------------------------------
const
PhotometricToColorScheme: array[PHOTOMETRIC_MINISWHITE..PHOTOMETRIC_CIELAB] of TColorScheme = (
csG,csG,csRGBA,csIndexed,csUnknown,csCMYK,csYCbCr,csUnknown,csCIELab);
function TTIFFGraphic.ReadImageProperties(Stream: PStream; 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.
var IFDCount: word;
ExtraSamples,LocalBitsPerSample: TCardinalArray;
PhotometricInterpretation: byte;
TIFCompression: word;
Index,IFDOffset: cardinal;
Header: TTIFFHeader;
begin
// clear image properties
Result:=inherited ReadImageProperties(Stream,ImageIndex);
with FImageProperties do
begin
// rewind stream to header position
Stream.Position:=FBasePosition;
Stream.Read(Header,sizeof(Header));
if Header.ByteOrder=TIFF_BIGENDIAN then
begin
Options:=Options+[ioBigEndian];
Header.Version:=System.Swap(Header.Version);
Header.FirstIFD:=SwapLong(Header.FirstIFD);
end;
FImageProperties.Version:=Header.Version;
FirstIFD:=Header.FirstIFD;
if FImageProperties.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.Read(IFDCount,sizeof(IFDCount));
if Header.ByteOrder=TIFF_BIGENDIAN then IFDCount:=System.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),spCurrent);
Stream.Read(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.Read(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: Compression:=ctPixarLog; // also a LZ77 clone
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 ----------------------------------------------------------------------------------------
// 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;
//----------------------------------------------------------------------------------------------------------------------
{$IFNDEF NOCLASSES} class {$ENDIF}
function TEPSGraphic.CanLoad(Stream: PStream): boolean;
var Header: TEPSHeader;
LastPosition: cardinal;
begin
Result:=(Stream.Size-Stream.Position)>sizeof(Header);
if Result then
begin
LastPosition:=Stream.Position;
Stream.Read(Header,sizeof(Header));
Result:=(Header.Code=$C6D3D0C5) and (Header.TiffPos>Integer(LastPosition)+sizeof(Header)) and (Header.TiffLen>0);
Stream.Position:=LastPosition;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TEPSGraphic.LoadFromStream(Stream: PStream);
var Header: TEPSHeader;
begin
// free previous image
if Assigned(FBitmap) then FBitmap.Free;
Stream.Read(Header,sizeof(Header));
if Header.Code<>$C6D3D0C5 then GraphicExError(1{gesInvalidImage},['EPS']);
Stream.Seek(Header.TIFFPos-sizeof(Header),spCurrent);
inherited;
end;
//----------------------------------------------------------------------------------------------------------------------
function TEPSGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
begin
Result:=inherited ReadImageProperties(Stream,ImageIndex);
end;
//----------------- TTGAGraphic --------------------------------------------------------------------------------------
// 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;
//----------------------------------------------------------------------------------------------------------------------
{$IFNDEF NOCLASSES} class {$ENDIF}
function TTGAGraphic.CanLoad(Stream: PStream): boolean;
var Header: TTargaHeader;
LastPosition: cardinal;
begin
LastPosition:=Stream.Position;
Result:=(Stream.Size-Stream.Position)>sizeof(Header);
if Result then
begin
Stream.Read(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;
Stream.Position:=LastPosition;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TTGAGraphic.LoadFromStream(Stream: PStream);
var Run,RLEBuffer: PChar;
I,LineSize: Integer;
LineBuffer: Pointer;
ReadLength: Integer;
Color16: Word;
Header: TTargaHeader;
FlipV: Boolean;
{$IFDEF NOCLASSES} Decoder: PTargaRLEDecoder;
//{$ELSE} Decoder: PTargaRLEDecoder;
{$ELSE} Decoder: TTargaRLEDecoder;
{$ENDIF}
begin
// free previous image
if Assigned(FBitmap) then FBitmap.Free;
FBasePosition:=Stream.Position;
if ReadImageProperties(Stream,0) then
with FImageProperties do
begin
Stream.Position:=FBasePosition;
Stream.Read(Header,sizeof(Header));
FlipV:=(Header.ImageDescriptor and $20)<>0;
Header.ImageDescriptor:=Header.ImageDescriptor and $F;
// skip image ID
Stream.Seek(Header.IDLength,spCurrent);
ColorManager.SourceSamplesPerPixel:=SamplesPerPixel;
ColorManager.TargetSamplesPerPixel:=SamplesPerPixel;
ColorManager.SourceColorScheme:=ColorScheme;
ColorManager.SourceOptions:=[];
ColorManager.TargetColorScheme:=csBGR;
ColorManager.SourceBitsPerSample:=BitsPerSample;
ColorManager.TargetBitsPerSample:=BitsPerSample;
FBitmap:=NewBitmap(Header.Width,Header.Height);
FBitmap.PixelFormat:=ColorManager.TargetPixelFormat;
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 ColorManager.CreateGrayscalePalette(FBitmap,False) else
begin
LineSize:=(Header.ColorMapEntrySize div 8)*Header.ColorMapSize;
GetMem(LineBuffer,LineSize);
try
Stream.Read(LineBuffer^,LineSize);
case Header.ColorMapEntrySize of
32: ColorManager.CreateColorPalette(FBitmap,[LineBuffer],pfInterlaced8Quad,Header.ColorMapSize,False);
24: ColorManager.CreateColorPalette(FBitmap,[LineBuffer],pfInterlaced8Triple,Header.ColorMapSize,False);
else
begin
// 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 pred(Header.ColorMapSize) do
begin
Stream.Read(Color16,2);
FBitmap.DIBPalEntries[I]:=Windows.RGB((Color16 and $7C00) shr 7,(Color16 and $3E0) shr 2,(Color16 and $1F) shl 3);
end;
end;
end;
finally
if Assigned(LineBuffer) then FreeMem(LineBuffer);
end;
end;
end;
LineSize:=Width*(Header.PixelSize div 8);
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 pred(FBitmap.Height) do
begin
if FlipV then LineBuffer:=FBitmap.ScanLine[I] else LineBuffer:=FBitmap.ScanLine[Header.Height-(I+1)];
Stream.Read(LineBuffer^,LineSize);
end;
end;
TARGA_BW_RLE_IMAGE,
TARGA_INDEXED_RLE_IMAGE,
TARGA_TRUECOLOR_RLE_IMAGE:
begin
RLEBuffer:=nil;
{$IFDEF NOCLASSES}
new( Decoder, Create(Header.PixelSize) );
{$ELSE} Decoder:=TTargaRLEDecoder.Create(Header.PixelSize); {$ENDIF}
try
GetMem(RLEBuffer,2*LineSize);
for I:=0 to pred(FBitmap.Height) do
begin
if FlipV then LineBuffer:=FBitmap.ScanLine[I] else LineBuffer:=FBitmap.ScanLine[Header.Height-(I+1)];
ReadLength:=Stream.Read(RLEBuffer^,2*LineSize);
Run:=RLEBuffer;
Decoder.Decode(Pointer(Run),LineBuffer,2*LineSize,FBitmap.Width);
Stream.Position:=Stream.Position-DWORD(ReadLength)+DWORD(Run-RLEBuffer);
end;
finally
if Assigned(RLEBuffer) then FreeMem(RLEBuffer);
Decoder.Free;
end;
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TTGAGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
var Header: TTargaHeader;
begin
inherited ReadImageProperties(Stream,ImageIndex);
with FImageProperties do
begin
Stream.Read(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;
//----------------- TPCXGraphic ----------------------------------------------------------------------------------------
type
TPCXHeader = packed 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;
//----------------------------------------------------------------------------------------------------------------------
{$IFNDEF NOCLASSES} class {$ENDIF}
function TPCXGraphic.CanLoad(Stream: PStream): boolean;
var Header: TPCXHeader;
LastPosition: cardinal;
begin
LastPosition:=Stream.Position;
Result:=(Stream.Size-Stream.Position)>sizeof(Header);
if Result then
begin
Stream.Read(Header,sizeof(Header));
Result:=(Header.FileID in [$0A,$0C]) and (Header.Version in [0,2,3,5])
and (Header.Encoding in [0,1]);
end;
Stream.Position:=LastPosition;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TPCXGraphic.LoadFromStream(Stream: PStream);
var Header: TPCXHeader;
//--------------- local functions -------------------------------------------
procedure MakePalette;
const
Pal16: array[0..15] of TColor =
(clBlack,clMaroon,clGreen,clOlive,clNavy,clPurple,clTeal,clDkGray,
clLtGray,clRed,clLime,clYellow,clBlue,clFuchsia,clAqua,clWhite);
var PCXPalette: array[0..255] of TRGB;
I,OldPos: integer;
Marker: byte;
begin
if (Header.Version<>3) or (FBitmap.PixelFormat=pf1Bit) then
begin
case FBitmap.PixelFormat of
pf1Bit: ColorManager.CreateGrayScalePalette(FBitmap,False);
pf4Bit:
with Header do
begin
if PaletteType=2 then ColorManager.CreateGrayScalePalette(FBitmap,False) else ColorManager.CreateColorPalette(FBitmap,[@ColorMap],pfInterlaced8Triple,16,False);
end;
pf8Bit:
begin
OldPos:=Stream.Position;
// 256 colors with 3 components plus one marker byte
Stream.Position:=Stream.Size-769;
for I := 1 to 768 do // ���� ������� � ���� ����� ���, ������ �������
begin // ����� ��� ���� ������...
Stream.Position := Stream.Position - 2;
Stream.Read(Marker,1);
if Marker = $0C then break;
end;
if Marker <> $0C then
begin // ��� ��� ��������� ������� ������� �� ������� ����� - �������
// ����� �����������
Stream.Position := OldPos + Integer( FImageProperties.BytesPerLine ) * Height;
Stream.Read(Marker, 1);
end;
if Marker<>$0C then
begin
// palette ID is wrong, perhaps gray scale?
if Header.PaletteType=2 then ColorManager.CreateGrayScalePalette(FBitmap,False) else ; // ignore palette
end
else
begin
Stream.Read(PCXPalette[0],768);
ColorManager.CreateColorPalette(FBitmap,[@PCXPalette],pfInterlaced8Triple,256,True);
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...
for I:=0 to 15 do FBitmap.DIBPalEntries[I]:=Pal16[I];
end;
end;
//--------------- end local functions ---------------------------------------
var PCXSize,Size: cardinal;
RawBuffer,DecodeBuffer: pointer;
Run,Plane1,Plane2,Plane3,Plane4: PByte;
Value,Mask: byte;
I,J: integer;
Line: PByte;
Increment: cardinal;
NewPixelFormat: TPixelFormat;
{$IFDEF NOCLASSES} Decoder: PPcxRLEDecoder; {$ELSE} Decoder: TPCXRLEDecoder; {$ENDIF}
begin
// free previous image
if Assigned(FBitmap) then FBitmap.Free;
FBasePosition:=Stream.Position;
if ReadImageProperties(Stream,0) then
begin
Stream.Position:=FBasePosition;
Stream.Read(Header,sizeof(Header));
PCXSize:=Stream.Size-Stream.Position;
with Header,FImageProperties do
begin
if not (FileID in [$0A,$CD]) then GraphicExError(1{gesInvalidImage},['PCX or SCR']);
ColorManager.SourceColorScheme:=ColorScheme;
ColorManager.SourceBitsPerSample:=BitsPerSample;
ColorManager.SourceSamplesPerPixel:=SamplesPerPixel;
if ColorScheme=csIndexed then ColorManager.TargetColorScheme:=csIndexed else ColorManager.TargetColorScheme:=csBGR;
if BitsPerPixel=2 then ColorManager.TargetBitsPerSample:=4 else ColorManager.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
ColorManager.TargetSamplesPerPixel:=SamplesPerPixel;
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(2{gesInvalidColorFormat},['PCX']);
end;
FBitmap:=NewBitmap(Width,Height);
FBitmap.PixelFormat:=NewPixelFormat;
// 256 colors palette is appended to the actual PCX data
if FBitmap.PixelFormat=pf8Bit then Dec(PCXSize,769);
if FBitmap.PixelFormat<>pf24Bit then MakePalette;
// 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.Read(RawBuffer^,PCXSize);
{$IFDEF NOCLASSES} new( Decoder, Create );
{$ELSE} Decoder := TPCXRLEDecoder.Create; {$ENDIF}
with Decoder {$IFDEF NOCLASSES}^{$ENDIF} 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.Read(DecodeBuffer^,PCXSize);
end;
try
Run:=DecodeBuffer;
if (SamplesPerPixel=4) and (BitsPerPixel=4) then
begin
// 4 planes with one bit
for I:=0 to pred(Height) do
begin
Plane1:=Run;
PChar(Plane2):=PChar(Run)+Increment div 4;
PChar(Plane3):=PChar(Run)+2*(Increment div 4);
PChar(Plane4):=PChar(Run)+3*(Increment div 4);
Line:=FBitmap.ScanLine[I];
// number of bytes to write
Size:=(FBitmap.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);
end;
end
else
if FBitmap.PixelFormat=pf24Bit then
begin
// true color
for I:=0 to pred(FBitmap.Height) do
begin
Line:=FBitmap.ScanLine[I];
Plane1:=Run;
PChar(Plane2):=PChar(Run)+Increment div 3;
PChar(Plane3):=PChar(Run)+2*(Increment div 3);
ColorManager.ConvertRow([Plane1,Plane2,Plane3],Line,FBitmap.Width,$FF);
Inc(Run,Increment);
end
end
else
begin
// other indexed formats
for I:=0 to pred(FBitmap.Height) do
begin
Line:=FBitmap.ScanLine[I];
ColorManager.ConvertRow([Run],Line,FBitmap.Width,$FF);
Inc(Run,Increment);
end;
end;
finally
if Assigned(DecodeBuffer) then FreeMem(DecodeBuffer);
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TPCXGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
var Header: TPCXHeader;
begin
Result:=inherited ReadImageProperties(Stream,0);
Stream.Read(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;
BytesPerLine := Header.BytesPerLine;
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;
//----------------- TPCDGraphic ----------------------------------------------------------------------------------------
const
PCD_BEGIN_BASE16 = 8192;
PCD_BEGIN_BASE4 = 47104;
PCD_BEGIN_BASE = 196608;
PCD_BEGIN_ORIENTATION = 194635;
PCD_BEGIN = 2048;
PCD_MAGIC = 'PCD_IPI';
//----------------------------------------------------------------------------------------------------------------------
{$IFNDEF NOCLASSES} class {$ENDIF}
function TPCDGraphic.CanLoad(Stream: PStream): boolean;
var Header: array[0..$802] of byte;
LastPosition: cardinal;
begin
LastPosition:=Stream.Position;
Result:=(Stream.Size-Stream.Position)>3*$800;
if Result then
begin
Stream.Read(Header,Length(Header));
Result:=(StrLComp(@Header[0],'PCD_OPA',7)=0) or (StrLComp(@Header[$800],'PCD',3)=0);
end;
Stream.Position:=LastPosition;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TPCDGraphic.LoadFromStream(Stream: PStream);
var C1,C2,YY: PChar;
YCbCrData: array[0..2] of PChar;
SourceDummy,DestDummy: pointer;
Offset,I,X,IX,Y,IY,ImageIndex,Rows,Columns: cardinal;
ScanLines: array of pointer;
LineBuffer: pointer;
Line,Run: PBGR;
{$IFDEF NOCLASSES} Decoder: PPCDDecoder; {$ELSE} Decoder: TPCDDecoder; {$ENDIF}
begin
// free previous image
if Assigned(FBitmap) then FBitmap.Free;
FBasePosition:=Stream.Position;
ImageIndex:=2; // third image is Base resolution
if ReadImageProperties(Stream,ImageIndex) then
begin
with FImageProperties do
begin
ScanLines:=nil;
LineBuffer:=nil;
Stream.Position:=FBasePosition;
Columns:=192 shl KOL.Min(ImageIndex,2);
Rows:=128 shl KOL.Min(ImageIndex,2);
Width:=192 shl ImageIndex;
Height:=128 shl ImageIndex;
// 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
IX:=Height;
IY:=Width;
end
else
begin
IX:=Width;
IY:=Height;
end;
// since row and columns might be swapped because of rotated images
// we determine the final dimensions once more
FBitmap:=NewBitmap(IX,IY);
if Rotate in [1,3] then
begin
SetLength(ScanLines,IX);
for Y:=0 to pred(IY) do ScanLines[Y]:=FBitmap.ScanLine[Y];
GetMem(LineBuffer,3*IY);
end;
ZeroMemory(@YCbCrData,sizeof(YCbCrData));
try
GetMem(YCbCrData[0],FBitmap.Width*FBitmap.Height);
GetMem(YCbCrData[1],FBitmap.Width*FBitmap.Height);
GetMem(YCbCrData[2],FBitmap.Width*FBitmap.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;
Stream.Seek(Offset*$800,spCurrent);
// color conversion setup
ColorManager.SourceColorScheme:=csPhotoYCC;
ColorManager.SourceBitsPerSample:=8;
ColorManager.SourceSamplesPerPixel:=3;
ColorManager.TargetColorScheme:=csBGR;
ColorManager.TargetBitsPerSample:=8;
ColorManager.TargetSamplesPerPixel:=3;
FBitmap.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);
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;
while I<Rows do
begin
Stream.Read(YY^,Columns);
Inc(YY,Width);
Stream.Read(YY^,Columns);
Inc(YY,Width);
Stream.Read(C1^,Columns shr 1);
Inc(C1,Width);
Stream.Read(C2^,Columns shr 1);
Inc(C2,Width);
Inc(I,2);
end;
// Y stands here for maximum number of upsample calls
// Y:=5;
if ImageIndex>=3 then
begin
// Inc(Y,3*(ImageIndex-3));
{$IFDEF NOCLASSES} new( Decoder, Create( Stream ) );
{$ELSE} Decoder:=TPCDDecoder.Create(Stream); {$ENDIF}
SourceDummy:=@YCbCrData;
DestDummy:=nil;
try
// recover luminance deltas for 1536 x 1024 image
Upsample(768,512,Width,YCbCrData[0]);
Upsample(384,256,Width,YCbCrData[1]);
Upsample(384,256,Width,YCbCrData[2]);
Stream.Seek(4*$800,spCurrent);
Decoder.Decode(SourceDummy,DestDummy,Width,1024);
if ImageIndex>=4 then
begin
// recover luminance deltas for 3072 x 2048 image
Upsample(1536,1024,FBitmap.Width,YCbCrData[0]);
Upsample(768,512,FBitmap.Width,YCbCrData[1]);
Upsample(768,512,FBitmap.Width,YCbCrData[2]);
Offset:=(Stream.Position-FBasePosition) div $800+12;
Stream.Seek(FBasePosition+Offset*$800,spBegin);
Decoder.Decode(SourceDummy,DestDummy,Width,2048);
if ImageIndex=5 then
begin
// recover luminance deltas for 6144 x 4096 image (vaporware)
Upsample(3072,2048,FBitmap.Width,YCbCrData[1]);
Upsample(1536,1024,FBitmap.Width,YCbCrData[1]);
Upsample(1536,1024,FBitmap.Width,YCbCrData[2]);
end;
end;
finally
Decoder.Free;
end;
end;
Upsample(FBitmap.Width shr 1,FBitmap.Height shr 1,FBitmap.Width,YCbCrData[1]);
Upsample(FBitmap.Width shr 1,FBitmap.Height shr 1,FBitmap.Width,YCbCrData[2]);
// transfer luminance and chrominance channels
YY:=YCbCrData[0];
C1:=YCbCrData[1];
C2:=YCbCrData[2];
try
case Rotate of
1: // rotate -90�
begin
for Y:=0 to pred(FBitmap.Height) do
begin
ColorManager.ConvertRow([YY,C1,C2],LineBuffer,FBitmap.Width,$FF);
Inc(YY,FBitmap.Width);
Inc(C1,FBitmap.Width);
Inc(C2,FBitmap.Width);
Run:=LineBuffer;
for X:=0 to pred(FBitmap.Width) do
begin
PChar(Line):=PChar(ScanLines[FBitmap.Width-Integer(X)-1])+Y*3;
Line^:=Run^;
Inc(Run);
end;
end;
end;
3: // rotate 90�
begin
for Y:=0 to pred(Height) do
begin
ColorManager.ConvertRow([YY,C1,C2],LineBuffer,FBitmap.Width,$FF);
Inc(YY,FBitmap.Width);
Inc(C1,FBitmap.Width);
Inc(C2,FBitmap.Width);
Run:=LineBuffer;
for X:=0 to pred(FBitmap.Width) do
begin
PChar(Line):=PChar(ScanLines[X])+(FBitmap.Height-Integer(Y)-1)*3;
Line^:=Run^;
Inc(Run);
end;
end;
end;
else
for Y:=0 to pred(Height) do
begin
ColorManager.ConvertRow([YY,C1,C2],FBitmap.ScanLine[Y],FBitmap.Width,$FF);
Inc(YY,FBitmap.Width);
Inc(C1,FBitmap.Width);
Inc(C2,FBitmap.Width);
end;
end;
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: PStream; ImageIndex: cardinal): boolean;
var Header: array[0..$17FF] of byte;
Temp: cardinal;
begin
if ImageIndex>5 then ImageIndex:=5;
Result:=inherited ReadImageProperties(Stream,ImageIndex) and ((Stream.Size-FBasePosition)>3*$800);
with FImageProperties do
begin
Stream.Read(Header,Length(Header));
try
Overview:=StrLComp(@Header[0],'PCD_OPA',7)=0;
// determine if image is a PhotoCD image
if Overview or (StrLComp(@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
end;
end;
end;
//----------------- TPPMGraphic ----------------------------------------------------------------------------------------
{$IFNDEF NOCLASSES} class {$ENDIF}
function TPPMGraphic.CanLoad(Stream: PStream): boolean;
var Buffer: array[0..9] of Char;
LastPosition: cardinal;
begin
LastPosition:=Stream.Position;
Result:=(Stream.Size-Stream.Position)>10;
if Result then
begin
Stream.Read(Buffer,sizeof(Buffer));
Result:=(Buffer[0]='P') and (Buffer[1] in ['1'..'6']);
end;
Stream.Position:=LastPosition;
end;
//----------------------------------------------------------------------------------------------------------------------
function TPPMGraphic.CurrentChar: Char;
begin
if FIndex=sizeof(FBuffer) then Result:=#0 else Result:=FBuffer[FIndex];
end;
//----------------------------------------------------------------------------------------------------------------------
function TPPMGraphic.GetChar: Char;
// buffered I/O
begin
if FIndex=sizeof(FBuffer) then
begin
if FStream.Position=FStream.Size then GraphicExError(3{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: Char;
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: string;
// reads one text line from stream and skips comments
var Ch: Char;
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('#',Result);
if I>0 then Delete(Result,I,MaxInt);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TPPMGraphic.LoadFromStream(Stream: PStream);
var Buffer: string;
Line24: PBGR;
Line8: PByte;
X,Y,W,H: integer;
Pixel: byte;
begin
// free previous image
if Assigned(FBitmap) then FBitmap.Free;
FBasePosition:=Stream.Position;
// copy reference for buffered access
FStream:=Stream;
if ReadImageProperties(Stream,0) then
begin
with FImageProperties do
begin
Stream.Position:=FBasePosition;
// set index pointer to end of buffer to cause reload
FIndex:=sizeof(FBuffer);
Buffer:=ReadLine;
case Str2Int(Buffer[2]) of
1: // PBM ASCII format (black & white)
begin
W:=GetNumber;
H:=GetNumber;
FBitmap:=NewBitmap(W,H);
FBitmap.PixelFormat:=pf1Bit;
ColorManager.TargetSamplesPerPixel:=1;
ColorManager.TargetBitsPerSample:=1;
ColorManager.CreateGrayScalePalette(FBitmap,True);
// read image data
for Y:=0 to pred(FBitmap.Height) do
begin
Line8:=FBitmap.ScanLine[Y];
Pixel:=0;
for X:=1 to FBitmap.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 (FBitmap.Width mod 8)<>0 then Line8^:=Pixel shl (8-(FBitmap.Width mod 8));
end;
end;
2: // PGM ASCII form (gray scale)
begin
W:=GetNumber;
H:=GetNumber;
FBitmap:=NewBitmap(W,H);
FBitmap.PixelFormat:=pf8Bit;
// skip maximum color value
GetNumber;
ColorManager.TargetSamplesPerPixel:=1;
ColorManager.TargetBitsPerSample:=8;
ColorManager.CreateGrayScalePalette(FBitmap,False);
// read image data
for Y:=0 to pred(FBitmap.Height) do
begin
Line8:=FBitmap.ScanLine[Y];
for X:=0 to pred(FBitmap.Width) do
begin
Line8^:=GetNumber;
Inc(Line8);
end;
end;
end;
3: // PPM ASCII form (true color)
begin
W:=GetNumber;
H:=GetNumber;
FBitmap:=NewBitmap(W,H);
FBitmap.PixelFormat:=pf24Bit;
// skip maximum color value
GetNumber;
for Y:=0 to pred(FBitmap.Height) do
begin
Line24:=FBitmap.ScanLine[Y];
for X:=0 to pred(FBitmap.Width) do
begin
Line24.R:=GetNumber;
Line24.G:=GetNumber;
Line24.B:=GetNumber;
Inc(Line24);
end;
end;
end;
4: // PBM binary format (black & white)
begin
W:=GetNumber;
H:=GetNumber;
FBitmap:=NewBitmap(W,H);
FBitmap.PixelFormat:=pf1Bit;
ColorManager.TargetSamplesPerPixel:=1;
ColorManager.TargetBitsPerSample:=1;
ColorManager.CreateGrayScalePalette(FBitmap,True);
// read image data
for Y:=0 to pred(FBitmap.Height) do
begin
Line8:=FBitmap.ScanLine[Y];
for X:=0 to pred(FBitmap.Width div 8) do
begin
Line8^:=Byte(GetChar);
Inc(Line8);
end;
if (FBitmap.Width mod 8)<>0 then Line8^:=Byte(GetChar);
end;
end;
5: // PGM binary form (gray scale)
begin
W:=GetNumber;
H:=GetNumber;
FBitmap:=NewBitmap(W,H);
FBitmap.PixelFormat:=pf8Bit;
// skip maximum color value
GetNumber;
ColorManager.TargetSamplesPerPixel:=1;
ColorManager.TargetBitsPerSample:=8;
ColorManager.CreateGrayScalePalette(FBitmap,False);
// read image data
for Y:=0 to pred(FBitmap.Height) do
begin
Line8:=FBitmap.ScanLine[Y];
for X:=0 to pred(FBitmap.Width) do
begin
Line8^:=Byte(GetChar);
Inc(Line8);
end;
end;
end;
6: // PPM binary form (true color)
begin
W:=GetNumber;
H:=GetNumber;
FBitmap:=NewBitmap(W,H);
FBitmap.PixelFormat:=pf24Bit;
// 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 pred(FBitmap.Height) do
begin
Line24:=FBitmap.ScanLine[Y];
for X:=0 to pred(FBitmap.Width) do
begin
Line24.R:=Byte(GetChar);
Line24.G:=Byte(GetChar);
Line24.B:=Byte(GetChar);
Inc(Line24);
end;
end;
end;
end;
end;
end
else GraphicExError(1{gesInvalidImage},['PBM, PGM or PPM']);
end;
//----------------------------------------------------------------------------------------------------------------------
function TPPMGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
var Buffer: string;
begin
FStream := Stream;
Result:=inherited ReadImageProperties(Stream,ImageIndex);
with 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 Str2Int(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;
//----------------- TCUTGraphic ----------------------------------------------------------------------------------------
{$IFNDEF NOCLASSES} class {$ENDIF}
function TCUTGraphic.CanLoad(Stream: PStream): 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.LoadFromStream(Stream: PStream);
var Buffer: PByte;
Run,Line: pointer;
{$IFDEF NOCLASSES} Decoder: PCUTRLEDECODER; {$ELSE} Decoder: TCUTRLEDecoder; {$ENDIF}
CUTSize: cardinal;
Y: integer;
begin
// free previous image
if Assigned(FBitmap) then FBitmap.Free;
FBasePosition:=Stream.Position;
if ReadImageProperties(Stream,0) then
begin
with FImageProperties do
begin
Stream.Position:=FBasePosition+6;
FBitmap:=NewBitmap(Width,Height);
FBitmap.PixelFormat:=pf8Bit;
LoadPalette;
CutSize:=Stream.Size-Stream.Position;
{$IFDEF NOCLASSES} new( Decoder, Create );
{$ELSE} Decoder:=TCUTRLEDecoder.Create; {$ENDIF}
Buffer:=nil;
try
GetMem(Buffer,CutSize);
Stream.Read(Buffer^,CUTSize);
Run:=Buffer;
for Y:=0 to pred(FBitmap.Height) do
begin
Line:=FBitmap.ScanLine[Y];
Decoder.Decode(Run,Line,0,FBitmap.Width);
end;
finally
Decoder.Free;
if Assigned(Buffer) then FreeMem(Buffer);
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TCUTGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
var Dummy: word;
begin
inherited ReadImageProperties(Stream,ImageIndex);
with FImageProperties do
begin
Stream.Read(Dummy,sizeof(Dummy));
Width:=Dummy;
Stream.Read(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 Char; // 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 Char; // 'Dr. Halo'
Filler: array[0..11] of byte;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TCUTGraphic.LoadPalette;
var Header: PHaloPaletteHeader;
I: integer;
Buffer: array[0..511] of byte;
Run: PWord;
R,G,B: byte;
Temp: PStream;
begin
if FileExists(FPaletteFile) then
begin
Temp:=NewReadFileStream(FPaletteFile);
try
// quite strange file organization here, we need always to load 512 bytes blocks
// and skip occasionally some bytes
Temp.Read(Buffer,sizeof(Buffer));
Header:=@Buffer;
Run:=@Buffer;
Inc(PByte(Run),sizeof(Header^));
for I:=0 to Header.MaxIndex do
begin
// load next 512 bytes buffer if necessary
if (Integer(Run)-Integer(@Buffer))>506 then
begin
Temp.Read(Buffer,sizeof(Buffer));
Run:=@Buffer;
end;
B:=Run^;
Inc(Run);
G:=Run^;
Inc(Run);
R:=Run^;
Inc(Run);
FBitmap.DIBPalEntries[I]:=Windows.RGB(R,G,B);
end;
finally
Temp.Free;
end;
end
else
begin
// no external palette so use gray scale
for I:=0 to 255 do FBitmap.DIBPalEntries[I]:=Windows.RGB(I,I,I);
end;
end;
//----------------- TGIFGraphic ----------------------------------------------------------------------------------------
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 Char; // magic ID 'GIF'
Version: array[0..2] of Char; // '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;
//----------------------------------------------------------------------------------------------------------------------
{$IFNDEF NOCLASSES} class {$ENDIF}
function TGIFGraphic.CanLoad(Stream: PStream): boolean;
var Header: TGIFHeader;
LastPosition: cardinal;
begin
LastPosition:=Stream.Position;
Result:=(Stream.Size-Stream.Position)>(sizeof(TGIFHeader)+sizeof(TLogicalScreenDescriptor)+sizeof(TImageDescriptor));
if Result then
begin
Stream.Read(Header,sizeof(Header));
Result:=UpperCase(Header.Signature)='GIF';
end;
Stream.Position:=LastPosition;
end;
function TGIFGraphic.Transparent: boolean;
begin
Result := ImageProperties.HasAlpha;
end;
function TGIFGraphic.Count: Integer;
begin
Result := ImageProperties.ImageCount;
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
// iterate through the blocks until first image is found
repeat
FStream.Read(Result,1);
if Result=GIF_EXTENSIONINTRODUCER then
begin
// skip any extension
FStream.Read(Result,1);
case Result of
GIF_PLAINTEXT:
begin
// block size of text grid data
FStream.Read(Increment,1);
FStream.Seek(Increment,spCurrent);
// skip variable lengthed text block
repeat
// block size
FStream.Read(Increment,1);
if Increment=0 then Break;
FStream.Seek(Increment,spCurrent);
until False;
end;
GIF_GRAPHICCONTROLEXTENSION:
begin
// block size
FStream.Read(Increment,1);
// skip block and its terminator
FStream.Seek(Increment+1,spCurrent);
end;
GIF_COMMENTEXTENSION:
repeat
// block size
FStream.Read(Increment,1);
if Increment=0 then Break;
FStream.Seek(Increment,spCurrent);
until False;
GIF_APPLICATIONEXTENSION:
begin
// application id and authentication code plus potential application data
repeat
FStream.Read(Increment,1);
if Increment=0 then Break;
FStream.Seek(Increment,spCurrent);
until False;
end;
end;
end;
until (Result=GIF_IMAGEDESCRIPTOR) or (Result=GIF_TRAILER);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TGIFGraphic.LoadFromStream(Stream: PStream);
var Header: TGIFHeader;
ScreenDescriptor: TLogicalScreenDescriptor;
ImageDescriptor: TImageDescriptor;
I,NE: cardinal;
R,G,B,BlockID,InitCodeSize: byte;
RawData,Run: PByte;
TargetBuffer,TargetRun,Line: pointer;
Pass,Increment,Marker: integer;
{$IFDEF NOCLASSES} Decoder: PGIFLZWDecoder; {$ELSE} Decoder: TGIFLZWDecoder; {$ENDIF}
begin
// free previous image
if Assigned(FBitmap) then FBitmap.Free;
FBasePosition:=Stream.Position;
FStream:=Stream;
if ReadImageProperties(Stream,0) then
begin
with FImageProperties do
begin
Stream.Position:=FBasePosition;
Stream.Read(Header,sizeof(Header));
FBitmap:=NewBitmap(Width,Height);
FBitmap.PixelFormat:=pf8Bit;
// general information
Stream.Read(ScreenDescriptor,sizeof(ScreenDescriptor));
// read global color table if given
if (ScreenDescriptor.PackedFields and GIF_GLOBALCOLORTABLE)<>0 then
begin
// the global color table immediately follows the screen descriptor
NE:=2 shl (ScreenDescriptor.PackedFields and GIF_COLORTABLESIZE);
for I:=0 to pred(NE) do
begin
Stream.Read(B,1);
Stream.Read(G,1);
Stream.Read(R,1);
FBitmap.DIBPalEntries[I]:=Windows.RGB(R,G,B);
end;
end;
BlockID:=SkipExtensions;
// image found?
if BlockID=GIF_IMAGEDESCRIPTOR then
begin
Stream.Read(ImageDescriptor,sizeof(TImageDescriptor));
// 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
NE:=2 shl (ImageDescriptor.PackedFields and GIF_COLORTABLESIZE);
for I:=0 to pred(NE) do
begin
Stream.Read(B,1);
Stream.Read(G,1);
Stream.Read(R,1);
FBitmap.DIBPalEntries[I]:=Windows.RGB(R,G,B);
end;
end;
Stream.Read(InitCodeSize,1);
// decompress data in one step
// 1) count data
Marker:=Stream.Position;
Pass:=0;
Increment:=0;
repeat
if Stream.Read(Increment,1)=0 then Break;
Inc(Pass,Increment);
Stream.Seek(Increment,spCurrent);
until Increment=0;
// 2) allocate enough memory
GetMem(RawData,Pass);
// add one extra line of extra memory for badly coded images
GetMem(TargetBuffer,FBitmap.Width*(FBitmap.Height+1));
try
// 3) read and decode data
Stream.Position:=Marker;
Increment:=0;
Run:=RawData;
repeat
if Stream.Read(Increment,1)=0 then Break;
Stream.Read(Run^,Increment);
Inc(Run,Increment);
until Increment=0;
{$IFDEF NOCLASSES} new( Decoder, Create( InitCodeSize, FBitmap.Width ) );
{$ELSE} Decoder:=TGIFLZWDecoder.Create(InitCodeSize, FBitmap.Width); {$ENDIF}
try
Run:=RawData;
Decoder.Decode(Pointer(Run),TargetBuffer,Pass,FBitmap.Width*FBitmap.Height);
finally
Decoder.Free;
if Decoder.GIFCorrupted then
FCorrupted := TRUE;
end;
// finally transfer image data
if (ImageDescriptor.PackedFields and GIF_INTERLACED)=0 then
begin
TargetRun:=TargetBuffer;
for I:=0 to pred(FBitmap.Height) do
begin
Line:=FBitmap.ScanLine[I];
Move(TargetRun^,Line^,FBitmap.Width);
Inc(PByte(TargetRun),FBitmap.Width);
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<DWORD(FBitmap.Height) do
begin
Line:=FBitmap.ScanLine[I];
Move(TargetRun^,Line^,FBitmap.Width);
Inc(PByte(TargetRun),FBitmap.Width);
Inc(I,Increment);
end;
end;
end;
finally
if Assigned(TargetBuffer) then FreeMem(TargetBuffer);
if Assigned(RawData) then FreeMem(RawData);
end;
end;
end;
end
else GraphicExError(1{gesInvalidImage},['GIF']);
end;
//----------------------------------------------------------------------------------------------------------------------
function TGIFGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): Boolean;
var Header: TGIFHeader;
ScreenDescriptor: TLogicalScreenDescriptor;
ImageDescriptor: TImageDescriptor;
BlockID: integer;
begin
Result:=inherited ReadImageProperties(Stream,ImageIndex);
with FImageProperties do
begin
Stream.Read(Header,sizeof(Header));
if UpperCase(Header.Signature)='GIF' then
begin
Version:=Str2Int(Copy(Header.Version,1,2));
ColorScheme:=csIndexed;
SamplesPerPixel:=1;
// might be overwritten
BitsPerSample:=8;
Compression:=ctLZW;
// general information
Stream.Read(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
Stream.Seek(3*(1 shl BitsPerSample),spCurrent);
end;
BlockID:=SkipExtensions;
// image found?
if BlockID=GIF_IMAGEDESCRIPTOR then
begin
Stream.Read(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;
//----------------- TRLAGraphic ----------------------------------------------------------------------------------------
// 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 Char; // gamma single value used when writing the image
Red_pri: array[0..23] of Char; // used chromaticity for red channel (typical format: "%7.4f %7.4f")
Green_pri: array[0..23] of Char; // used chromaticity for green channel
Blue_pri: array[0..23] of Char; // used chromaticity for blue channel
White_pt: array[0..23] of Char; // used chromaticity for white point
Job_num: integer; // rendering speciifc
Name: array[0..127] of Char; // original file name
Desc: array[0..127] of Char; // a file description
ProgramName: array[0..63] of Char; // name of program which created the image
Machine: array[0..31] of Char; // name of computer on which the image was rendered
User: array[0..31] of Char; // user who ran the creation program of the image
Date: array[0..19] of Char; // creation data of image (ex: Sep 30 12:29 1993)
Aspect: array[0..23] of Char; // aspect format of the file (external resource)
Aspect_ratio: array[0..7] of Char; // float number Width /Height
Chan: array[0..31] of Char; // 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 Char; // time needed to create the image (used when rendering)
Filter: array[0..31] of Char; // 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 Char; // auxiliary channel as either range or depth
Space: array[0..35] of Char; // unused
Next: integer; // offset for next header if multi-frame image
end;
//----------------------------------------------------------------------------------------------------------------------
{$IFNDEF NOCLASSES} class {$ENDIF}
function TRLAGraphic.CanLoad(Stream: PStream): boolean;
var Header: TRLAHeader;
LastPosition: cardinal;
begin
LastPosition:=Stream.Position;
Result:=(Stream.Size-Stream.Position)>sizeof(Header);
if Result then
begin
Stream.Read(Header,sizeof(Header));
Result:=(System.Swap(Word(Header.Revision))=$FFFE) and ((LowerCase(Header.Chan)='rgb') or (LowerCase(Header.Chan)='xyz'));
end;
Stream.Position:=LastPosition;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TRLAGraphic.LoadFromStream(Stream: PStream);
var Offsets: TCardinalArray;
RLELength: word;
Line: pointer;
Y,I: Integer;
// RLE buffers
RawBuffer,RedBuffer,GreenBuffer,BlueBuffer,AlphaBuffer: pointer;
{$IFDEF NOCLASSES} Decoder: PRLADecoder; {$ELSE} Decoder: TRLADecoder; {$ENDIF}
begin
// free previous image
if Assigned(FBitmap) then FBitmap.Free;
FBasePosition:=Stream.Position;
if ReadImageProperties(Stream,0) then
begin
with FImageProperties do
begin
// dimension of image, top might be larger than bottom denoting a bottom up image
FBitmap:=NewBitmap(Width,Height);
ColorManager.SourceSamplesPerPixel:=SamplesPerPixel;
ColorManager.TargetSamplesPerPixel:=SamplesPerPixel;
ColorManager.SourceBitsPerSample:=BitsPerSample;
if BitsPerSample>8 then ColorManager.TargetBitsPerSample:=8 else ColorManager.TargetBitsPerSample:=BitsPerSample;
ColorManager.SourceColorScheme:=ColorScheme;
if ColorScheme=csRGBA then ColorManager.TargetColorScheme:=csBGRA else ColorManager.TargetColorScheme:=csBGR;
FBitmap.PixelFormat:=ColorManager.TargetPixelFormat;
if FileGamma<>1 then
begin
ColorManager.SetGamma(FileGamma);
ColorManager.TargetOptions:=ColorManager.TargetOptions+[coApplyGamma];
Include(Options,ioUseGamma);
end;
// each scanline is organized in RLE compressed strips whose location in the stream
// is determined by the offsets table
SetLength(Offsets,Height);
Stream.Read(Offsets[0],Height*sizeof(Cardinal));
for I:=0 to pred(Height) do SwapLong(@Offsets[I],1);
// setup intermediate storage
{$IFDEF NOCLASSES} new( Decoder, Create );
{$ELSE} Decoder:=TRLADecoder.Create; {$ENDIF}
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 pred(Height) do
begin
Stream.Position:=FBasePosition+Offsets[Y];
if BottomUp then Line:=FBitmap.ScanLine[Integer(Height)-Y-1] else Line:=FBitmap.ScanLine[Y];
// read channel data to decode
// red
Stream.Read(RLELength,sizeof(RLELength));
RLELength:=System.Swap(RLELength);
ReallocMem(RawBuffer,RLELength);
Stream.Read(RawBuffer^,RLELength);
Decoder.Decode(RawBuffer,RedBuffer,RLELength,Width);
// green
Stream.Read(RLELength,sizeof(RLELength));
RLELength:=System.Swap(RLELength);
ReallocMem(RawBuffer,RLELength);
Stream.Read(RawBuffer^,RLELength);
Decoder.Decode(RawBuffer,GreenBuffer,RLELength,Width);
// blue
Stream.Read(RLELength,sizeof(RLELength));
RLELength:=System.Swap(RLELength);
ReallocMem(RawBuffer,RLELength);
Stream.Read(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
Stream.Read(RLELength,sizeof(RLELength));
RLELength:=System.Swap(RLELength);
ReallocMem(RawBuffer,RLELength);
Stream.Read(RawBuffer^,RLELength);
Decoder.Decode(RawBuffer,AlphaBuffer,RLELength,Width);
ColorManager.ConvertRow([RedBuffer,GreenBuffer,BlueBuffer,AlphaBuffer],Line,Width,$FF);
end;
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;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TRLAGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
var Header: TRLAHeader;
begin
Result:=inherited ReadImageProperties(Stream,ImageIndex);
with FImageProperties do
begin
Stream.Read(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(Header.Chan)='rgb' then
begin
if Header.num_matte>0 then ColorScheme:=csRGBA else ColorScheme:=csRGB;
end
else
if LowerCase(Header.Chan)='xyz' then Exit;
try
FileGamma:=Str2Double(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:=System.Swap(Frame);
Storage_type:=System.Swap(Storage_type);
Num_chan:=System.Swap(Num_chan);
Num_matte:=System.Swap(Num_matte);
Num_aux:=System.Swap(Num_aux);
Revision:=System.Swap(Revision);
Job_num:=SwapLong(Job_num);
Field:=System.Swap(Field);
Chan_bits:=System.Swap(Chan_bits);
Matte_type:=System.Swap(Matte_type);
Matte_bits:=System.Swap(Matte_bits);
Aux_type:=System.Swap(Aux_type);
Aux_bits:=System.Swap(Aux_bits);
Next:=SwapLong(Next);
end;
end;
//----------------- TPSDGraphic ----------------------------------------------------------------------------------------
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 Char; // 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;
//----------------------------------------------------------------------------------------------------------------------
{$IFNDEF NOCLASSES} class {$ENDIF}
function TPSDGraphic.CanLoad(Stream: PStream): boolean;
var Header: TPSDHeader;
LastPosition: cardinal;
begin
LastPosition:=Stream.Position;
Result:=(Stream.Size-Stream.Position)>sizeof(Header);
if Result then
begin
Stream.Read(Header,sizeof(Header));
Result:=(UpperCase(Header.Signature)='8BPS') and (System.Swap(Header.Version)=1);
end;
Stream.Position:=LastPosition;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TPSDGraphic.LoadFromStream(Stream: PStream);
var Header: TPSDHeader;
Count: cardinal;
{$IFDEF NOCLASSES} Decoder: PPackbitsRLEDecoder; {$ELSE} Decoder: TDecoder; {$ENDIF}
RLELength: array[0..65535] 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)
NChannels: Integer;
Increment: integer; // pointer increment from one line to next
// RLE buffers
Line,RawBuffer, // all image data compressed
Buffer: pointer; // all image data uncompressed
Run1, // running pointer in Buffer 1
Run2, // etc.
Run3,
Run4: PByte;
RawPalette: array[0..767] of byte;
begin
// free previous image
if Assigned(FBitmap) then FBitmap.Free;
FBasePosition:=Stream.Position;
if ReadImageProperties(Stream,0) then
begin
with FImageProperties do
begin
Stream.Position:=FBasePosition;
Stream.Read(Header,sizeof(Header));
// initialize color manager
ColorManager.SourceOptions:=[coNeedByteSwap];
ColorManager.SourceBitsPerSample:=BitsPerSample;
if BitsPerSample=16 then ColorManager.TargetBitsPerSample:=8 else ColorManager.TargetBitsPerSample:=BitsPerSample;
ColorManager.SourceSamplesPerPixel:=SamplesPerPixel;
ColorManager.TargetSamplesPerPixel:=SamplesPerPixel;
if (SamplesPerPixel = 2) and (ColorScheme = csG) then
ColorManager.TargetSamplesPerPixel := 1;
// color space
ColorManager.SourceColorScheme:=ColorScheme;
case ColorScheme of
csG,csIndexed: ColorManager.TargetColorScheme:=ColorScheme;
csRGB: ColorManager.TargetColorScheme:=csBGR;
csRGBA: ColorManager.TargetColorScheme:=csBGRA;
csCMYK: begin
ColorManager.TargetColorScheme:=csBGR;
ColorManager.TargetSamplesPerPixel:=3;
end;
csCIELab: begin
// PSD uses 0..255 for a and b so we need to convert them to -128..127
ColorManager.SourceOptions:=ColorManager.SourceOptions+[coLabByteRange,coLabChromaOffset];
ColorManager.TargetColorScheme:=csBGR;
end;
end;
FBitmap:=NewDibBitmap(Width,Height, {);
FBitmap.PixelFormat:=}ColorManager.TargetPixelFormat);
// size of palette
Stream.Read(Count,sizeof(Count));
Count:=SwapLong(Count);
// setup the palette if necessary, color data immediately follows header
case ColorScheme of
csG: ColorManager.CreateGrayscalePalette(FBitmap,ioMinIsWhite in Options);
csIndexed:
begin
Stream.Read(RawPalette,Count);
Count:=Count div 3;
ColorManager.CreateColorPalette(FBitmap,[@RawPalette,@RawPalette[Count],@RawPalette[2*Count]],pfPlane8Triple,Count,False);
end;
end;
// skip resource and layers section
Stream.Read(Count,sizeof(Count));
Count:=SwapLong(Count);
Stream.Seek(Count,spCurrent);
Stream.Read(Count,sizeof(Count));
Count:=SwapLong(Count);
// +2 in order to skip the following compression value
Stream.Seek(Count+2,spCurrent);
// now read out image data
RawBuffer:=nil;
if Compression=ctPackedBits then
begin
{$IFDEF NOCLASSES} new( Decoder, Create );
{$ELSE} Decoder:=TPackbitsRLEDecoder.Create; {$ENDIF}
//+++++++++++++++ by VK: if not clear buffer before using it,
//process can be VERY slow even for small images!
FillChar( RLELength, SizeOf( RLELength ), 0 );
Stream.Read(RLELength,2*Height*Channels);
SwapShort(@RLELength[0],Height*Channels);
end
else Decoder:=nil;
try
case ColorScheme of
csG,csIndexed:
begin
// 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 pred(Height) do Inc(Count,RLELength[Y]);
GetMem(RawBuffer,Count);
try
Stream.Read(RawBuffer^,Count);
Run1:=RawBuffer;
for Y:=0 to pred(Height) do
begin
Count:=RLELength[Y];
Line:=FBitmap.ScanLine[Y];
Decoder.Decode(Pointer(Run1),Line,Count,Width);
Inc(Run1,Count);
end;
finally
if Assigned(RawBuffer) then FreeMem(RawBuffer);
end;
end
else // uncompressed data
for Y:=0 to pred(Height) do
begin
Stream.Read(FBitmap.ScanLine[Y]^,Width);
end;
end;
csRGB,csRGBA,csCMYK,csCIELab:
begin
// 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
Stream.Read(RawBuffer^,Count);
NChannels := Channels;
Decoder.Decode(RawBuffer,Buffer,Count,NChannels*ChannelSize);
finally
if Assigned(RawBuffer) then FreeMem(RawBuffer);
end;
end
else Stream.Read(Buffer^,Channels*ChannelSize);
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 pred(Height) do
begin
ColorManager.ConvertRow([Run1,Run2,Run3],FBitmap.ScanLine[Y],Width,$FF);
Inc(Run1,Increment);
Inc(Run2,Increment);
Inc(Run3,Increment);
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 pred(Height) do
begin
ColorManager.ConvertRow([Run1,Run2,Run3,Run4],FBitmap.ScanLine[Y],Width,$FF);
Inc(Run1,Increment);
Inc(Run2,Increment);
Inc(Run3,Increment);
Inc(Run4,Increment);
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 pred(Height) do
begin
ColorManager.ConvertRow([Run1,Run2,Run3,Run4],FBitmap.ScanLine[Y],Width,$FF);
Inc(Run1,Increment);
Inc(Run2,Increment);
Inc(Run3,Increment);
Inc(Run4,Increment);
end;
end;
csCIELab:
begin
Run1:=Buffer;
Run2:=Run1;
Inc(Run2,ChannelSize);
Run3:=Run2;
Inc(Run3,ChannelSize);
for Y:=0 to pred(Height) do
begin
ColorManager.ConvertRow([Run1,Run2,Run3],FBitmap.ScanLine[Y],Width,$FF);
Inc(Run1,Increment);
Inc(Run2,Increment);
Inc(Run3,Increment);
end;
end;
end;
finally
if Assigned(Buffer) then FreeMem(Buffer);
end;
end;
end;
finally
Decoder.Free;
end;
end;
end
else GraphicExError(1{gesInvalidImage},['PSD or PDD']);
end;
//----------------------------------------------------------------------------------------------------------------------
function TPSDGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
var Header: TPSDHeader;
Dummy: word;
Count: cardinal;
begin
Result:=inherited ReadImageProperties(Stream,ImageIndex);
with FImageProperties do
begin
Stream.Read(Header,sizeof(Header));
if Header.Signature='8BPS' then
begin
with Header do
begin
// PSD files are big endian only
Channels:=System.Swap(Channels);
Rows:=SwapLong(Rows);
Columns:=SwapLong(Columns);
Depth:=System.Swap(Depth);
Mode:=System.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
Stream.Read(Count,sizeof(Count));
Count:=SwapLong(Count);
// skip palette (count is always given, might be 0 however, e.g. for RGB)
Stream.Seek(Count,spCurrent);
// skip resource and layers section
Stream.Read(Count,sizeof(Count));
Count:=SwapLong(Count);
Stream.Seek(Count,spCurrent);
Stream.Read(Count,sizeof(Count));
Count:=SwapLong(Count);
Stream.Seek(Count,spCurrent);
Stream.Read(Dummy,sizeof(Dummy));
if System.Swap(Dummy)=1 then Compression:=ctPackedBits else Compression:=ctNone;
Result:=True;
end;
end;
end;
//----------------- TPSPGraphic ----------------------------------------------------------------------------------------
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 Char; // 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 Char; // 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 Char; // 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 Char; // 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;
//----------------------------------------------------------------------------------------------------------------------
{$IFNDEF NOCLASSES} class {$ENDIF}
function TPSPGraphic.CanLoad(Stream: PStream): boolean;
var Header: TPSPFileHeader;
LastPosition: cardinal;
begin
LastPosition:=Stream.Position;
Result:=(Stream.Size-Stream.Position)>sizeof(Header);
if Result then
begin
Stream.Read(Header,sizeof(Header));
Result:=(StrLIComp(Header.Signature,MagicID,Length(MagicID))=0) and (Header.MajorVersion>=3);
end;
Stream.Position:=LastPosition;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TPSPGraphic.LoadFromStream(Stream: PStream);
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 Char; // 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: string;
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..1023] 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
Result:=Stream.Position<Stream.Size;
//TotalBlockLength := 0;
if Result then
begin
Stream.Read(HeaderIdentifier,sizeof(HeaderIdentifier));
Stream.Read(BlockIdentifier,sizeof(BlockIdentifier));
if Header.MajorVersion=3 then Stream.Read(InitialChunkLength,sizeof(InitialChunkLength));
Stream.Read(TotalBlockLength,sizeof(TotalBlockLength));
end;
end;
//---------------------------------------------------------------------------
procedure ReadAndDecompress(Target: pointer);
// reads a stream of data from file stream and decompresses it into Target
var RawBuffer,Source: pointer;
{$IFDEF NOCLASSES} Decoder: PDecoder;
PSPRLEDecoder: PPSPRLEDecoder;
LZ77Decoder: PLZ77Decoder;
{$ELSE} Decoder: TDecoder; {$ENDIF}
begin
Decoder:=nil;
GetMem(RawBuffer,ChannelInfo.CompressedSize);
try
Stream.Read(RawBuffer^,ChannelInfo.CompressedSize);
// pointer might be advanced while decoding, so use a copy
Source:=RawBuffer;
case Image.Compression of
PSP_COMP_RLE:
begin
{$IFDEF NOCLASSES}
new( PSPRLEDecoder, Create ); Decoder := PSPRLEDecoder;
{$ELSE} Decoder:=TPSPRLEDecoder.Create; {$ENDIF}
Decoder.Decode(Source,Target,ChannelInfo.CompressedSize,ChannelInfo.UncompressedSize);
end;
PSP_COMP_LZ77:
begin
{$IFDEF NOCLASSES}
new( LZ77Decoder, Create(Z_FINISH,False) ); Decoder := LZ77Decoder;
{$ELSE} Decoder:=TLZ77Decoder.Create(Z_FINISH,False); {$ENDIF}
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
ReadBlockHeader;
if Header.MajorVersion>3 then Stream.Read(ChunkSize,sizeof(ChunkSize));
Stream.Read(ChannelInfo,sizeof(ChannelInfo));
case ChannelInfo.ChannelType of
PSP_CHANNEL_COMPOSITE: // single channel bitmap (indexed or transparency mask)
begin
if Assigned(CompBuffer) then FreeMem(CompBuffer);
GetMem(CompBuffer,ChannelInfo.UncompressedSize);
if Image.Compression<>PSP_COMP_NONE then
ReadAndDecompress(CompBuffer)
else
Stream.Read(CompBuffer^,ChannelInfo.CompressedSize);
end;
PSP_CHANNEL_RED: // red channel of 24 bit bitmap
begin
if Assigned(RedBuffer) then FreeMem(RedBuffer);
GetMem(RedBuffer,ChannelInfo.UncompressedSize);
if Image.Compression<>PSP_COMP_NONE then
ReadAndDecompress(RedBuffer)
else
Stream.Read(RedBuffer^,ChannelInfo.CompressedSize);
end;
PSP_CHANNEL_GREEN:
begin
if Assigned(GreenBuffer) then FreeMem(GreenBuffer);
GetMem(GreenBuffer,ChannelInfo.UncompressedSize);
if Image.Compression<>PSP_COMP_NONE then
ReadAndDecompress(GreenBuffer)
else
Stream.Read(GreenBuffer^,ChannelInfo.CompressedSize);
end;
PSP_CHANNEL_BLUE:
begin
if Assigned(BlueBuffer) then FreeMem(BlueBuffer);
GetMem(BlueBuffer,ChannelInfo.UncompressedSize);
if Image.Compression<>PSP_COMP_NONE then
ReadAndDecompress(BlueBuffer)
else
Stream.Read(BlueBuffer^,ChannelInfo.CompressedSize);
end;
end;
end;
//--------------- end local functions ---------------------------------------
var RowIncrement: Integer;
RowWidth: Integer;
RowOffset: Integer;
begin
// free previous image
if Assigned(FBitmap) then FBitmap.Free;
FBasePosition:=Stream.Position;
if ReadImageProperties(Stream,0) then
begin
Stream.Position:=FBasePosition;
RedBuffer:=nil;
GreenBuffer:=nil;
BlueBuffer:=nil;
CompBuffer := nil; //*//
with FImageProperties do
try
// 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).
Stream.Read(Header,sizeof(Header));
// read general image attribute block
ReadBlockHeader;
LastPosition:=Stream.Position;
if Version>3 then Stream.Read(ChunkSize,sizeof(ChunkSize));
Stream.Read(Image,sizeof(Image));
Stream.Position:=LastPosition+TotalBlockLength;
//FBitmap:=NewBitmap(Width,Height);
with Image do
begin
ColorManager.SourceOptions:=[];
ColorManager.SourceBitsPerSample:=BitsPerSample;
ColorManager.TargetBitsPerSample:=BitsPerSample;
ColorManager.SourceSamplesPerPixel:=SamplesPerPixel;
ColorManager.TargetSamplesPerPixel:=SamplesPerPixel;
ColorManager.SourceColorScheme:=ColorScheme;
if ColorScheme=csRGB then ColorManager.TargetColorScheme:=csBGR
else ColorManager.TargetColorScheme:=ColorScheme;
//FBitmap.PixelFormat:=ColorManager.TargetPixelFormat;
FBitmap:=NewDibBitmap(Width,Height,ColorManager.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(2{gesInvalidColorFormat},['PSP']);
end;
// go through main blocks and read what is needed
repeat
if not ReadBlockHeader then Break;
NextMainBlock:=Stream.Position+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;
// calculate start of next (layer) block in case we need to skip this one
NextLayerPosition:=Stream.Position+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:=Stream.Position;
Stream.Read(ChunkSize,sizeof(ChunkSize));
Stream.Read(NameLength,sizeof(NameLength));
SetLength(LayerName,NameLength);
if NameLength>0 then Stream.Read(LayerName[1],NameLength);
Stream.Read(LayerInfo,sizeof(LayerInfo));
Stream.Position:=LastPosition+ChunkSize;
// continue only with undefined or raster chunks
if not (LayerInfo.LayerType in [PSP_LAYER_UNDEFINED,PSP_LAYER_RASTER]) then
begin
Stream.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:=Stream.Position;
Stream.Read(ChunkSize,sizeof(ChunkSize));
end
else
begin
SetLength(LayerName,256);
Stream.Read(LayerName[1],256);
Stream.Read(LayerInfo,sizeof(LayerInfo));
// continue only with normal (raster) chunks
if LayerInfo.LayerType<>PSP_LAYER_NORMAL then
begin
Stream.Position:=NextLayerPosition;
Continue;
end;
end;
{if LayerInfo.Visible = 0 then
begin
Stream.Position:=NextLayerPosition;
Continue;
end;}
Stream.Read(BitmapCount,sizeof(BitmapCount));
Stream.Read(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 BitmapCount > 0 then
begin
//Dec( BitmapCount );
if ChannelCount>3 then
begin
ColorManager.TargetColorScheme:=csBGRA;
FBitmap.PixelFormat:=pf32Bit;
end;
if Version>3 then Stream.Position:=LastPosition+ChunkSize;
// allocate memory for all channels and read raw data
for X:=0 to pred(ChannelCount) do
ReadChannelData;
R:=RedBuffer;
G:=GreenBuffer;
B:=BlueBuffer;
C:=CompBuffer;
TRY
RowWidth := LayerInfo.SavedImageRectangle.Right -
LayerInfo.SavedImageRectangle.Left;
CASE ImageProperties.BitsPerSample of
1: RowIncrement:=(RowWidth +7) div 8;
4: RowIncrement:=RowWidth div 2+1;
8: RowIncrement:=RowWidth;
else RowIncrement := RowSize;
end;
RowOffset := LayerInfo.SavedImageRectangle.Left *
((BitsPerSample + 7) div 8);
if ColorManager.TargetColorScheme in [csIndexed,csG] then
begin
//for Y:=0 to pred(Height) do
for Y:= LayerInfo.SavedImageRectangle.Top to
LayerInfo.SavedImageRectangle.Bottom do
begin
//ColorManager.ConvertRow([C],FBitmap.ScanLine[Y],Width,$FF);
ColorManager.ConvertRow([C],
Pointer( Integer( FBitmap.ScanLine[Y] ) + RowOffset ),
RowWidth,$FF);
Inc( C, RowIncrement ); //Inc(C,RowSize);
end;
end
else
begin
//for Y:=0 to pred(Height) do
for Y:= LayerInfo.SavedImageRectangle.Top to
LayerInfo.SavedImageRectangle.Bottom-1 do
begin
//ColorManager.ConvertRow([R,G,B,C],FBitmap.ScanLine[Y],Width,$FF);
ColorManager.ConvertRow([R,G,B,C],
Pointer( Integer( FBitmap.ScanLine[Y] ) + RowOffset ),
RowWidth,$FF);
Inc( R, RowIncrement ); //Inc(R,RowSize);
Inc( G, RowIncrement ); //Inc(G,RowSize);
Inc( B, RowIncrement ); //Inc(B,RowSize);
Inc( C, RowIncrement ); //Inc(C,RowSize);
end;
end;
FINALLY
{if Assigned(RedBuffer) then FreeMem(RedBuffer);
if Assigned(GreenBuffer) then FreeMem(GreenBuffer);
if Assigned(BlueBuffer) then FreeMem(BlueBuffer);
if Assigned(CompBuffer) then FreeMem(CompBuffer);
RedBuffer := nil;
GreenBuffer := nil;
BlueBuffer := nil;
CompBuffer := nil;}
END;
// after the raster layer has been read there's no need to loop further
asm
nop
end;
Break; //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
end;
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 Stream.Read(ChunkSize,sizeof(ChunkSize));
Stream.Read(Index,sizeof(Index));
Stream.Read(RawPalette,Index*sizeof(TRGBQuad));
ColorManager.CreateColorPalette(FBitmap,[@RawPalette],pfInterlaced8Quad,Index,True);
end;
end;
// explicitly set stream position to next main block as we might have read a block only partially
Stream.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);
if Assigned(CompBuffer) then FreeMem(BlueBuffer);
end;
end
else GraphicExError(1{gesInvalidImage},['PSP']);
end;
//----------------------------------------------------------------------------------------------------------------------
function TPSPGraphic.ReadImageProperties(Stream: PStream; 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 Char; // 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
Result:=Stream.Position<Stream.Size;
if Result then
begin
Stream.Read(HeaderIdentifier,sizeof(HeaderIdentifier));
Stream.Read(BlockIdentifier,sizeof(BlockIdentifier));
if Header.MajorVersion=3 then Stream.Read(InitialChunkLength,sizeof(InitialChunkLength));
Stream.Read(TotalBlockLength,sizeof(TotalBlockLength));
end;
end;
//--------------- end local functions ---------------------------------------
begin
Result:=inherited ReadImageProperties(Stream,ImageIndex);
with FImageProperties do
begin
Stream.Read(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:=Stream.Position;
if Header.MajorVersion>3 then Stream.Read(ChunkSize,sizeof(ChunkSize));
Stream.Read(Image,sizeof(Image));
Stream.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;
//----------------- TPNGGraphic ----------------------------------------------------------------------------------------
const
PNGMagic: array[0..7] of Byte = (137,80,78,71,13,10,26,10);
// recognized and handled chunk types
IHDR = 'IHDR';
IDAT = 'IDAT';
IEND = 'IEND';
PLTE = 'PLTE';
gAMA = 'gAMA';
tRNS = 'tRNS';
bKGD = '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;
//----------------------------------------------------------------------------------------------------------------------
{$IFNDEF NOCLASSES} class {$ENDIF}
function TPNGGraphic.CanLoad(Stream: PStream): boolean;
var Magic: array[0..7] of byte;
LastPosition: cardinal;
begin
LastPosition:=Stream.Position;
Result:=(Stream.Size-Stream.Position)>sizeof(Magic);
if Result then
begin
Stream.Read(Magic,sizeof(Magic));
Result:=CompareMem(@Magic,@PNGMagic,8);
end;
Stream.Position:=LastPosition;
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:=(Cardinal(FHeader.ChunkType) and Mask)=(Cardinal(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.Read(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: Move(Line^,Target^,BytesPerRow); // no filter, just copy data
1: begin // subtraction filter
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: begin // Up filter
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: begin // average filter
// first handle BPP virtual pixels to the left
Raw:=Line;
Decoded:=Line;
Prior:=PrevLine;
TargetRun:=Target;
for I:=0 to pred(BPP) 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: begin // paeth prediction
// 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 pred(BPP) 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: PStream);
var Description: TIHDRChunk;
begin
// free previous image
if Assigned(FBitmap) then FBitmap.Free;
FBasePosition:=Stream.Position;
FDecoder:=nil;
FStream:=Stream;
if ReadImageProperties(Stream,0) then
begin
with FImageProperties do
begin
FBitmap:=NewBitmap(Width,Height);
Stream.Position:=FBasePosition+8; // skip magic
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
{$IFDEF NOCLASSES}
new( FDecoder, Create(Z_PARTIAL_FLUSH,False) );
{$ELSE} FDecoder:=TLZ77Decoder.Create(Z_PARTIAL_FLUSH,False); {$ENDIF}
FDecoder.DecodeInit;
end
else
GraphicExError(5{gesUnsupportedFeature},[ErrorMsg[11]{gesCompressionScheme},PNG]);
// setup is done, now go for the chunks
repeat
FCurrentCRC:=LoadAndSwapHeader;
if IsChunk(IDAT) then
begin
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(9{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);
ColorManager.CreateColorPalette(FBitmap,[FRawBuffer],pfInterlaced8Triple,FHeader.Length div 3,True);
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.
Stream.Seek(FHeader.Length+4,spCurrent);
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(10{gesUnknownCriticalChunk});
until False;
finally
if Assigned(FDecoder) then
begin
FDecoder.DecodeEnd;
FDecoder.Free;
end;
if Assigned(FRawBuffer) then FreeMem(FRawBuffer);
end;
end;
end
else GraphicExError(1{gesInvalidImage},[PNG]);
end;
//----------------------------------------------------------------------------------------------------------------------
function TPNGGraphic.ReadImageProperties(Stream: PStream; ImageIndex: cardinal): boolean;
var Magic: array[0..7] of byte;
Description: TIHDRChunk;
begin
Result:=inherited ReadImageProperties(Stream,ImageIndex);
FStream:=Stream;
with FImageProperties do
begin
Stream.Read(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;
Stream.Seek(FHeader.Length+4,spCurrent);
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(System.Swap(PWord(FRawBuffer)^),15,3);
16: FBackgroundColor:=MulDiv16(System.Swap(PWord(FRawBuffer)^),255,65535);
else // 1,4,8 bits gray scale
FBackgroundColor:=Byte(System.Swap(PWord(FRawBuffer)^));
end;
end;
2,6: // RGB(A)
begin
Run:=FRawBuffer;
if BitDepth=16 then
begin
R:=MulDiv16(System.Swap(Run^),255,65535);
Inc(Run);
G:=MulDiv16(System.Swap(Run^),255,65535);
Inc(Run);
B:=MulDiv16(System.Swap(Run^),255,65535);
end
else
begin
R:=Byte(System.Swap(Run^));
Inc(Run);
G:=Byte(System.Swap(Run^));
Inc(Run);
B:=Byte(System.Swap(Run^));
end;
FBackgroundColor:=Windows.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 PChar; // 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,BytesPerRow,InterlaceRowBytes,InterlaceWidth: integer;
begin
RowBuffer[False]:=nil;
RowBuffer[True]:=nil;
try
// we can set the dimensions too without
// initiating color conversions
if FBitmap=nil then FBitmap:=NewBitmap(TIHDRChunk(Description).Width,TIHDRChunk(Description).Height);
// adjust pixel format etc. if not yet done
if FBitmap.PixelFormat=pfDevice then FSourceBPP:=SetupColorDepth(TIHDRChunk(Description).ColorType,TIHDRChunk(Description).BitDepth);
if TIHDRChunk(Description).BitDepth=16 then TargetBPP:=FSourceBPP div 2 else TargetBPP:=FSourceBPP;
// set background and transparency color, these values must be set after the
// bitmap is actually valid (although, not filled)
FBitmap.Canvas.Brush.Color:=FBackgroundColor;
FBitmap.Canvas.FillRect(Rect(0,0,FBitmap.Width,FBitmap.Height));
// determine maximum number of bytes per row and consider there's one filter byte at the start of each row
BytesPerRow:=TargetBPP*((FBitmap.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 FBitmap.Width<=ColumnStart[Pass] then Continue;
InterlaceWidth:=(FBitmap.Width+ColumnIncrement[Pass]-1-ColumnStart[Pass]) div ColumnIncrement[Pass];
InterlaceRowBytes:=TargetBPP*((InterlaceWidth*TIHDRChunk(Description).BitDepth+7) div 8)+1;
Row:=RowStart[Pass];
while Row<FBitmap.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)],FBitmap.ScanLine[Row],FBitmap.Width,PassMask[Pass]);
EvenRow:=not EvenRow;
// continue with next row in interlaced order
Inc(Row,RowIncrement[Pass]);
end;
end;
end
else
begin
for Row:=0 to pred(FBitmap.Height) 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)],FBitmap.ScanLine[Row],FBitmap.Width,$FF);
EvenRow:=not EvenRow;
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;
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(System.Swap(PWord(FRawBuffer)^),15,3);
16: R:=MulDiv16(System.Swap(PWord(FRawBuffer)^),255,65535);
else // 1,4,8 bits gray scale
R:=Byte(System.Swap(PWord(FRawBuffer)^));
end;
FTransparentColor:=Windows.RGB(R,R,R);
end;
2: // RGB
begin
Run:=FRawBuffer;
if BitDepth=16 then
begin
R:=MulDiv16(System.Swap(Run^),255,65535);
Inc(Run);
G:=MulDiv16(System.Swap(Run^),255,65535);
Inc(Run);
B:=MulDiv16(System.Swap(Run^),255,65535);
end
else
begin
R:=Byte(System.Swap(Run^));
Inc(Run);
G:=Byte(System.Swap(Run^));
Inc(Run);
B:=Byte(System.Swap(Run^));
end;
FTransparentColor:=Windows.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],KOL.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.Read(FRawBuffer^,FHeader.Length);
FStream.Read(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(6{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(8{gesExtraCompressedData},[PNG]);
Break;
end;
if FDecoder.ZLibResult<>Z_OK then GraphicExError(7{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
begin
ColorManager.SourceColorScheme:=csG;
ColorManager.TargetColorScheme:=csG;
ColorManager.SourceSamplesPerPixel:=1;
ColorManager.TargetSamplesPerPixel:=1;
ColorManager.SourceBitsPerSample:=BitDepth;
// 2 bits values are converted to 4 bits values because DIBs don't know the former variant
case BitDepth of
2: ColorManager.TargetBitsPerSample:=4;
16: ColorManager.TargetBitsPerSample:=8;
else
ColorManager.TargetBitsPerSample:=BitDepth;
end;
FBitmap.PixelFormat:=ColorManager.TargetPixelFormat;
ColorManager.CreateGrayscalePalette(FBitmap,False);
Result:=(BitDepth+7) div 8;
end
else GraphicExError(2{gesInvalidColorFormat},[PNG]);
2: // RGB
if BitDepth in [8,16] then
begin
ColorManager.SourceSamplesPerPixel:=3;
ColorManager.TargetSamplesPerPixel:=3;
ColorManager.SourceColorScheme:=csRGB;
ColorManager.TargetColorScheme:=csBGR;
ColorManager.SourceBitsPerSample:=BitDepth;
ColorManager.TargetBitsPerSample:=8;
FBitmap.PixelFormat:=pf24Bit;
Result:=BitDepth*3 div 8;
end
else GraphicExError(2{gesInvalidColorFormat},[PNG]);
3: // palette
if BitDepth in [1,2,4,8] then
begin
ColorManager.SourceColorScheme:=csIndexed;
ColorManager.TargetColorScheme:=csIndexed;
ColorManager.SourceSamplesPerPixel:=1;
ColorManager.TargetSamplesPerPixel:=1;
ColorManager.SourceBitsPerSample:=BitDepth;
// 2 bits values are converted to 4 bits values because DIBs don't know the former variant
if BitDepth=2 then ColorManager.TargetBitsPerSample:=4 else ColorManager.TargetBitsPerSample:=BitDepth;
FBitmap.PixelFormat:=ColorManager.TargetPixelFormat;
Result:=1;
end
else GraphicExError(2{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
begin
ColorManager.SourceSamplesPerPixel:=1;
ColorManager.TargetSamplesPerPixel:=1;
ColorManager.SourceBitsPerSample:=BitDepth;
ColorManager.TargetBitsPerSample:=8;
ColorManager.SourceColorScheme:=csGA;
ColorManager.TargetColorScheme:=csIndexed;
FBitmap.PixelFormat:=pf8Bit;
ColorManager.CreateGrayScalePalette(FBitmap,False);
Result:=2*BitDepth div 8;
end
else GraphicExError(2{gesInvalidColorFormat},[PNG]);
6: // RGB with alpha (8, 16)
if BitDepth in [8,16] then
begin
ColorManager.SourceSamplesPerPixel:=4;
ColorManager.TargetSamplesPerPixel:=4;
ColorManager.SourceColorScheme:=csRGBA;
ColorManager.TargetColorScheme:=csBGRA;
ColorManager.SourceBitsPerSample:=BitDepth;
ColorManager.TargetBitsPerSample:=8;
FBitmap.PixelFormat:=pf32Bit;
Result:=BitDepth*4 div 8;
end
else GraphicExError(2{gesInvalidColorFormat},[PNG]);
else
GraphicExError(2{gesInvalidColorFormat},[PNG]);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
end.