CentrED/Imaging/ImagingDds.pas

854 lines
31 KiB
Plaintext

{
$Id: ImagingDds.pas 100 2007-06-28 21:09:52Z galfar $
Vampyre Imaging Library
by Marek Mauder
http://imaginglib.sourceforge.net
The contents of this file are used with permission, subject to the Mozilla
Public License Version 1.1 (the "License"); you may not use this file except
in compliance with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/MPL-1.1.html
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
the specific language governing rights and limitations under the License.
Alternatively, the contents of this file may be used under the terms of the
GNU Lesser General Public License (the "LGPL License"), in which case the
provisions of the LGPL License are applicable instead of those above.
If you wish to allow use of your version of this file only under the terms
of the LGPL License and not to allow others to use your version of this file
under the MPL, indicate your decision by deleting the provisions above and
replace them with the notice and other provisions required by the LGPL
License. If you do not delete the provisions above, a recipient may use
your version of this file under either the MPL or the LGPL License.
For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
}
{ This unit contains image format loader/saver for DirectDraw Surface images.}
unit ImagingDds;
{$I ImagingOptions.inc}
interface
uses
ImagingTypes, Imaging, ImagingUtility, ImagingFormats;
type
{ Class for loading and saving Microsoft DirectDraw surfaces.
It can load/save all D3D formats which have coresponding
TImageFormat. It supports plain textures, cube textures and
volume textures, all of these can have mipmaps. It can also
load some formats which have no exact TImageFormat, but can be easily
converted to one (bump map formats).
You can get some information about last loaded DDS file by calling
GetOption with ImagingDDSLoadedXXX options and you can set some
saving options by calling SetOption with ImagingDDSSaveXXX or you can
simply use properties of this class.
Note that when saving cube maps and volumes input image array must contain
at least number of images to build cube/volume based on current
Depth and MipMapCount settings.}
TDDSFileFormat = class(TImageFileFormat)
protected
FLoadedCubeMap: LongBool;
FLoadedVolume: LongBool;
FLoadedMipMapCount: LongInt;
FLoadedDepth: LongInt;
FSaveCubeMap: LongBool;
FSaveVolume: LongBool;
FSaveMipMapCount: LongInt;
FSaveDepth: LongInt;
procedure ComputeSubDimensions(Idx, Width, Height, MipMaps, Depth: LongInt;
IsCubeMap, IsVolume: Boolean; var CurWidth, CurHeight: LongInt);
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
constructor Create; override;
function TestFormat(Handle: TImagingHandle): Boolean; override;
procedure CheckOptionsValidity; override;
published
{ True if last loaded DDS file was cube map.}
property LoadedCubeMap: LongBool read FLoadedCubeMap write FLoadedCubeMap;
{ True if last loaded DDS file was volume texture.}
property LoadedVolume: LongBool read FLoadedVolume write FLoadedVolume;
{ Number of mipmap levels of last loaded DDS image.}
property LoadedMipMapCount: LongInt read FLoadedMipMapCount write FLoadedMipMapCount;
{ Depth (slices of volume texture or faces of cube map) of last loaded DDS image.}
property LoadedDepth: LongInt read FLoadedDepth write FLoadedDepth;
{ True if next DDS file to be saved should be stored as cube map.}
property SaveCubeMap: LongBool read FSaveCubeMap write FSaveCubeMap;
{ True if next DDS file to be saved should be stored as volume texture.}
property SaveVolume: LongBool read FSaveVolume write FSaveVolume;
{ Sets the number of mipmaps which should be stored in the next saved DDS file.
Only applies to cube maps and volumes, ordinary 2D textures save all
levels present in input.}
property SaveMipMapCount: LongInt read FSaveMipMapCount write FSaveMipMapCount;
{ Sets the depth (slices of volume texture or faces of cube map)
of the next saved DDS file.}
property SaveDepth: LongInt read FSaveDepth write FSaveDepth;
end;
implementation
const
SDDSFormatName = 'DirectDraw Surface';
SDDSMasks = '*.dds';
DDSSupportedFormats: TImageFormats = [ifR8G8B8, ifA8R8G8B8, ifX8R8G8B8,
ifA1R5G5B5, ifA4R4G4B4, ifX1R5G5B5, ifX4R4G4B4, ifR5G6B5, ifA16B16G16R16,
ifR32F, ifA32B32G32R32F, ifR16F, ifA16B16G16R16F, ifR3G3B2, ifGray8, ifA8Gray8,
ifGray16, ifDXT1, ifDXT3, ifDXT5];
const
{ Four character codes.}
DDSMagic = LongWord(Byte('D') or (Byte('D') shl 8) or (Byte('S') shl 16) or
(Byte(' ') shl 24));
FOURCC_DXT1 = LongWord(Byte('D') or (Byte('X') shl 8) or (Byte('T') shl 16) or
(Byte('1') shl 24));
FOURCC_DXT3 = LongWord(Byte('D') or (Byte('X') shl 8) or (Byte('T') shl 16) or
(Byte('3') shl 24));
FOURCC_DXT5 = LongWord(Byte('D') or (Byte('X') shl 8) or (Byte('T') shl 16) or
(Byte('5') shl 24));
{ Some D3DFORMAT values used in DDS files as FourCC value.}
D3DFMT_A16B16G16R16 = 36;
D3DFMT_R32F = 114;
D3DFMT_A32B32G32R32F = 116;
D3DFMT_R16F = 111;
D3DFMT_A16B16G16R16F = 113;
{ Constans used by TDDSurfaceDesc2.Flags.}
DDSD_CAPS = $00000001;
DDSD_HEIGHT = $00000002;
DDSD_WIDTH = $00000004;
DDSD_PITCH = $00000008;
DDSD_PIXELFORMAT = $00001000;
DDSD_MIPMAPCOUNT = $00020000;
DDSD_LINEARSIZE = $00080000;
DDSD_DEPTH = $00800000;
{ Constans used by TDDSPixelFormat.Flags.}
DDPF_ALPHAPIXELS = $00000001; // used by formats which contain alpha
DDPF_FOURCC = $00000004; // used by DXT and large ARGB formats
DDPF_RGB = $00000040; // used by RGB formats
DDPF_LUMINANCE = $00020000; // used by formats like D3DFMT_L16
DDPF_BUMPLUMINANCE = $00040000; // used by mixed signed-unsigned formats
DDPF_BUMPDUDV = $00080000; // used by signed formats
{ Constans used by TDDSCaps.Caps1.}
DDSCAPS_COMPLEX = $00000008;
DDSCAPS_TEXTURE = $00001000;
DDSCAPS_MIPMAP = $00400000;
{ Constans used by TDDSCaps.Caps2.}
DDSCAPS2_CUBEMAP = $00000200;
DDSCAPS2_POSITIVEX = $00000400;
DDSCAPS2_NEGATIVEX = $00000800;
DDSCAPS2_POSITIVEY = $00001000;
DDSCAPS2_NEGATIVEY = $00002000;
DDSCAPS2_POSITIVEZ = $00004000;
DDSCAPS2_NEGATIVEZ = $00008000;
DDSCAPS2_VOLUME = $00200000;
{ Flags for TDDSurfaceDesc2.Flags used when saving DDS file.}
DDS_SAVE_FLAGS = DDSD_CAPS or DDSD_PIXELFORMAT or DDSD_WIDTH or
DDSD_HEIGHT or DDSD_LINEARSIZE;
type
{ Stores the pixel format information.}
TDDPixelFormat = packed record
Size: LongWord; // Size of the structure = 32 bytes
Flags: LongWord; // Flags to indicate valid fields
FourCC: LongWord; // Four-char code for compressed textures (DXT)
BitCount: LongWord; // Bits per pixel if uncomp. usually 16,24 or 32
RedMask: LongWord; // Bit mask for the Red component
GreenMask: LongWord; // Bit mask for the Green component
BlueMask: LongWord; // Bit mask for the Blue component
AlphaMask: LongWord; // Bit mask for the Alpha component
end;
{ Specifies capabilities of surface.}
TDDSCaps = packed record
Caps1: LongWord; // Should always include DDSCAPS_TEXTURE
Caps2: LongWord; // For cubic environment maps
Reserved: array[0..1] of LongWord; // Reserved
end;
{ Record describing DDS file contents.}
TDDSurfaceDesc2 = packed record
Size: LongWord; // Size of the structure = 124 Bytes
Flags: LongWord; // Flags to indicate valid fields
Height: LongWord; // Height of the main image in pixels
Width: LongWord; // Width of the main image in pixels
PitchOrLinearSize: LongWord; // For uncomp formats number of bytes per
// scanline. For comp it is the size in
// bytes of the main image
Depth: LongWord; // Only for volume text depth of the volume
MipMaps: LongInt; // Total number of levels in the mipmap chain
Reserved1: array[0..10] of LongWord; // Reserved
PixelFormat: TDDPixelFormat; // Format of the pixel data
Caps: TDDSCaps; // Capabilities
Reserved2: LongWord; // Reserved
end;
{ DDS file header.}
TDDSFileHeader = packed record
Magic: LongWord; // File format magic
Desc: TDDSurfaceDesc2; // Surface description
end;
{ TDDSFileFormat class implementation }
constructor TDDSFileFormat.Create;
begin
inherited Create;
FName := SDDSFormatName;
FCanLoad := True;
FCanSave := True;
FIsMultiImageFormat := True;
FSupportedFormats := DDSSupportedFormats;
FSaveCubeMap := False;
FSaveVolume := False;
FSaveMipMapCount := 1;
FSaveDepth := 1;
AddMasks(SDDSMasks);
RegisterOption(ImagingDDSLoadedCubeMap, @FLoadedCubeMap);
RegisterOption(ImagingDDSLoadedVolume, @FLoadedVolume);
RegisterOption(ImagingDDSLoadedMipMapCount, @FLoadedMipMapCount);
RegisterOption(ImagingDDSLoadedDepth, @FLoadedDepth);
RegisterOption(ImagingDDSSaveCubeMap, @FSaveCubeMap);
RegisterOption(ImagingDDSSaveVolume, @FSaveVolume);
RegisterOption(ImagingDDSSaveMipMapCount, @FSaveMipMapCount);
RegisterOption(ImagingDDSSaveDepth, @FSaveDepth);
end;
procedure TDDSFileFormat.CheckOptionsValidity;
begin
if FSaveCubeMap then
FSaveVolume := False;
if FSaveVolume then
FSaveCubeMap := False;
if FSaveDepth < 1 then
FSaveDepth := 1;
if FSaveMipMapCount < 1 then
FSaveMipMapCount := 1;
end;
procedure TDDSFileFormat.ComputeSubDimensions(Idx, Width, Height, MipMaps, Depth: LongInt;
IsCubeMap, IsVolume: Boolean; var CurWidth, CurHeight: LongInt);
var
I, Last, Shift: LongInt;
begin
CurWidth := Width;
CurHeight := Height;
if MipMaps > 1 then
begin
if not IsVolume then
begin
if IsCubeMap then
begin
// Cube maps are stored like this
// Face 0 mimap 0
// Face 0 mipmap 1
// ...
// Face 1 mipmap 0
// Face 1 mipmap 1
// ...
// Modify index so later in for loop we iterate less times
Idx := Idx - ((Idx div MipMaps) * MipMaps);
end;
for I := 0 to Idx - 1 do
begin
CurWidth := ClampInt(CurWidth shr 1, 1, CurWidth);
CurHeight := ClampInt(CurHeight shr 1, 1, CurHeight);
end;
end
else
begin
// Volume textures are stored in DDS files like this:
// Slice 0 mipmap 0
// Slice 1 mipmap 0
// Slice 2 mipmap 0
// Slice 3 mipmap 0
// Slice 0 mipmap 1
// Slice 1 mipmap 1
// Slice 0 mipmap 2
// Slice 0 mipmap 3 ...
Shift := 0;
Last := Depth;
while Idx > Last - 1 do
begin
CurWidth := ClampInt(CurWidth shr 1, 1, CurWidth);
CurHeight := ClampInt(CurHeight shr 1, 1, CurHeight);
if (CurWidth = 1) and (CurHeight = 1) then
Break;
Inc(Shift);
Inc(Last, ClampInt(Depth shr Shift, 1, Depth));
end;
end;
end;
end;
function TDDSFileFormat.LoadData(Handle: TImagingHandle;
var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
var
Hdr: TDDSFileHeader;
SrcFormat: TImageFormat;
FmtInfo: TImageFormatInfo;
NeedsSwapChannels: Boolean;
CurrentWidth, CurrentHeight, ImageCount, LoadSize, I, PitchOrLinear: LongInt;
Data: PByte;
UseAsPitch: Boolean;
UseAsLinear: Boolean;
function MasksEqual(const DDPF: TDDPixelFormat; PF: PPixelFormatInfo): Boolean;
begin
Result := (DDPF.AlphaMask = PF.ABitMask) and
(DDPF.RedMask = PF.RBitMask) and (DDPF.GreenMask = PF.GBitMask) and
(DDPF.BlueMask = PF.BBitMask);
end;
begin
Result := False;
ImageCount := 1;
FLoadedMipMapCount := 1;
FLoadedDepth := 1;
FLoadedVolume := False;
FLoadedCubeMap := False;
with GetIO, Hdr, Hdr.Desc.PixelFormat do
begin
Read(Handle, @Hdr, SizeOF(Hdr));
{
// Set position to the end of the header (for possible future versions
// ith larger header)
Seek(Handle, Hdr.Desc.Size + SizeOf(Hdr.Magic) - SizeOf(Hdr),
smFromCurrent);
}
SrcFormat := ifUnknown;
NeedsSwapChannels := False;
// Get image data format
if (Flags and DDPF_FOURCC) = DDPF_FOURCC then
begin
// Handle FourCC and large ARGB formats
case FourCC of
D3DFMT_A16B16G16R16: SrcFormat := ifA16B16G16R16;
D3DFMT_R32F: SrcFormat := ifR32F;
D3DFMT_A32B32G32R32F: SrcFormat := ifA32B32G32R32F;
D3DFMT_R16F: SrcFormat := ifR16F;
D3DFMT_A16B16G16R16F: SrcFormat := ifA16B16G16R16F;
FOURCC_DXT1: SrcFormat := ifDXT1;
FOURCC_DXT3: SrcFormat := ifDXT3;
FOURCC_DXT5: SrcFormat := ifDXT5;
end;
end
else if (Flags and DDPF_RGB) = DDPF_RGB then
begin
// Handle RGB formats
if (Flags and DDPF_ALPHAPIXELS) = DDPF_ALPHAPIXELS then
begin
// Handle RGB with alpha formats
case BitCount of
16:
begin
if MasksEqual(Desc.PixelFormat,
GetFormatInfo(ifA4R4G4B4).PixelFormat) then
SrcFormat := ifA4R4G4B4;
if MasksEqual(Desc.PixelFormat,
GetFormatInfo(ifA1R5G5B5).PixelFormat) then
SrcFormat := ifA1R5G5B5;
end;
32:
begin
SrcFormat := ifA8R8G8B8;
if BlueMask = $00FF0000 then
NeedsSwapChannels := True;
end;
end;
end
else
begin
// Handle RGB without alpha formats
case BitCount of
8:
if MasksEqual(Desc.PixelFormat,
GetFormatInfo(ifR3G3B2).PixelFormat) then
SrcFormat := ifR3G3B2;
16:
begin
if MasksEqual(Desc.PixelFormat,
GetFormatInfo(ifX4R4G4B4).PixelFormat) then
SrcFormat := ifX4R4G4B4;
if MasksEqual(Desc.PixelFormat,
GetFormatInfo(ifX1R5G5B5).PixelFormat) then
SrcFormat := ifX1R5G5B5;
if MasksEqual(Desc.PixelFormat,
GetFormatInfo(ifR5G6B5).PixelFormat) then
SrcFormat := ifR5G6B5;
end;
24: SrcFormat := ifR8G8B8;
32:
begin
SrcFormat := ifX8R8G8B8;
if BlueMask = $00FF0000 then
NeedsSwapChannels := True;
end;
end;
end;
end
else if (Flags and DDPF_LUMINANCE) = DDPF_LUMINANCE then
begin
// Handle luminance formats
if (Flags and DDPF_ALPHAPIXELS) = DDPF_ALPHAPIXELS then
begin
// Handle luminance with alpha formats
if BitCount = 16 then
SrcFormat := ifA8Gray8;
end
else
begin
// Handle luminance without alpha formats
case BitCount of
8: SrcFormat := ifGray8;
16: SrcFormat := ifGray16;
end;
end;
end
else if (Flags and DDPF_BUMPLUMINANCE) = DDPF_BUMPLUMINANCE then
begin
// Handle mixed bump-luminance formats like D3DFMT_X8L8V8U8
case BitCount of
32:
if BlueMask = $00FF0000 then
begin
SrcFormat := ifX8R8G8B8; // D3DFMT_X8L8V8U8
NeedsSwapChannels := True;
end;
end;
end
else if (Flags and DDPF_BUMPDUDV) = DDPF_BUMPDUDV then
begin
// Handle bumpmap formats like D3DFMT_Q8W8V8U8
case BitCount of
16: SrcFormat := ifA8Gray8; // D3DFMT_V8U8
32:
if AlphaMask = $FF000000 then
begin
SrcFormat := ifA8R8G8B8; // D3DFMT_Q8W8V8U8
NeedsSwapChannels := True;
end;
64: SrcFormat := ifA16B16G16R16; // D3DFMT_Q16W16V16U16
end;
end;
// If DDS format is not supported we will exit
if SrcFormat = ifUnknown then Exit;
// File contains mipmaps for each subimage.
{ Some DDS writers ignore setting proper Caps and Flags so
this check is not usable:
if ((Desc.Caps.Caps1 and DDSCAPS_MIPMAP) = DDSCAPS_MIPMAP) and
((Desc.Flags and DDSD_MIPMAPCOUNT) = DDSD_MIPMAPCOUNT) then}
if Desc.MipMaps > 1 then
begin
FLoadedMipMapCount := Desc.MipMaps;
ImageCount := Desc.MipMaps;
end;
// File stores volume texture
if ((Desc.Caps.Caps2 and DDSCAPS2_VOLUME) = DDSCAPS2_VOLUME) and
((Desc.Flags and DDSD_DEPTH) = DDSD_DEPTH) then
begin
FLoadedVolume := True;
FLoadedDepth := Desc.Depth;
ImageCount := GetVolumeLevelCount(Desc.Depth, ImageCount);
end;
// File stores cube texture
if (Desc.Caps.Caps2 and DDSCAPS2_CUBEMAP) = DDSCAPS2_CUBEMAP then
begin
FLoadedCubeMap := True;
I := 0;
if (Desc.Caps.Caps2 and DDSCAPS2_POSITIVEX) = DDSCAPS2_POSITIVEX then Inc(I);
if (Desc.Caps.Caps2 and DDSCAPS2_POSITIVEY) = DDSCAPS2_POSITIVEY then Inc(I);
if (Desc.Caps.Caps2 and DDSCAPS2_POSITIVEZ) = DDSCAPS2_POSITIVEZ then Inc(I);
if (Desc.Caps.Caps2 and DDSCAPS2_NEGATIVEX) = DDSCAPS2_NEGATIVEX then Inc(I);
if (Desc.Caps.Caps2 and DDSCAPS2_NEGATIVEY) = DDSCAPS2_NEGATIVEY then Inc(I);
if (Desc.Caps.Caps2 and DDSCAPS2_NEGATIVEZ) = DDSCAPS2_NEGATIVEZ then Inc(I);
FLoadedDepth := I;
ImageCount := ImageCount * I;
end;
// Allocate and load all images in file
FmtInfo := GetFormatInfo(SrcFormat);
SetLength(Images, ImageCount);
// Compute the pitch or get if from file if present
UseAsPitch := (Desc.Flags and DDSD_PITCH) = DDSD_PITCH;
UseAsLinear := (Desc.Flags and DDSD_LINEARSIZE) = DDSD_LINEARSIZE;
// Use linear as default if none is set
if not UseAsPitch and not UseAsLinear then
UseAsLinear := True;
// Main image pitch or linear size
PitchOrLinear := Desc.PitchOrLinearSize;
for I := 0 to ImageCount - 1 do
begin
// Compute dimensions of surrent subimage based on texture type and
// number of mipmaps
ComputeSubDimensions(I, Desc.Width, Desc.Height, Desc.MipMaps, Desc.Depth,
FloadedCubeMap, FLoadedVolume, CurrentWidth, CurrentHeight);
NewImage(CurrentWidth, CurrentHeight, SrcFormat, Images[I]);
if (I > 0) or (PitchOrLinear = 0) then
begin
// Compute pitch or linear size for mipmap levels, or even for main image
// since some formats do not fill pitch nor size
if UseAsLinear then
PitchOrLinear := FmtInfo.GetPixelsSize(SrcFormat, CurrentWidth, CurrentHeight)
else
PitchOrLinear := (CurrentWidth * FmtInfo.BytesPerPixel + 3) div 4 * 4; // must be DWORD aligned
end;
if UseAsLinear then
LoadSize := PitchOrLinear
else
LoadSize := CurrentHeight * PitchOrLinear;
if UseAsLinear or (LoadSize = Images[I].Size) then
begin
// If DDS does not use Pitch we can simply copy data
Read(Handle, Images[I].Bits, LoadSize)
end
else
begin
// If DDS uses Pitch we must load aligned scanlines
// and then remove padding
GetMem(Data, LoadSize);
try
Read(Handle, Data, LoadSize);
RemovePadBytes(Data, Images[I].Bits, CurrentWidth, CurrentHeight,
FmtInfo.BytesPerPixel, PitchOrLinear);
finally
FreeMem(Data);
end;
end;
if NeedsSwapChannels then
SwapChannels(Images[I], ChannelRed, ChannelBlue);
end;
Result := True;
end;
end;
function TDDSFileFormat.SaveData(Handle: TImagingHandle;
const Images: TDynImageDataArray; Index: LongInt): Boolean;
var
Hdr: TDDSFileHeader;
MainImage, ImageToSave: TImageData;
I, MainIdx, Len, ImageCount: LongInt;
J: LongWord;
FmtInfo: TImageFormatInfo;
MustBeFreed: Boolean;
Is2DTexture, IsCubeMap, IsVolume: Boolean;
MipMapCount, CurrentWidth, CurrentHeight: LongInt;
NeedsResize: Boolean;
NeedsConvert: Boolean;
begin
Result := False;
FillChar(Hdr, Sizeof(Hdr), 0);
MainIdx := FFirstIdx;
Len := FLastIdx - MainIdx + 1;
// Some DDS saving rules:
// 2D textures: Len is used as mipmap count (FSaveMipMapCount not used!).
// Cube maps: FSaveDepth * FSaveMipMapCount images are used, if Len is
// smaller than this file is saved as regular 2D texture.
// Volume maps: GetVolumeLevelCount(FSaveDepth, FSaveMipMapCount) images are
// used, if Len is smaller than this file is
// saved as regular 2D texture.
IsCubeMap := FSaveCubeMap;
IsVolume := FSaveVolume;
MipMapCount := FSaveMipMapCount;
if IsCubeMap then
begin
// Check if we have enough images on Input to save cube map
if Len < FSaveDepth * FSaveMipMapCount then
IsCubeMap := False;
end
else if IsVolume then
begin
// Check if we have enough images on Input to save volume texture
if Len < GetVolumeLevelCount(FSaveDepth, FSaveMipMapCount) then
IsVolume := False;
end;
Is2DTexture := not IsCubeMap and not IsVolume;
if Is2DTexture then
begin
// Get number of mipmaps used with 2D texture
MipMapCount := Min(Len, GetNumMipMapLevels(Images[MainIdx].Width, Images[MainIdx].Height));
end;
// we create compatible main image and fill headers
if MakeCompatible(Images[MainIdx], MainImage, MustBeFreed) then
with GetIO, MainImage, Hdr do
try
FmtInfo := GetFormatInfo(Format);
Magic := DDSMagic;
Desc.Size := SizeOf(Desc);
Desc.Width := Width;
Desc.Height := Height;
Desc.Flags := DDS_SAVE_FLAGS;
Desc.Caps.Caps1 := DDSCAPS_TEXTURE;
Desc.PixelFormat.Size := SizeOf(Desc.PixelFormat);
Desc.PitchOrLinearSize := MainImage.Size;
ImageCount := MipMapCount;
if MipMapCount > 1 then
begin
// Set proper flags if we have some mipmaps to be saved
Desc.Flags := Desc.Flags or DDSD_MIPMAPCOUNT;
Desc.Caps.Caps1 := Desc.Caps.Caps1 or DDSCAPS_MIPMAP or DDSCAPS_COMPLEX;
Desc.MipMaps := MipMapCount;
end;
if IsCubeMap then
begin
// Set proper cube map flags - number of stored faces is taken
// from FSaveDepth
Desc.Caps.Caps1 := Desc.Caps.Caps1 or DDSCAPS_COMPLEX;
Desc.Caps.Caps2 := Desc.Caps.Caps2 or DDSCAPS2_CUBEMAP;
J := DDSCAPS2_POSITIVEX;
for I := 0 to FSaveDepth - 1 do
begin
Desc.Caps.Caps2 := Desc.Caps.Caps2 or J;
J := J shl 1;
end;
ImageCount := FSaveDepth * FSaveMipMapCount;
end
else if IsVolume then
begin
// Set proper flags for volume texture
Desc.Flags := Desc.Flags or DDSD_DEPTH;
Desc.Caps.Caps1 := Desc.Caps.Caps1 or DDSCAPS_COMPLEX;
Desc.Caps.Caps2 := Desc.Caps.Caps2 or DDSCAPS2_VOLUME;
Desc.Depth := FSaveDepth;
ImageCount := GetVolumeLevelCount(FSaveDepth, FSaveMipMapCount);
end;
// Now we set DDS pixel format for main image
if FmtInfo.IsSpecial or FmtInfo.IsFloatingPoint or
(FmtInfo.BytesPerPixel > 4) then
begin
Desc.PixelFormat.Flags := DDPF_FOURCC;
case Format of
ifA16B16G16R16: Desc.PixelFormat.FourCC := D3DFMT_A16B16G16R16;
ifR32F: Desc.PixelFormat.FourCC := D3DFMT_R32F;
ifA32B32G32R32F: Desc.PixelFormat.FourCC := D3DFMT_A32B32G32R32F;
ifR16F: Desc.PixelFormat.FourCC := D3DFMT_R16F;
ifA16B16G16R16F: Desc.PixelFormat.FourCC := D3DFMT_A16B16G16R16F;
ifDXT1: Desc.PixelFormat.FourCC := FOURCC_DXT1;
ifDXT3: Desc.PixelFormat.FourCC := FOURCC_DXT3;
ifDXT5: Desc.PixelFormat.FourCC := FOURCC_DXT5;
end;
end
else if FmtInfo.HasGrayChannel then
begin
Desc.PixelFormat.Flags := DDPF_LUMINANCE;
Desc.PixelFormat.BitCount := FmtInfo.BytesPerPixel * 8;
case Format of
ifGray8: Desc.PixelFormat.RedMask := 255;
ifGray16: Desc.PixelFormat.RedMask := 65535;
ifA8Gray8:
begin
Desc.PixelFormat.Flags := Desc.PixelFormat.Flags or DDPF_ALPHAPIXELS;
Desc.PixelFormat.RedMask := 255;
Desc.PixelFormat.AlphaMask := 65280;
end;
end;
end
else
begin
Desc.PixelFormat.Flags := DDPF_RGB;
Desc.PixelFormat.BitCount := FmtInfo.BytesPerPixel * 8;
if FmtInfo.HasAlphaChannel then
begin
Desc.PixelFormat.Flags := Desc.PixelFormat.Flags or DDPF_ALPHAPIXELS;
Desc.PixelFormat.AlphaMask := $FF000000;
end;
if FmtInfo.BytesPerPixel > 2 then
begin
Desc.PixelFormat.RedMask := $00FF0000;
Desc.PixelFormat.GreenMask := $0000FF00;
Desc.PixelFormat.BlueMask := $000000FF;
end
else
begin
Desc.PixelFormat.AlphaMask := FmtInfo.PixelFormat.ABitMask;
Desc.PixelFormat.RedMask := FmtInfo.PixelFormat.RBitMask;
Desc.PixelFormat.GreenMask := FmtInfo.PixelFormat.GBitMask;
Desc.PixelFormat.BlueMask := FmtInfo.PixelFormat.BBitMask;
end;
end;
// Header and main image are written to output
Write(Handle, @Hdr, SizeOf(Hdr));
Write(Handle, MainImage.Bits, MainImage.Size);
// Write the rest of the images and convert them to
// the same format as main image if necessary and ensure proper mipmap
// simensions too.
for I := MainIdx + 1 to MainIdx + ImageCount - 1 do
begin
// Get proper dimensions for this level
ComputeSubDimensions(I, Desc.Width, Desc.Height, Desc.MipMaps, Desc.Depth,
IsCubeMap, IsVolume, CurrentWidth, CurrentHeight);
// Check if input image for this level has the right size and format
NeedsResize := not ((Images[I].Width = CurrentWidth) and (Images[I].Height = CurrentHeight));
NeedsConvert := not (Images[I].Format = Format);
if NeedsResize or NeedsConvert then
begin
// Input image must be resized or converted to different format
// to become valid mipmap level
InitImage(ImageToSave);
CloneImage(Images[I], ImageToSave);
if NeedsConvert then
ConvertImage(ImageToSave, Format);
if NeedsResize then
ResizeImage(ImageToSave, CurrentWidth, CurrentHeight, rfBilinear);
end
else
// Input image can be used without any changes
ImageToSave := Images[I];
// Write level data and release temp image if necessary
Write(Handle, ImageToSave.Bits, ImageToSave.Size);
if Images[I].Bits <> ImageToSave.Bits then
FreeImage(ImageToSave);
end;
Result := True;
finally
if MustBeFreed then
FreeImage(MainImage);
end;
end;
procedure TDDSFileFormat.ConvertToSupported(var Image: TImageData;
const Info: TImageFormatInfo);
var
ConvFormat: TImageFormat;
begin
if Info.IsIndexed or Info.IsSpecial then
// convert indexed and unsupported special formatd to A8R8G8B8
ConvFormat := ifA8R8G8B8
else if Info.IsFloatingPoint then
begin
if Info.Format = ifA16R16G16B16F then
// only swap channels here
ConvFormat := ifA16B16G16R16F
else
// convert other floating point formats to A32B32G32R32F
ConvFormat := ifA32B32G32R32F
end
else if Info.HasGrayChannel then
begin
if Info.HasAlphaChannel then
// convert grayscale with alpha to A8Gray8
ConvFormat := ifA8Gray8
else if Info.BytesPerPixel = 1 then
// convert 8bit grayscale to Gray8
ConvFormat := ifGray8
else
// convert 16-64bit grayscales to Gray16
ConvFormat := ifGray16;
end
else if Info.BytesPerPixel > 4 then
ConvFormat := ifA16B16G16R16
else if Info.HasAlphaChannel then
// convert the other images with alpha channel to A8R8G8B8
ConvFormat := ifA8R8G8B8
else
// convert the other formats to X8R8G8B8
ConvFormat := ifX8R8G8B8;
ConvertImage(Image, ConvFormat);
end;
function TDDSFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
var
Hdr: TDDSFileHeader;
ReadCount: LongInt;
begin
Result := False;
if Handle <> nil then
with GetIO do
begin
ReadCount := Read(Handle, @Hdr, SizeOf(Hdr));
Seek(Handle, -ReadCount, smFromCurrent);
Result := (Hdr.Magic = DDSMagic) and (ReadCount = SizeOf(Hdr)) and
((Hdr.Desc.Caps.Caps1 and DDSCAPS_TEXTURE) = DDSCAPS_TEXTURE);
end;
end;
initialization
RegisterImageFileFormat(TDDSFileFormat);
{
File Notes:
-- TODOS ----------------------------------------------------
- nothing now
-- 0.23 Changes/Bug Fixes -----------------------------------
- Saved DDS with mipmaps now correctly defineds COMPLEX flag.
- Fixed loading of RGB DDS files that use pitch and have mipmaps -
mipmaps were loaded wrongly.
-- 0.21 Changes/Bug Fixes -----------------------------------
- Changed saving behaviour a bit: mipmaps are inlcuded automatically for
2D textures if input image array has more than 1 image (no need to
set SaveMipMapCount manually).
- Mipmap levels are now saved with proper dimensions when saving DDS files.
- Made some changes to not be so strict when loading DDS files.
Many programs seem to save them in non-standard format
(by MS DDS File Reference).
- Added missing ifX8R8G8B8 to SupportedFormats, MakeCompatible failed
when image was converted to this format (inside).
- MakeCompatible method moved to base class, put ConvertToSupported here.
GetSupportedFormats removed, it is now set in constructor.
- Fixed bug that sometimes saved non-standard DDS files and another
one that caused crash when these files were loaded.
- Changed extensions to filename masks.
- Changed SaveData, LoadData, and MakeCompatible methods according
to changes in base class in Imaging unit.
-- 0.19 Changes/Bug Fixes -----------------------------------
- added support for half-float image formats
- change in LoadData to allow support for more images
in one stream loading
-- 0.17 Changes/Bug Fixes -----------------------------------
- fixed bug in TestFormat which does not recognize many DDS files
- changed pitch/linearsize handling in DDS loading code to
load DDS files produced by NVidia's Photoshop plugin
}
end.