2 $Id: ImagingDds.pas 129 2008-08-06 20:01:30Z galfar $
3 Vampyre Imaging Library
5 http://imaginglib.sourceforge.net
7 The contents of this file are used with permission, subject to the Mozilla
8 Public License Version 1.1 (the "License"); you may not use this file except
9 in compliance with the License. You may obtain a copy of the License at
10 http://www.mozilla.org/MPL/MPL-1.1.html
12 Software distributed under the License is distributed on an "AS IS" basis,
13 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
14 the specific language governing rights and limitations under the License.
16 Alternatively, the contents of this file may be used under the terms of the
17 GNU Lesser General Public License (the "LGPL License"), in which case the
18 provisions of the LGPL License are applicable instead of those above.
19 If you wish to allow use of your version of this file only under the terms
20 of the LGPL License and not to allow others to use your version of this file
21 under the MPL, indicate your decision by deleting the provisions above and
22 replace them with the notice and other provisions required by the LGPL
23 License. If you do not delete the provisions above, a recipient may use
24 your version of this file under either the MPL or the LGPL License.
26 For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
29 { This unit contains image format loader/saver for DirectDraw Surface images.}
32 {$I ImagingOptions.inc}
37 ImagingTypes
, Imaging
, ImagingUtility
, ImagingFormats
;
40 { Class for loading and saving Microsoft DirectDraw surfaces.
41 It can load/save all D3D formats which have coresponding
42 TImageFormat. It supports plain textures, cube textures and
43 volume textures, all of these can have mipmaps. It can also
44 load some formats which have no exact TImageFormat, but can be easily
45 converted to one (bump map formats).
46 You can get some information about last loaded DDS file by calling
47 GetOption with ImagingDDSLoadedXXX options and you can set some
48 saving options by calling SetOption with ImagingDDSSaveXXX or you can
49 simply use properties of this class.
50 Note that when saving cube maps and volumes input image array must contain
51 at least number of images to build cube/volume based on current
52 Depth and MipMapCount settings.}
53 TDDSFileFormat
= class(TImageFileFormat
)
55 FLoadedCubeMap
: LongBool;
56 FLoadedVolume
: LongBool;
57 FLoadedMipMapCount
: LongInt;
58 FLoadedDepth
: LongInt;
59 FSaveCubeMap
: LongBool;
60 FSaveVolume
: LongBool;
61 FSaveMipMapCount
: LongInt;
63 procedure ComputeSubDimensions(Idx
, Width
, Height
, MipMaps
, Depth
: LongInt;
64 IsCubeMap
, IsVolume
: Boolean; var CurWidth
, CurHeight
: LongInt);
65 function LoadData(Handle
: TImagingHandle
; var Images
: TDynImageDataArray
;
66 OnlyFirstLevel
: Boolean): Boolean; override;
67 function SaveData(Handle
: TImagingHandle
; const Images
: TDynImageDataArray
;
68 Index
: LongInt): Boolean; override;
69 procedure ConvertToSupported(var Image
: TImageData
;
70 const Info
: TImageFormatInfo
); override;
72 constructor Create
; override;
73 function TestFormat(Handle
: TImagingHandle
): Boolean; override;
74 procedure CheckOptionsValidity
; override;
76 { True if last loaded DDS file was cube map.}
77 property LoadedCubeMap
: LongBool read FLoadedCubeMap write FLoadedCubeMap
;
78 { True if last loaded DDS file was volume texture.}
79 property LoadedVolume
: LongBool read FLoadedVolume write FLoadedVolume
;
80 { Number of mipmap levels of last loaded DDS image.}
81 property LoadedMipMapCount
: LongInt read FLoadedMipMapCount write FLoadedMipMapCount
;
82 { Depth (slices of volume texture or faces of cube map) of last loaded DDS image.}
83 property LoadedDepth
: LongInt read FLoadedDepth write FLoadedDepth
;
84 { True if next DDS file to be saved should be stored as cube map.}
85 property SaveCubeMap
: LongBool read FSaveCubeMap write FSaveCubeMap
;
86 { True if next DDS file to be saved should be stored as volume texture.}
87 property SaveVolume
: LongBool read FSaveVolume write FSaveVolume
;
88 { Sets the number of mipmaps which should be stored in the next saved DDS file.
89 Only applies to cube maps and volumes, ordinary 2D textures save all
90 levels present in input.}
91 property SaveMipMapCount
: LongInt read FSaveMipMapCount write FSaveMipMapCount
;
92 { Sets the depth (slices of volume texture or faces of cube map)
93 of the next saved DDS file.}
94 property SaveDepth
: LongInt read FSaveDepth write FSaveDepth
;
100 SDDSFormatName
= 'DirectDraw Surface';
102 DDSSupportedFormats
: TImageFormats
= [ifR8G8B8
, ifA8R8G8B8
, ifX8R8G8B8
,
103 ifA1R5G5B5
, ifA4R4G4B4
, ifX1R5G5B5
, ifX4R4G4B4
, ifR5G6B5
, ifA16B16G16R16
,
104 ifR32F
, ifA32B32G32R32F
, ifR16F
, ifA16B16G16R16F
, ifR3G3B2
, ifGray8
, ifA8Gray8
,
105 ifGray16
, ifDXT1
, ifDXT3
, ifDXT5
, ifATI1N
, ifATI2N
];
108 { Four character codes.}
109 DDSMagic
= LongWord(Byte('D') or (Byte('D') shl 8) or (Byte('S') shl 16) or
111 FOURCC_DXT1
= LongWord(Byte('D') or (Byte('X') shl 8) or (Byte('T') shl 16) or
113 FOURCC_DXT3
= LongWord(Byte('D') or (Byte('X') shl 8) or (Byte('T') shl 16) or
115 FOURCC_DXT5
= LongWord(Byte('D') or (Byte('X') shl 8) or (Byte('T') shl 16) or
117 FOURCC_ATI1
= LongWord(Byte('A') or (Byte('T') shl 8) or (Byte('I') shl 16) or
119 FOURCC_ATI2
= LongWord(Byte('A') or (Byte('T') shl 8) or (Byte('I') shl 16) or
122 { Some D3DFORMAT values used in DDS files as FourCC value.}
123 D3DFMT_A16B16G16R16
= 36;
125 D3DFMT_A32B32G32R32F
= 116;
127 D3DFMT_A16B16G16R16F
= 113;
129 { Constans used by TDDSurfaceDesc2.Flags.}
130 DDSD_CAPS
= $00000001;
131 DDSD_HEIGHT
= $00000002;
132 DDSD_WIDTH
= $00000004;
133 DDSD_PITCH
= $00000008;
134 DDSD_PIXELFORMAT
= $00001000;
135 DDSD_MIPMAPCOUNT
= $00020000;
136 DDSD_LINEARSIZE
= $00080000;
137 DDSD_DEPTH
= $00800000;
139 { Constans used by TDDSPixelFormat.Flags.}
140 DDPF_ALPHAPIXELS
= $00000001; // used by formats which contain alpha
141 DDPF_FOURCC
= $00000004; // used by DXT and large ARGB formats
142 DDPF_RGB
= $00000040; // used by RGB formats
143 DDPF_LUMINANCE
= $00020000; // used by formats like D3DFMT_L16
144 DDPF_BUMPLUMINANCE
= $00040000; // used by mixed signed-unsigned formats
145 DDPF_BUMPDUDV
= $00080000; // used by signed formats
147 { Constans used by TDDSCaps.Caps1.}
148 DDSCAPS_COMPLEX
= $00000008;
149 DDSCAPS_TEXTURE
= $00001000;
150 DDSCAPS_MIPMAP
= $00400000;
152 { Constans used by TDDSCaps.Caps2.}
153 DDSCAPS2_CUBEMAP
= $00000200;
154 DDSCAPS2_POSITIVEX
= $00000400;
155 DDSCAPS2_NEGATIVEX
= $00000800;
156 DDSCAPS2_POSITIVEY
= $00001000;
157 DDSCAPS2_NEGATIVEY
= $00002000;
158 DDSCAPS2_POSITIVEZ
= $00004000;
159 DDSCAPS2_NEGATIVEZ
= $00008000;
160 DDSCAPS2_VOLUME
= $00200000;
162 { Flags for TDDSurfaceDesc2.Flags used when saving DDS file.}
163 DDS_SAVE_FLAGS
= DDSD_CAPS
or DDSD_PIXELFORMAT
or DDSD_WIDTH
or
164 DDSD_HEIGHT
or DDSD_LINEARSIZE
;
167 { Stores the pixel format information.}
168 TDDPixelFormat
= packed record
169 Size
: LongWord; // Size of the structure = 32 bytes
170 Flags
: LongWord; // Flags to indicate valid fields
171 FourCC
: LongWord; // Four-char code for compressed textures (DXT)
172 BitCount
: LongWord; // Bits per pixel if uncomp. usually 16,24 or 32
173 RedMask
: LongWord; // Bit mask for the Red component
174 GreenMask
: LongWord; // Bit mask for the Green component
175 BlueMask
: LongWord; // Bit mask for the Blue component
176 AlphaMask
: LongWord; // Bit mask for the Alpha component
179 { Specifies capabilities of surface.}
180 TDDSCaps
= packed record
181 Caps1
: LongWord; // Should always include DDSCAPS_TEXTURE
182 Caps2
: LongWord; // For cubic environment maps
183 Reserved
: array[0..1] of LongWord; // Reserved
186 { Record describing DDS file contents.}
187 TDDSurfaceDesc2
= packed record
188 Size
: LongWord; // Size of the structure = 124 Bytes
189 Flags
: LongWord; // Flags to indicate valid fields
190 Height
: LongWord; // Height of the main image in pixels
191 Width
: LongWord; // Width of the main image in pixels
192 PitchOrLinearSize
: LongWord; // For uncomp formats number of bytes per
193 // scanline. For comp it is the size in
194 // bytes of the main image
195 Depth
: LongWord; // Only for volume text depth of the volume
196 MipMaps
: LongInt; // Total number of levels in the mipmap chain
197 Reserved1
: array[0..10] of LongWord; // Reserved
198 PixelFormat
: TDDPixelFormat
; // Format of the pixel data
199 Caps
: TDDSCaps
; // Capabilities
200 Reserved2
: LongWord; // Reserved
204 TDDSFileHeader
= packed record
205 Magic
: LongWord; // File format magic
206 Desc
: TDDSurfaceDesc2
; // Surface description
210 { TDDSFileFormat class implementation }
212 constructor TDDSFileFormat
.Create
;
215 FName
:= SDDSFormatName
;
218 FIsMultiImageFormat
:= True;
219 FSupportedFormats
:= DDSSupportedFormats
;
221 FSaveCubeMap
:= False;
222 FSaveVolume
:= False;
223 FSaveMipMapCount
:= 1;
228 RegisterOption(ImagingDDSLoadedCubeMap
, @FLoadedCubeMap
);
229 RegisterOption(ImagingDDSLoadedVolume
, @FLoadedVolume
);
230 RegisterOption(ImagingDDSLoadedMipMapCount
, @FLoadedMipMapCount
);
231 RegisterOption(ImagingDDSLoadedDepth
, @FLoadedDepth
);
232 RegisterOption(ImagingDDSSaveCubeMap
, @FSaveCubeMap
);
233 RegisterOption(ImagingDDSSaveVolume
, @FSaveVolume
);
234 RegisterOption(ImagingDDSSaveMipMapCount
, @FSaveMipMapCount
);
235 RegisterOption(ImagingDDSSaveDepth
, @FSaveDepth
);
238 procedure TDDSFileFormat
.CheckOptionsValidity
;
241 FSaveVolume
:= False;
243 FSaveCubeMap
:= False;
244 if FSaveDepth
< 1 then
246 if FSaveMipMapCount
< 1 then
247 FSaveMipMapCount
:= 1;
250 procedure TDDSFileFormat
.ComputeSubDimensions(Idx
, Width
, Height
, MipMaps
, Depth
: LongInt;
251 IsCubeMap
, IsVolume
: Boolean; var CurWidth
, CurHeight
: LongInt);
253 I
, Last
, Shift
: LongInt;
263 // Cube maps are stored like this
271 // Modify index so later in for loop we iterate less times
272 Idx
:= Idx
- ((Idx
div MipMaps
) * MipMaps
);
274 for I
:= 0 to Idx
- 1 do
276 CurWidth
:= ClampInt(CurWidth
shr 1, 1, CurWidth
);
277 CurHeight
:= ClampInt(CurHeight
shr 1, 1, CurHeight
);
282 // Volume textures are stored in DDS files like this:
290 // Slice 0 mipmap 3 ...
293 while Idx
> Last
- 1 do
295 CurWidth
:= ClampInt(CurWidth
shr 1, 1, CurWidth
);
296 CurHeight
:= ClampInt(CurHeight
shr 1, 1, CurHeight
);
297 if (CurWidth
= 1) and (CurHeight
= 1) then
300 Inc(Last
, ClampInt(Depth
shr Shift
, 1, Depth
));
306 function TDDSFileFormat
.LoadData(Handle
: TImagingHandle
;
307 var Images
: TDynImageDataArray
; OnlyFirstLevel
: Boolean): Boolean;
310 SrcFormat
: TImageFormat
;
311 FmtInfo
: TImageFormatInfo
;
312 NeedsSwapChannels
: Boolean;
313 CurrentWidth
, CurrentHeight
, ImageCount
, LoadSize
, I
, PitchOrLinear
: LongInt;
316 UseAsLinear
: Boolean;
318 function MasksEqual(const DDPF
: TDDPixelFormat
; PF
: PPixelFormatInfo
): Boolean;
320 Result
:= (DDPF
.AlphaMask
= PF
.ABitMask
) and
321 (DDPF
.RedMask
= PF
.RBitMask
) and (DDPF
.GreenMask
= PF
.GBitMask
) and
322 (DDPF
.BlueMask
= PF
.BBitMask
);
328 FLoadedMipMapCount
:= 1;
330 FLoadedVolume
:= False;
331 FLoadedCubeMap
:= False;
333 with GetIO
, Hdr
, Hdr
.Desc
.PixelFormat
do
335 Read(Handle
, @Hdr
, SizeOF(Hdr
));
337 // Set position to the end of the header (for possible future versions
338 // ith larger header)
339 Seek(Handle, Hdr.Desc.Size + SizeOf(Hdr.Magic) - SizeOf(Hdr),
342 SrcFormat
:= ifUnknown
;
343 NeedsSwapChannels
:= False;
344 // Get image data format
345 if (Flags
and DDPF_FOURCC
) = DDPF_FOURCC
then
347 // Handle FourCC and large ARGB formats
349 D3DFMT_A16B16G16R16
: SrcFormat
:= ifA16B16G16R16
;
350 D3DFMT_R32F
: SrcFormat
:= ifR32F
;
351 D3DFMT_A32B32G32R32F
: SrcFormat
:= ifA32B32G32R32F
;
352 D3DFMT_R16F
: SrcFormat
:= ifR16F
;
353 D3DFMT_A16B16G16R16F
: SrcFormat
:= ifA16B16G16R16F
;
354 FOURCC_DXT1
: SrcFormat
:= ifDXT1
;
355 FOURCC_DXT3
: SrcFormat
:= ifDXT3
;
356 FOURCC_DXT5
: SrcFormat
:= ifDXT5
;
357 FOURCC_ATI1
: SrcFormat
:= ifATI1N
;
358 FOURCC_ATI2
: SrcFormat
:= ifATI2N
;
361 else if (Flags
and DDPF_RGB
) = DDPF_RGB
then
363 // Handle RGB formats
364 if (Flags
and DDPF_ALPHAPIXELS
) = DDPF_ALPHAPIXELS
then
366 // Handle RGB with alpha formats
370 if MasksEqual(Desc
.PixelFormat
,
371 GetFormatInfo(ifA4R4G4B4
).PixelFormat
) then
372 SrcFormat
:= ifA4R4G4B4
;
373 if MasksEqual(Desc
.PixelFormat
,
374 GetFormatInfo(ifA1R5G5B5
).PixelFormat
) then
375 SrcFormat
:= ifA1R5G5B5
;
379 SrcFormat
:= ifA8R8G8B8
;
380 if BlueMask
= $00FF0000 then
381 NeedsSwapChannels
:= True;
387 // Handle RGB without alpha formats
390 if MasksEqual(Desc
.PixelFormat
,
391 GetFormatInfo(ifR3G3B2
).PixelFormat
) then
392 SrcFormat
:= ifR3G3B2
;
395 if MasksEqual(Desc
.PixelFormat
,
396 GetFormatInfo(ifX4R4G4B4
).PixelFormat
) then
397 SrcFormat
:= ifX4R4G4B4
;
398 if MasksEqual(Desc
.PixelFormat
,
399 GetFormatInfo(ifX1R5G5B5
).PixelFormat
) then
400 SrcFormat
:= ifX1R5G5B5
;
401 if MasksEqual(Desc
.PixelFormat
,
402 GetFormatInfo(ifR5G6B5
).PixelFormat
) then
403 SrcFormat
:= ifR5G6B5
;
405 24: SrcFormat
:= ifR8G8B8
;
408 SrcFormat
:= ifX8R8G8B8
;
409 if BlueMask
= $00FF0000 then
410 NeedsSwapChannels
:= True;
415 else if (Flags
and DDPF_LUMINANCE
) = DDPF_LUMINANCE
then
417 // Handle luminance formats
418 if (Flags
and DDPF_ALPHAPIXELS
) = DDPF_ALPHAPIXELS
then
420 // Handle luminance with alpha formats
421 if BitCount
= 16 then
422 SrcFormat
:= ifA8Gray8
;
426 // Handle luminance without alpha formats
428 8: SrcFormat
:= ifGray8
;
429 16: SrcFormat
:= ifGray16
;
433 else if (Flags
and DDPF_BUMPLUMINANCE
) = DDPF_BUMPLUMINANCE
then
435 // Handle mixed bump-luminance formats like D3DFMT_X8L8V8U8
438 if BlueMask
= $00FF0000 then
440 SrcFormat
:= ifX8R8G8B8
; // D3DFMT_X8L8V8U8
441 NeedsSwapChannels
:= True;
445 else if (Flags
and DDPF_BUMPDUDV
) = DDPF_BUMPDUDV
then
447 // Handle bumpmap formats like D3DFMT_Q8W8V8U8
449 16: SrcFormat
:= ifA8Gray8
; // D3DFMT_V8U8
451 if AlphaMask
= $FF000000 then
453 SrcFormat
:= ifA8R8G8B8
; // D3DFMT_Q8W8V8U8
454 NeedsSwapChannels
:= True;
456 64: SrcFormat
:= ifA16B16G16R16
; // D3DFMT_Q16W16V16U16
460 // If DDS format is not supported we will exit
461 if SrcFormat
= ifUnknown
then Exit
;
463 // File contains mipmaps for each subimage.
464 { Some DDS writers ignore setting proper Caps and Flags so
465 this check is not usable:
466 if ((Desc.Caps.Caps1 and DDSCAPS_MIPMAP) = DDSCAPS_MIPMAP) and
467 ((Desc.Flags and DDSD_MIPMAPCOUNT) = DDSD_MIPMAPCOUNT) then}
468 if Desc
.MipMaps
> 1 then
470 FLoadedMipMapCount
:= Desc
.MipMaps
;
471 ImageCount
:= Desc
.MipMaps
;
474 // File stores volume texture
475 if ((Desc
.Caps
.Caps2
and DDSCAPS2_VOLUME
) = DDSCAPS2_VOLUME
) and
476 ((Desc
.Flags
and DDSD_DEPTH
) = DDSD_DEPTH
) then
478 FLoadedVolume
:= True;
479 FLoadedDepth
:= Desc
.Depth
;
480 ImageCount
:= GetVolumeLevelCount(Desc
.Depth
, ImageCount
);
483 // File stores cube texture
484 if (Desc
.Caps
.Caps2
and DDSCAPS2_CUBEMAP
) = DDSCAPS2_CUBEMAP
then
486 FLoadedCubeMap
:= True;
488 if (Desc
.Caps
.Caps2
and DDSCAPS2_POSITIVEX
) = DDSCAPS2_POSITIVEX
then Inc(I
);
489 if (Desc
.Caps
.Caps2
and DDSCAPS2_POSITIVEY
) = DDSCAPS2_POSITIVEY
then Inc(I
);
490 if (Desc
.Caps
.Caps2
and DDSCAPS2_POSITIVEZ
) = DDSCAPS2_POSITIVEZ
then Inc(I
);
491 if (Desc
.Caps
.Caps2
and DDSCAPS2_NEGATIVEX
) = DDSCAPS2_NEGATIVEX
then Inc(I
);
492 if (Desc
.Caps
.Caps2
and DDSCAPS2_NEGATIVEY
) = DDSCAPS2_NEGATIVEY
then Inc(I
);
493 if (Desc
.Caps
.Caps2
and DDSCAPS2_NEGATIVEZ
) = DDSCAPS2_NEGATIVEZ
then Inc(I
);
495 ImageCount
:= ImageCount
* I
;
498 // Allocate and load all images in file
499 FmtInfo
:= GetFormatInfo(SrcFormat
);
500 SetLength(Images
, ImageCount
);
502 // Compute the pitch or get if from file if present
503 UseAsPitch
:= (Desc
.Flags
and DDSD_PITCH
) = DDSD_PITCH
;
504 UseAsLinear
:= (Desc
.Flags
and DDSD_LINEARSIZE
) = DDSD_LINEARSIZE
;
505 // Use linear as default if none is set
506 if not UseAsPitch
and not UseAsLinear
then
508 // Main image pitch or linear size
509 PitchOrLinear
:= Desc
.PitchOrLinearSize
;
511 for I
:= 0 to ImageCount
- 1 do
513 // Compute dimensions of surrent subimage based on texture type and
515 ComputeSubDimensions(I
, Desc
.Width
, Desc
.Height
, Desc
.MipMaps
, Desc
.Depth
,
516 FloadedCubeMap
, FLoadedVolume
, CurrentWidth
, CurrentHeight
);
517 NewImage(CurrentWidth
, CurrentHeight
, SrcFormat
, Images
[I
]);
519 if (I
> 0) or (PitchOrLinear
= 0) then
521 // Compute pitch or linear size for mipmap levels, or even for main image
522 // since some formats do not fill pitch nor size
524 PitchOrLinear
:= FmtInfo
.GetPixelsSize(SrcFormat
, CurrentWidth
, CurrentHeight
)
526 PitchOrLinear
:= (CurrentWidth
* FmtInfo
.BytesPerPixel
+ 3) div 4 * 4; // must be DWORD aligned
530 LoadSize
:= PitchOrLinear
532 LoadSize
:= CurrentHeight
* PitchOrLinear
;
534 if UseAsLinear
or (LoadSize
= Images
[I
].Size
) then
536 // If DDS does not use Pitch we can simply copy data
537 Read(Handle
, Images
[I
].Bits
, LoadSize
)
541 // If DDS uses Pitch we must load aligned scanlines
542 // and then remove padding
543 GetMem(Data
, LoadSize
);
545 Read(Handle
, Data
, LoadSize
);
546 RemovePadBytes(Data
, Images
[I
].Bits
, CurrentWidth
, CurrentHeight
,
547 FmtInfo
.BytesPerPixel
, PitchOrLinear
);
553 if NeedsSwapChannels
then
554 SwapChannels(Images
[I
], ChannelRed
, ChannelBlue
);
560 function TDDSFileFormat
.SaveData(Handle
: TImagingHandle
;
561 const Images
: TDynImageDataArray
; Index
: LongInt): Boolean;
564 MainImage
, ImageToSave
: TImageData
;
565 I
, MainIdx
, Len
, ImageCount
: LongInt;
567 FmtInfo
: TImageFormatInfo
;
568 MustBeFreed
: Boolean;
569 Is2DTexture
, IsCubeMap
, IsVolume
: Boolean;
570 MipMapCount
, CurrentWidth
, CurrentHeight
: LongInt;
571 NeedsResize
: Boolean;
572 NeedsConvert
: Boolean;
575 FillChar(Hdr
, Sizeof(Hdr
), 0);
577 MainIdx
:= FFirstIdx
;
578 Len
:= FLastIdx
- MainIdx
+ 1;
579 // Some DDS saving rules:
580 // 2D textures: Len is used as mipmap count (FSaveMipMapCount not used!).
581 // Cube maps: FSaveDepth * FSaveMipMapCount images are used, if Len is
582 // smaller than this file is saved as regular 2D texture.
583 // Volume maps: GetVolumeLevelCount(FSaveDepth, FSaveMipMapCount) images are
584 // used, if Len is smaller than this file is
585 // saved as regular 2D texture.
587 IsCubeMap
:= FSaveCubeMap
;
588 IsVolume
:= FSaveVolume
;
589 MipMapCount
:= FSaveMipMapCount
;
593 // Check if we have enough images on Input to save cube map
594 if Len
< FSaveDepth
* FSaveMipMapCount
then
597 else if IsVolume
then
599 // Check if we have enough images on Input to save volume texture
600 if Len
< GetVolumeLevelCount(FSaveDepth
, FSaveMipMapCount
) then
604 Is2DTexture
:= not IsCubeMap
and not IsVolume
;
607 // Get number of mipmaps used with 2D texture
608 MipMapCount
:= Min(Len
, GetNumMipMapLevels(Images
[MainIdx
].Width
, Images
[MainIdx
].Height
));
611 // we create compatible main image and fill headers
612 if MakeCompatible(Images
[MainIdx
], MainImage
, MustBeFreed
) then
613 with GetIO
, MainImage
, Hdr
do
615 FmtInfo
:= GetFormatInfo(Format
);
617 Desc
.Size
:= SizeOf(Desc
);
619 Desc
.Height
:= Height
;
620 Desc
.Flags
:= DDS_SAVE_FLAGS
;
621 Desc
.Caps
.Caps1
:= DDSCAPS_TEXTURE
;
622 Desc
.PixelFormat
.Size
:= SizeOf(Desc
.PixelFormat
);
623 Desc
.PitchOrLinearSize
:= MainImage
.Size
;
624 ImageCount
:= MipMapCount
;
626 if MipMapCount
> 1 then
628 // Set proper flags if we have some mipmaps to be saved
629 Desc
.Flags
:= Desc
.Flags
or DDSD_MIPMAPCOUNT
;
630 Desc
.Caps
.Caps1
:= Desc
.Caps
.Caps1
or DDSCAPS_MIPMAP
or DDSCAPS_COMPLEX
;
631 Desc
.MipMaps
:= MipMapCount
;
636 // Set proper cube map flags - number of stored faces is taken
638 Desc
.Caps
.Caps1
:= Desc
.Caps
.Caps1
or DDSCAPS_COMPLEX
;
639 Desc
.Caps
.Caps2
:= Desc
.Caps
.Caps2
or DDSCAPS2_CUBEMAP
;
640 J
:= DDSCAPS2_POSITIVEX
;
641 for I
:= 0 to FSaveDepth
- 1 do
643 Desc
.Caps
.Caps2
:= Desc
.Caps
.Caps2
or J
;
646 ImageCount
:= FSaveDepth
* FSaveMipMapCount
;
648 else if IsVolume
then
650 // Set proper flags for volume texture
651 Desc
.Flags
:= Desc
.Flags
or DDSD_DEPTH
;
652 Desc
.Caps
.Caps1
:= Desc
.Caps
.Caps1
or DDSCAPS_COMPLEX
;
653 Desc
.Caps
.Caps2
:= Desc
.Caps
.Caps2
or DDSCAPS2_VOLUME
;
654 Desc
.Depth
:= FSaveDepth
;
655 ImageCount
:= GetVolumeLevelCount(FSaveDepth
, FSaveMipMapCount
);
658 // Now we set DDS pixel format for main image
659 if FmtInfo
.IsSpecial
or FmtInfo
.IsFloatingPoint
or
660 (FmtInfo
.BytesPerPixel
> 4) then
662 Desc
.PixelFormat
.Flags
:= DDPF_FOURCC
;
664 ifA16B16G16R16
: Desc
.PixelFormat
.FourCC
:= D3DFMT_A16B16G16R16
;
665 ifR32F
: Desc
.PixelFormat
.FourCC
:= D3DFMT_R32F
;
666 ifA32B32G32R32F
: Desc
.PixelFormat
.FourCC
:= D3DFMT_A32B32G32R32F
;
667 ifR16F
: Desc
.PixelFormat
.FourCC
:= D3DFMT_R16F
;
668 ifA16B16G16R16F
: Desc
.PixelFormat
.FourCC
:= D3DFMT_A16B16G16R16F
;
669 ifDXT1
: Desc
.PixelFormat
.FourCC
:= FOURCC_DXT1
;
670 ifDXT3
: Desc
.PixelFormat
.FourCC
:= FOURCC_DXT3
;
671 ifDXT5
: Desc
.PixelFormat
.FourCC
:= FOURCC_DXT5
;
672 ifATI1N
: Desc
.PixelFormat
.FourCC
:= FOURCC_ATI1
;
673 ifATI2N
: Desc
.PixelFormat
.FourCC
:= FOURCC_ATI2
;
676 else if FmtInfo
.HasGrayChannel
then
678 Desc
.PixelFormat
.Flags
:= DDPF_LUMINANCE
;
679 Desc
.PixelFormat
.BitCount
:= FmtInfo
.BytesPerPixel
* 8;
681 ifGray8
: Desc
.PixelFormat
.RedMask
:= 255;
682 ifGray16
: Desc
.PixelFormat
.RedMask
:= 65535;
685 Desc
.PixelFormat
.Flags
:= Desc
.PixelFormat
.Flags
or DDPF_ALPHAPIXELS
;
686 Desc
.PixelFormat
.RedMask
:= 255;
687 Desc
.PixelFormat
.AlphaMask
:= 65280;
693 Desc
.PixelFormat
.Flags
:= DDPF_RGB
;
694 Desc
.PixelFormat
.BitCount
:= FmtInfo
.BytesPerPixel
* 8;
695 if FmtInfo
.HasAlphaChannel
then
697 Desc
.PixelFormat
.Flags
:= Desc
.PixelFormat
.Flags
or DDPF_ALPHAPIXELS
;
698 Desc
.PixelFormat
.AlphaMask
:= $FF000000;
700 if FmtInfo
.BytesPerPixel
> 2 then
702 Desc
.PixelFormat
.RedMask
:= $00FF0000;
703 Desc
.PixelFormat
.GreenMask
:= $0000FF00;
704 Desc
.PixelFormat
.BlueMask
:= $000000FF;
708 Desc
.PixelFormat
.AlphaMask
:= FmtInfo
.PixelFormat
.ABitMask
;
709 Desc
.PixelFormat
.RedMask
:= FmtInfo
.PixelFormat
.RBitMask
;
710 Desc
.PixelFormat
.GreenMask
:= FmtInfo
.PixelFormat
.GBitMask
;
711 Desc
.PixelFormat
.BlueMask
:= FmtInfo
.PixelFormat
.BBitMask
;
715 // Header and main image are written to output
716 Write(Handle
, @Hdr
, SizeOf(Hdr
));
717 Write(Handle
, MainImage
.Bits
, MainImage
.Size
);
719 // Write the rest of the images and convert them to
720 // the same format as main image if necessary and ensure proper mipmap
722 for I
:= MainIdx
+ 1 to MainIdx
+ ImageCount
- 1 do
724 // Get proper dimensions for this level
725 ComputeSubDimensions(I
, Desc
.Width
, Desc
.Height
, Desc
.MipMaps
, Desc
.Depth
,
726 IsCubeMap
, IsVolume
, CurrentWidth
, CurrentHeight
);
728 // Check if input image for this level has the right size and format
729 NeedsResize
:= not ((Images
[I
].Width
= CurrentWidth
) and (Images
[I
].Height
= CurrentHeight
));
730 NeedsConvert
:= not (Images
[I
].Format
= Format
);
732 if NeedsResize
or NeedsConvert
then
734 // Input image must be resized or converted to different format
735 // to become valid mipmap level
736 InitImage(ImageToSave
);
737 CloneImage(Images
[I
], ImageToSave
);
739 ConvertImage(ImageToSave
, Format
);
741 ResizeImage(ImageToSave
, CurrentWidth
, CurrentHeight
, rfBilinear
);
744 // Input image can be used without any changes
745 ImageToSave
:= Images
[I
];
747 // Write level data and release temp image if necessary
748 Write(Handle
, ImageToSave
.Bits
, ImageToSave
.Size
);
749 if Images
[I
].Bits
<> ImageToSave
.Bits
then
750 FreeImage(ImageToSave
);
756 FreeImage(MainImage
);
760 procedure TDDSFileFormat
.ConvertToSupported(var Image
: TImageData
;
761 const Info
: TImageFormatInfo
);
763 ConvFormat
: TImageFormat
;
765 if Info
.IsIndexed
or Info
.IsSpecial
then
766 // convert indexed and unsupported special formatd to A8R8G8B8
767 ConvFormat
:= ifA8R8G8B8
768 else if Info
.IsFloatingPoint
then
770 if Info
.Format
= ifA16R16G16B16F
then
771 // only swap channels here
772 ConvFormat
:= ifA16B16G16R16F
774 // convert other floating point formats to A32B32G32R32F
775 ConvFormat
:= ifA32B32G32R32F
777 else if Info
.HasGrayChannel
then
779 if Info
.HasAlphaChannel
then
780 // convert grayscale with alpha to A8Gray8
781 ConvFormat
:= ifA8Gray8
782 else if Info
.BytesPerPixel
= 1 then
783 // convert 8bit grayscale to Gray8
784 ConvFormat
:= ifGray8
786 // convert 16-64bit grayscales to Gray16
787 ConvFormat
:= ifGray16
;
789 else if Info
.BytesPerPixel
> 4 then
790 ConvFormat
:= ifA16B16G16R16
791 else if Info
.HasAlphaChannel
then
792 // convert the other images with alpha channel to A8R8G8B8
793 ConvFormat
:= ifA8R8G8B8
795 // convert the other formats to X8R8G8B8
796 ConvFormat
:= ifX8R8G8B8
;
798 ConvertImage(Image
, ConvFormat
);
801 function TDDSFileFormat
.TestFormat(Handle
: TImagingHandle
): Boolean;
807 if Handle
<> nil then
810 ReadCount
:= Read(Handle
, @Hdr
, SizeOf(Hdr
));
811 Seek(Handle
, -ReadCount
, smFromCurrent
);
812 Result
:= (Hdr
.Magic
= DDSMagic
) and (ReadCount
= SizeOf(Hdr
)) and
813 ((Hdr
.Desc
.Caps
.Caps1
and DDSCAPS_TEXTURE
) = DDSCAPS_TEXTURE
);
818 RegisterImageFileFormat(TDDSFileFormat
);
823 -- TODOS ----------------------------------------------------
826 -- 0.25.0 Changes/Bug Fixes ---------------------------------
827 - Added support for 3Dc ATI1/2 formats.
829 -- 0.23 Changes/Bug Fixes -----------------------------------
830 - Saved DDS with mipmaps now correctly defineds COMPLEX flag.
831 - Fixed loading of RGB DDS files that use pitch and have mipmaps -
832 mipmaps were loaded wrongly.
834 -- 0.21 Changes/Bug Fixes -----------------------------------
835 - Changed saving behaviour a bit: mipmaps are inlcuded automatically for
836 2D textures if input image array has more than 1 image (no need to
837 set SaveMipMapCount manually).
838 - Mipmap levels are now saved with proper dimensions when saving DDS files.
839 - Made some changes to not be so strict when loading DDS files.
840 Many programs seem to save them in non-standard format
841 (by MS DDS File Reference).
842 - Added missing ifX8R8G8B8 to SupportedFormats, MakeCompatible failed
843 when image was converted to this format (inside).
844 - MakeCompatible method moved to base class, put ConvertToSupported here.
845 GetSupportedFormats removed, it is now set in constructor.
846 - Fixed bug that sometimes saved non-standard DDS files and another
847 one that caused crash when these files were loaded.
848 - Changed extensions to filename masks.
849 - Changed SaveData, LoadData, and MakeCompatible methods according
850 to changes in base class in Imaging unit.
852 -- 0.19 Changes/Bug Fixes -----------------------------------
853 - added support for half-float image formats
854 - change in LoadData to allow support for more images
855 in one stream loading
857 -- 0.17 Changes/Bug Fixes -----------------------------------
858 - fixed bug in TestFormat which does not recognize many DDS files
859 - changed pitch/linearsize handling in DDS loading code to
860 load DDS files produced by NVidia's Photoshop plugin