Repo for the search and displace ingest module that takes odf, docx and pdf and transforms it into .md to be used with search and displace operations
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

801 lines
26 KiB

{
Vampyre Imaging Library
by Marek Mauder
http://imaginglib.sourceforge.net
The contents of this file are used with permission, subject to the Mozilla
Public License Version 1.1 (the "License"); you may not use this file except
in compliance with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/MPL-1.1.html
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
the specific language governing rights and limitations under the License.
Alternatively, the contents of this file may be used under the terms of the
GNU Lesser General Public License (the "LGPL License"), in which case the
provisions of the LGPL License are applicable instead of those above.
If you wish to allow use of your version of this file only under the terms
of the LGPL License and not to allow others to use your version of this file
under the MPL, indicate your decision by deleting the provisions above and
replace them with the notice and other provisions required by the LGPL
License. If you do not delete the provisions above, a recipient may use
your version of this file under either the MPL or the LGPL License.
For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
}
{ This unit contains image format loader/saver for Photoshop PSD image format.}
unit ImagingPsd;
{$I ImagingOptions.inc}
interface
uses
SysUtils, ImagingTypes, Imaging, ImagingColors, ImagingUtility;
type
{ Class for loading and saving Adobe Photoshop PSD images.
Loading and saving of indexed, grayscale, RGB(A), HDR (FP32), and CMYK
(auto converted to RGB) images is supported. Non-HDR gray, RGB,
and CMYK images can have 8bit or 16bit color channels.
There is no support for loading mono images, duotone images are treated
like grayscale images, and multichannel and CIE Lab images are loaded as
RGB images but without actual conversion to RGB color space.
Also no layer information is loaded.}
TPSDFileFormat = class(TImageFileFormat)
private
FSaveAsLayer: LongBool;
protected
procedure Define; override;
function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
OnlyFirstLevel: Boolean): Boolean; override;
function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
Index: LongInt): Boolean; override;
procedure ConvertToSupported(var Image: TImageData;
const Info: TImageFormatInfo); override;
public
function TestFormat(Handle: TImagingHandle): Boolean; override;
published
property SaveAsLayer: LongBool read FSaveAsLayer write FSaveAsLayer;
end;
implementation
uses
ImagingExtras;
const
SPSDFormatName = 'Photoshop Image';
SPSDMasks = '*.psd,*.pdd';
PSDSupportedFormats: TImageFormats = [ifIndex8, ifGray8, ifA8Gray8,
ifR8G8B8, ifA8R8G8B8, ifGray16, ifA16Gray16, ifR16G16B16, ifA16R16G16B16,
ifR32F, ifR32G32B32F, ifA32R32G32B32F];
PSDDefaultSaveAsLayer = True;
const
SPSDMagic = '8BPS';
CompressionNone: Word = 0;
CompressionRLE: Word = 1;
type
{$MINENUMSIZE 2}
{ PSD Image color mode.}
TPSDColorMode = (
cmMono = 0,
cmGrayscale = 1,
cmIndexed = 2,
cmRGB = 3,
cmCMYK = 4,
cmMultiChannel = 7,
cmDuoTone = 8,
cmLab = 9
);
{ PSD image main header.}
TPSDHeader = packed record
Signature: TChar4; // Format ID '8BPS'
Version: Word; // Always 1
Reserved: array[0..5] of Byte; // Reserved, all zero
Channels: Word; // Number of color channels (1-24) including alpha channels
Rows : LongWord; // Height of image in pixels (1-30000)
Columns: LongWord; // Width of image in pixels (1-30000)
Depth: Word; // Number of bits per channel (1, 8, and 16)
Mode: TPSDColorMode; // Color mode
end;
TPSDChannelInfo = packed record
ChannelID: Word; // 0 = Red, 1 = Green, 2 = Blue etc., -1 = Transparency mask, -2 = User mask
Size: LongWord; // Size of channel data.
end;
procedure SwapHeader(var Header: TPSDHeader);
begin
Header.Version := SwapEndianWord(Header.Version);
Header.Channels := SwapEndianWord(Header.Channels);
Header.Depth := SwapEndianWord(Header.Depth);
Header.Rows := SwapEndianLongWord(Header.Rows);
Header.Columns := SwapEndianLongWord(Header.Columns);
Header.Mode := TPSDColorMode(SwapEndianWord(Word(Header.Mode)));
end;
{
TPSDFileFormat class implementation
}
procedure TPSDFileFormat.Define;
begin
inherited;
FName := SPSDFormatName;
FFeatures := [ffLoad, ffSave];
FSupportedFormats := PSDSupportedFormats;
AddMasks(SPSDMasks);
FSaveAsLayer := PSDDefaultSaveAsLayer;
RegisterOption(ImagingPSDSaveAsLayer, @FSaveAsLayer);
end;
function TPSDFileFormat.LoadData(Handle: TImagingHandle;
var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
var
Header: TPSDHeader;
ByteCount: LongWord;
RawPal: array[0..767] of Byte;
Compression, PackedSize: Word;
LineSize, ChannelPixelSize, WidthBytes,
CurrChannel, MaxRLESize, I, Y, X: LongInt;
Info: TImageFormatInfo;
PackedLine, LineBuffer: PByte;
RLELineSizes: array of Word;
Col32: TColor32Rec;
Col64: TColor64Rec;
PCol32: PColor32Rec;
PCol64: PColor64Rec;
{ PackBits RLE decode code from Mike Lischke's GraphicEx library.}
procedure DecodeRLE(Source, Dest: PByte; PackedSize, UnpackedSize: LongInt);
var
Count: LongInt;
begin
while (UnpackedSize > 0) and (PackedSize > 0) do
begin
Count := ShortInt(Source^);
Inc(Source);
Dec(PackedSize);
if Count < 0 then
begin
// Replicate next byte -Count + 1 times
if Count = -128 then
Continue;
Count := -Count + 1;
if Count > UnpackedSize then
Count := UnpackedSize;
FillChar(Dest^, Count, Source^);
Inc(Source);
Dec(PackedSize);
Inc(Dest, Count);
Dec(UnpackedSize, Count);
end
else
begin
// Copy next Count + 1 bytes from input
Inc(Count);
if Count > UnpackedSize then
Count := UnpackedSize;
if Count > PackedSize then
Count := PackedSize;
Move(Source^, Dest^, Count);
Inc(Dest, Count);
Inc(Source, Count);
Dec(PackedSize, Count);
Dec(UnpackedSize, Count);
end;
end;
end;
begin
Result := False;
SetLength(Images, 1);
with GetIO, Images[0] do
begin
// Read PSD header
Read(Handle, @Header, SizeOf(Header));
SwapHeader(Header);
// Determine image data format
Format := ifUnknown;
case Header.Mode of
cmGrayscale, cmDuoTone:
begin
if Header.Depth in [8, 16] then
begin
if Header.Channels = 1 then
Format := IffFormat(Header.Depth = 8, ifGray8, ifGray16)
else if Header.Channels >= 2 then
Format := IffFormat(Header.Depth = 8, ifA8Gray8, ifA16Gray16);
end
else if (Header.Depth = 32) and (Header.Channels = 1) then
Format := ifR32F;
end;
cmIndexed:
begin
if Header.Depth = 8 then
Format := ifIndex8;
end;
cmRGB, cmMultiChannel, cmCMYK, cmLab:
begin
if Header.Depth in [8, 16] then
begin
if Header.Channels = 3 then
Format := IffFormat(Header.Depth = 8, ifR8G8B8, ifR16G16B16)
else if Header.Channels >= 4 then
Format := IffFormat(Header.Depth = 8, ifA8R8G8B8, ifA16R16G16B16);
end
else if Header.Depth = 32 then
begin
if Header.Channels = 3 then
Format := ifR32G32B32F
else if Header.Channels >= 4 then
Format := ifA32R32G32B32F;
end;
end;
cmMono:; // Not supported
end;
// Exit if no compatible format was found
if Format = ifUnknown then
Exit;
NewImage(Header.Columns, Header.Rows, Format, Images[0]);
Info := GetFormatInfo(Format);
// Read or skip Color Mode Data Block (palette)
Read(Handle, @ByteCount, SizeOf(ByteCount));
ByteCount := SwapEndianLongWord(ByteCount);
if Format = ifIndex8 then
begin
// Read palette only for indexed images
Read(Handle, @RawPal, SizeOf(RawPal));
for I := 0 to 255 do
begin
Palette[I].A := $FF;
Palette[I].R := RawPal[I + 0];
Palette[I].G := RawPal[I + 256];
Palette[I].B := RawPal[I + 512];
end;
end
else
Seek(Handle, ByteCount, smFromCurrent);
// Skip Image Resources Block
Read(Handle, @ByteCount, SizeOf(ByteCount));
ByteCount := SwapEndianLongWord(ByteCount);
Seek(Handle, ByteCount, smFromCurrent);
// Now there is Layer and Mask Information Block
Read(Handle, @ByteCount, SizeOf(ByteCount));
ByteCount := SwapEndianLongWord(ByteCount);
// Skip Layer and Mask Information Block
Seek(Handle, ByteCount, smFromCurrent);
// Read compression flag
Read(Handle, @Compression, SizeOf(Compression));
Compression := SwapEndianWord(Compression);
if Compression = CompressionRLE then
begin
// RLE compressed PSDs (most) have first lengths of compressed scanlines
// for each channel stored
SetLength(RLELineSizes, Height * Header.Channels);
Read(Handle, @RLELineSizes[0], Length(RLELineSizes) * SizeOf(Word));
SwapEndianWord(@RLELineSizes[0], Height * Header.Channels);
MaxRLESize := RLELineSizes[0];
for I := 1 to High(RLELineSizes) do
begin
if MaxRLESize < RLELineSizes[I] then
MaxRLESize := RLELineSizes[I];
end;
end
else
MaxRLESize := 0;
ChannelPixelSize := Info.BytesPerPixel div Info.ChannelCount;
LineSize := Width * ChannelPixelSize;
WidthBytes := Width * Info.BytesPerPixel;
GetMem(LineBuffer, LineSize);
GetMem(PackedLine, MaxRLESize);
try
// Image color chanels are stored separately in PSDs so we will load
// one by one and copy their data to appropriate addresses of dest image.
for I := 0 to Header.Channels - 1 do
begin
// Now determine to which color channel of destination image we are going
// to write pixels.
if I <= 4 then
begin
// If PSD has alpha channel we need to switch current channel order -
// PSDs have alpha stored after blue channel but Imaging has alpha
// before red.
if Info.HasAlphaChannel and (Header.Mode <> cmCMYK) then
begin
if I = Info.ChannelCount - 1 then
CurrChannel := I
else
CurrChannel := Info.ChannelCount - 2 - I;
end
else
CurrChannel := Info.ChannelCount - 1 - I;
end
else
begin
// No valid channel remains
CurrChannel := -1;
end;
if CurrChannel >= 0 then
begin
for Y := 0 to Height - 1 do
begin
if Compression = CompressionRLE then
begin
// Read RLE line and decompress it
PackedSize := RLELineSizes[I * Height + Y];
Read(Handle, PackedLine, PackedSize);
DecodeRLE(PackedLine, LineBuffer, PackedSize, LineSize);
end
else
begin
// Just read uncompressed line
Read(Handle, LineBuffer, LineSize);
end;
// Swap endian if needed
if ChannelPixelSize = 4 then
SwapEndianLongWord(PLongWord(LineBuffer), Width)
else if ChannelPixelSize = 2 then
SwapEndianWord(PWordArray(LineBuffer), Width);
if Info.ChannelCount > 1 then
begin
// Copy each pixel fragment to its right place in destination image
for X := 0 to Width - 1 do
begin
Move(PByteArray(LineBuffer)[X * ChannelPixelSize],
PByteArray(Bits)[Y * WidthBytes + X * Info.BytesPerPixel + CurrChannel * ChannelPixelSize],
ChannelPixelSize);
end;
end
else
begin
// Just copy the line
Move(LineBuffer^, PByteArray(Bits)[Y * LineSize], LineSize);
end;
end;
end
else
begin
// Skip current color channel, not needed for image loading - just to
// get stream's position to the end of PSD
if Compression = CompressionRLE then
begin
for Y := 0 to Height - 1 do
Seek(Handle, RLELineSizes[I * Height + Y], smFromCurrent);
end
else
Seek(Handle, LineSize * Height, smFromCurrent);
end;
end;
if Header.Mode = cmCMYK then
begin
// Convert CMYK images to RGB (alpha is ignored here). PSD stores CMYK
// channels in the way that first requires substraction from max channel value
if ChannelPixelSize = 1 then
begin
PCol32 := Bits;
for X := 0 to Width * Height - 1 do
begin
Col32.A := 255 - PCol32.A;
Col32.R := 255 - PCol32.R;
Col32.G := 255 - PCol32.G;
Col32.B := 255 - PCol32.B;
CMYKToRGB(Col32.A, Col32.R, Col32.G, Col32.B, PCol32.R, PCol32.G, PCol32.B);
PCol32.A := 255;
Inc(PCol32);
end;
end
else
begin
PCol64 := Bits;
for X := 0 to Width * Height - 1 do
begin
Col64.A := 65535 - PCol64.A;
Col64.R := 65535 - PCol64.R;
Col64.G := 65535 - PCol64.G;
Col64.B := 65535 - PCol64.B;
CMYKToRGB16(Col64.A, Col64.R, Col64.G, Col64.B, PCol64.R, PCol64.G, PCol64.B);
PCol64.A := 65535;
Inc(PCol64);
end;
end;
end;
Result := True;
finally
FreeMem(LineBuffer);
FreeMem(PackedLine);
end;
end;
end;
function TPSDFileFormat.SaveData(Handle: TImagingHandle;
const Images: TDynImageDataArray; Index: LongInt): Boolean;
type
TURect = packed record
Top, Left, Bottom, Right: LongWord;
end;
const
BlendMode: TChar8 = '8BIMnorm';
LayerOptions: array[0..3] of Byte = (255, 0, 0, 0);
LayerName: array[0..7] of AnsiChar = #7'Layer 0';
var
MustBeFreed: Boolean;
ImageToSave: TImageData;
Info: TImageFormatInfo;
Header: TPSDHeader;
I, CurrChannel, ChannelPixelSize: LongInt;
LayerBlockOffset, SaveOffset, ChannelInfoOffset: Integer;
ChannelInfo: TPSDChannelInfo;
R: TURect;
LongVal: LongWord;
WordVal, LayerCount: Word;
RawPal: array[0..767] of Byte;
ChannelDataSizes: array of Integer;
function PackLine(Src, Dest: PByteArray; Length: Integer): Integer;
var
I, Remaining: Integer;
begin
Remaining := Length;
Result := 0;
while Remaining > 0 do
begin
I := 0;
// Look for characters same as the first
while (I < 128) and (Remaining - I > 0) and (Src[0] = Src[I]) do
Inc(I);
if I > 2 then
begin
Dest[0] := Byte(-(I - 1));
Dest[1] := Src[0];
Dest := PByteArray(@Dest[2]);
Src := PByteArray(@Src[I]);
Dec(Remaining, I);
Inc(Result, 2);
end
else
begin
// Look for different characters
I := 0;
while (I < 128) and (Remaining - (I + 1) > 0) and
((Src[I] <> Src[I + 1]) or (Remaining - (I + 2) <= 0) or
(Src[I] <> Src[I + 2])) do
begin
Inc(I);
end;
// If there's only 1 remaining, the previous WHILE doesn't catch it
if Remaining = 1 then
I := 1;
if I > 0 then
begin
// Some distinct ones found
Dest[0] := I - 1;
Move(Src[0], Dest[1], I);
Dest := PByteArray(@Dest[1 + I]);
Src := PByteArray(@Src[I]);
Dec(Remaining, I);
Inc(Result, I + 1);
end;
end;
end;
end;
procedure WriteChannelData(SeparateChannelStorage: Boolean);
var
I, X, Y, LineSize, WidthBytes, RLETableOffset, CurrentOffset, WrittenLineSize: Integer;
LineBuffer, RLEBuffer: PByteArray;
RLELengths: array of Word;
Compression: Word;
begin
LineSize := ImageToSave.Width * ChannelPixelSize;
WidthBytes := ImageToSave.Width * Info.BytesPerPixel;
GetMem(LineBuffer, LineSize);
GetMem(RLEBuffer, LineSize * 3);
SetLength(RLELengths, ImageToSave.Height * Info.ChannelCount);
RLETableOffset := 0;
// No compression for FP32, Photoshop won't open them
Compression := Iff(Info.IsFloatingPoint, CompressionNone, CompressionRLE);
if not SeparateChannelStorage then
begin
// This is for storing background merged image. There's only one
// compression flag and one RLE lenghts table for all channels
WordVal := Swap(Compression);
GetIO.Write(Handle, @WordVal, SizeOf(WordVal));
if Compression = CompressionRLE then
begin
RLETableOffset := GetIO.Tell(Handle);
GetIO.Write(Handle, @RLELengths[0], SizeOf(Word) * ImageToSave.Height * Info.ChannelCount);
end;
end;
for I := 0 to Info.ChannelCount - 1 do
begin
if SeparateChannelStorage then
begin
// Layer image data has compression flag and RLE lenghts table
// independent for each channel
WordVal := Swap(CompressionRLE);
GetIO.Write(Handle, @WordVal, SizeOf(WordVal));
if Compression = CompressionRLE then
begin
RLETableOffset := GetIO.Tell(Handle);
GetIO.Write(Handle, @RLELengths[0], SizeOf(Word) * ImageToSave.Height);
ChannelDataSizes[I] := 0;
end;
end;
// Now determine which color channel we are going to write to file.
if Info.HasAlphaChannel then
begin
if I = Info.ChannelCount - 1 then
CurrChannel := I
else
CurrChannel := Info.ChannelCount - 2 - I;
end
else
CurrChannel := Info.ChannelCount - 1 - I;
for Y := 0 to ImageToSave.Height - 1 do
begin
if Info.ChannelCount > 1 then
begin
// Copy each pixel fragment to its right place in destination image
for X := 0 to ImageToSave.Width - 1 do
begin
Move(PByteArray(ImageToSave.Bits)[Y * WidthBytes + X * Info.BytesPerPixel + CurrChannel * ChannelPixelSize],
PByteArray(LineBuffer)[X * ChannelPixelSize], ChannelPixelSize);
end;
end
else
Move(PByteArray(ImageToSave.Bits)[Y * LineSize], LineBuffer^, LineSize);
// Write current channel line to file (swap endian if needed first)
if ChannelPixelSize = 4 then
SwapEndianLongWord(PLongWord(LineBuffer), ImageToSave.Width)
else if ChannelPixelSize = 2 then
SwapEndianWord(PWordArray(LineBuffer), ImageToSave.Width);
if Compression = CompressionRLE then
begin
// Compress and write line
WrittenLineSize := PackLine(LineBuffer, RLEBuffer, LineSize);
RLELengths[ImageToSave.Height * I + Y] := SwapEndianWord(WrittenLineSize);
GetIO.Write(Handle, RLEBuffer, WrittenLineSize);
end
else
begin
WrittenLineSize := LineSize;
GetIO.Write(Handle, LineBuffer, WrittenLineSize);
end;
if SeparateChannelStorage then
Inc(ChannelDataSizes[I], WrittenLineSize);
end;
if SeparateChannelStorage and (Compression = CompressionRLE) then
begin
// Update channel RLE lengths
CurrentOffset := GetIO.Tell(Handle);
GetIO.Seek(Handle, RLETableOffset, smFromBeginning);
GetIO.Write(Handle, @RLELengths[ImageToSave.Height * I], SizeOf(Word) * ImageToSave.Height);
GetIO.Seek(Handle, CurrentOffset, smFromBeginning);
Inc(ChannelDataSizes[I], SizeOf(Word) * ImageToSave.Height);
end;
end;
if not SeparateChannelStorage and (Compression = CompressionRLE) then
begin
// Update channel RLE lengths
CurrentOffset := GetIO.Tell(Handle);
GetIO.Seek(Handle, RLETableOffset, smFromBeginning);
GetIO.Write(Handle, @RLELengths[0], SizeOf(Word) * ImageToSave.Height * Info.ChannelCount);
GetIO.Seek(Handle, CurrentOffset, smFromBeginning);
end;
FreeMem(LineBuffer);
FreeMem(RLEBuffer);
end;
begin
Result := False;
if MakeCompatible(Images[Index], ImageToSave, MustBeFreed) then
with GetIO, ImageToSave do
try
Info := GetFormatInfo(Format);
ChannelPixelSize := Info.BytesPerPixel div Info.ChannelCount;
// Fill header with proper info and save it
FillChar(Header, SizeOf(Header), 0);
Header.Signature := SPSDMagic;
Header.Version := 1;
Header.Channels := Info.ChannelCount;
Header.Rows := Height;
Header.Columns := Width;
Header.Depth := Info.BytesPerPixel div Info.ChannelCount * 8;
if Info.IsIndexed then
Header.Mode := cmIndexed
else if Info.HasGrayChannel or (Info.ChannelCount = 1) then
Header.Mode := cmGrayscale
else
Header.Mode := cmRGB;
SwapHeader(Header);
Write(Handle, @Header, SizeOf(Header));
// Write palette size and data
LongVal := SwapEndianLongWord(IffUnsigned(Info.IsIndexed, SizeOf(RawPal), 0));
Write(Handle, @LongVal, SizeOf(LongVal));
if Info.IsIndexed then
begin
for I := 0 to Info.PaletteEntries - 1 do
begin
RawPal[I] := Palette[I].R;
RawPal[I + 256] := Palette[I].G;
RawPal[I + 512] := Palette[I].B;
end;
Write(Handle, @RawPal, SizeOf(RawPal));
end;
// Write empty resource and layer block sizes
LongVal := 0;
Write(Handle, @LongVal, SizeOf(LongVal));
LayerBlockOffset := Tell(Handle);
Write(Handle, @LongVal, SizeOf(LongVal));
if FSaveAsLayer and (ChannelPixelSize < 4) then // No Layers for FP32 images
begin
LayerCount := SwapEndianWord(Iff(Info.HasAlphaChannel, Word(-1), 1)); // Must be -1 to get transparency in Photoshop
R.Top := 0;
R.Left := 0;
R.Bottom := SwapEndianLongWord(Height);
R.Right := SwapEndianLongWord(Width);
WordVal := SwapEndianWord(Info.ChannelCount);
Write(Handle, @LongVal, SizeOf(LongVal)); // Layer section size, empty now
Write(Handle, @LayerCount, SizeOf(LayerCount)); // Layer count
Write(Handle, @R, SizeOf(R)); // Bounds rect
Write(Handle, @WordVal, SizeOf(WordVal)); // Channel count
ChannelInfoOffset := Tell(Handle);
SetLength(ChannelDataSizes, Info.ChannelCount); // Empty channel infos
FillChar(ChannelInfo, SizeOf(ChannelInfo), 0);
for I := 0 to Info.ChannelCount - 1 do
Write(Handle, @ChannelInfo, SizeOf(ChannelInfo));
Write(Handle, @BlendMode, SizeOf(BlendMode)); // Blend mode = normal
Write(Handle, @LayerOptions, SizeOf(LayerOptions)); // Predefined options
LongVal := SwapEndianLongWord(16); // Extra data size (4 (mask size) + 4 (ranges size) + 8 (name))
Write(Handle, @LongVal, SizeOf(LongVal));
LongVal := 0;
Write(Handle, @LongVal, SizeOf(LongVal)); // Mask size = 0
LongVal := 0;
Write(Handle, @LongVal, SizeOf(LongVal)); // Blend ranges size
Write(Handle, @LayerName, SizeOf(LayerName)); // Layer name
WriteChannelData(True); // Write Layer image data
Write(Handle, @LongVal, SizeOf(LongVal)); // Global mask info size = 0
SaveOffset := Tell(Handle);
Seek(Handle, LayerBlockOffset, smFromBeginning);
// Update layer and mask section sizes
LongVal := SwapEndianLongWord(SaveOffset - LayerBlockOffset - 4);
Write(Handle, @LongVal, SizeOf(LongVal));
LongVal := SwapEndianLongWord(SaveOffset - LayerBlockOffset - 8);
Write(Handle, @LongVal, SizeOf(LongVal));
// Update layer channel info
Seek(Handle, ChannelInfoOffset, smFromBeginning);
for I := 0 to Info.ChannelCount - 1 do
begin
ChannelInfo.ChannelID := SwapEndianWord(I);
if (I = Info.ChannelCount - 1) and Info.HasAlphaChannel then
ChannelInfo.ChannelID := Swap(Word(-1));
ChannelInfo.Size := SwapEndianLongWord(ChannelDataSizes[I] + 2); // datasize (incl RLE table) + comp. flag
Write(Handle, @ChannelInfo, SizeOf(ChannelInfo));
end;
Seek(Handle, SaveOffset, smFromBeginning);
end;
// Write background merged image
WriteChannelData(False);
Result := True;
finally
if MustBeFreed then
FreeImage(ImageToSave);
end;
end;
procedure TPSDFileFormat.ConvertToSupported(var Image: TImageData;
const Info: TImageFormatInfo);
var
ConvFormat: TImageFormat;
begin
if Info.IsFloatingPoint then
begin
if Info.ChannelCount = 1 then
ConvFormat := ifR32F
else if Info.HasAlphaChannel then
ConvFormat := ifA32R32G32B32F
else
ConvFormat := ifR32G32B32F;
end
else if Info.HasGrayChannel then
ConvFormat := IffFormat(Info.HasAlphaChannel, ifA16Gray16, ifGray16)
else if Info.RBSwapFormat in GetSupportedFormats then
ConvFormat := Info.RBSwapFormat
else
ConvFormat := IffFormat(Info.HasAlphaChannel, ifA8R8G8B8, ifR8G8B8);
ConvertImage(Image, ConvFormat);
end;
function TPSDFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
var
Header: TPSDHeader;
ReadCount: LongInt;
begin
Result := False;
if Handle <> nil then
begin
ReadCount := GetIO.Read(Handle, @Header, SizeOf(Header));
SwapHeader(Header);
GetIO.Seek(Handle, -ReadCount, smFromCurrent);
Result := (ReadCount >= SizeOf(Header)) and
(Header.Signature = SPSDMagic) and
(Header.Version = 1);
end;
end;
initialization
RegisterImageFileFormat(TPSDFileFormat);
{
File Notes:
-- 0.77.1 ---------------------------------------------------
- 3 channel RGB float images are loaded and saved directly
as ifR32G32B32F.
-- 0.26.1 Changes/Bug Fixes ---------------------------------
- PSDs are now saved with RLE compression.
- Mask layer saving added to SaveData for images with alpha
(shows proper transparency when opened in Photoshop). Can be
enabled/disabled using option
- Fixed memory leak in SaveData.
-- 0.23 Changes/Bug Fixes -----------------------------------
- Saving implemented.
- Loading implemented.
- Unit created with initial stuff!
}
end.