DEADSOFTWARE

added Vampyre Imaging Library; now textures can be in various formats, including...
[d2df-sdl.git] / src / lib / vampimg / ImagingDds.pas
1 {
2 $Id: ImagingDds.pas 129 2008-08-06 20:01:30Z galfar $
3 Vampyre Imaging Library
4 by Marek Mauder
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
27 }
29 { This unit contains image format loader/saver for DirectDraw Surface images.}
30 unit ImagingDds;
32 {$I ImagingOptions.inc}
34 interface
36 uses
37 ImagingTypes, Imaging, ImagingUtility, ImagingFormats;
39 type
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)
54 protected
55 FLoadedCubeMap: LongBool;
56 FLoadedVolume: LongBool;
57 FLoadedMipMapCount: LongInt;
58 FLoadedDepth: LongInt;
59 FSaveCubeMap: LongBool;
60 FSaveVolume: LongBool;
61 FSaveMipMapCount: LongInt;
62 FSaveDepth: 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;
71 public
72 constructor Create; override;
73 function TestFormat(Handle: TImagingHandle): Boolean; override;
74 procedure CheckOptionsValidity; override;
75 published
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;
95 end;
97 implementation
99 const
100 SDDSFormatName = 'DirectDraw Surface';
101 SDDSMasks = '*.dds';
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];
107 const
108 { Four character codes.}
109 DDSMagic = LongWord(Byte('D') or (Byte('D') shl 8) or (Byte('S') shl 16) or
110 (Byte(' ') shl 24));
111 FOURCC_DXT1 = LongWord(Byte('D') or (Byte('X') shl 8) or (Byte('T') shl 16) or
112 (Byte('1') shl 24));
113 FOURCC_DXT3 = LongWord(Byte('D') or (Byte('X') shl 8) or (Byte('T') shl 16) or
114 (Byte('3') shl 24));
115 FOURCC_DXT5 = LongWord(Byte('D') or (Byte('X') shl 8) or (Byte('T') shl 16) or
116 (Byte('5') shl 24));
117 FOURCC_ATI1 = LongWord(Byte('A') or (Byte('T') shl 8) or (Byte('I') shl 16) or
118 (Byte('1') shl 24));
119 FOURCC_ATI2 = LongWord(Byte('A') or (Byte('T') shl 8) or (Byte('I') shl 16) or
120 (Byte('2') shl 24));
122 { Some D3DFORMAT values used in DDS files as FourCC value.}
123 D3DFMT_A16B16G16R16 = 36;
124 D3DFMT_R32F = 114;
125 D3DFMT_A32B32G32R32F = 116;
126 D3DFMT_R16F = 111;
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;
166 type
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
177 end;
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
184 end;
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
201 end;
203 { DDS file header.}
204 TDDSFileHeader = packed record
205 Magic: LongWord; // File format magic
206 Desc: TDDSurfaceDesc2; // Surface description
207 end;
210 { TDDSFileFormat class implementation }
212 constructor TDDSFileFormat.Create;
213 begin
214 inherited Create;
215 FName := SDDSFormatName;
216 FCanLoad := True;
217 FCanSave := True;
218 FIsMultiImageFormat := True;
219 FSupportedFormats := DDSSupportedFormats;
221 FSaveCubeMap := False;
222 FSaveVolume := False;
223 FSaveMipMapCount := 1;
224 FSaveDepth := 1;
226 AddMasks(SDDSMasks);
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);
236 end;
238 procedure TDDSFileFormat.CheckOptionsValidity;
239 begin
240 if FSaveCubeMap then
241 FSaveVolume := False;
242 if FSaveVolume then
243 FSaveCubeMap := False;
244 if FSaveDepth < 1 then
245 FSaveDepth := 1;
246 if FSaveMipMapCount < 1 then
247 FSaveMipMapCount := 1;
248 end;
250 procedure TDDSFileFormat.ComputeSubDimensions(Idx, Width, Height, MipMaps, Depth: LongInt;
251 IsCubeMap, IsVolume: Boolean; var CurWidth, CurHeight: LongInt);
252 var
253 I, Last, Shift: LongInt;
254 begin
255 CurWidth := Width;
256 CurHeight := Height;
257 if MipMaps > 1 then
258 begin
259 if not IsVolume then
260 begin
261 if IsCubeMap then
262 begin
263 // Cube maps are stored like this
264 // Face 0 mimap 0
265 // Face 0 mipmap 1
266 // ...
267 // Face 1 mipmap 0
268 // Face 1 mipmap 1
269 // ...
271 // Modify index so later in for loop we iterate less times
272 Idx := Idx - ((Idx div MipMaps) * MipMaps);
273 end;
274 for I := 0 to Idx - 1 do
275 begin
276 CurWidth := ClampInt(CurWidth shr 1, 1, CurWidth);
277 CurHeight := ClampInt(CurHeight shr 1, 1, CurHeight);
278 end;
279 end
280 else
281 begin
282 // Volume textures are stored in DDS files like this:
283 // Slice 0 mipmap 0
284 // Slice 1 mipmap 0
285 // Slice 2 mipmap 0
286 // Slice 3 mipmap 0
287 // Slice 0 mipmap 1
288 // Slice 1 mipmap 1
289 // Slice 0 mipmap 2
290 // Slice 0 mipmap 3 ...
291 Shift := 0;
292 Last := Depth;
293 while Idx > Last - 1 do
294 begin
295 CurWidth := ClampInt(CurWidth shr 1, 1, CurWidth);
296 CurHeight := ClampInt(CurHeight shr 1, 1, CurHeight);
297 if (CurWidth = 1) and (CurHeight = 1) then
298 Break;
299 Inc(Shift);
300 Inc(Last, ClampInt(Depth shr Shift, 1, Depth));
301 end;
302 end;
303 end;
304 end;
306 function TDDSFileFormat.LoadData(Handle: TImagingHandle;
307 var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
308 var
309 Hdr: TDDSFileHeader;
310 SrcFormat: TImageFormat;
311 FmtInfo: TImageFormatInfo;
312 NeedsSwapChannels: Boolean;
313 CurrentWidth, CurrentHeight, ImageCount, LoadSize, I, PitchOrLinear: LongInt;
314 Data: PByte;
315 UseAsPitch: Boolean;
316 UseAsLinear: Boolean;
318 function MasksEqual(const DDPF: TDDPixelFormat; PF: PPixelFormatInfo): Boolean;
319 begin
320 Result := (DDPF.AlphaMask = PF.ABitMask) and
321 (DDPF.RedMask = PF.RBitMask) and (DDPF.GreenMask = PF.GBitMask) and
322 (DDPF.BlueMask = PF.BBitMask);
323 end;
325 begin
326 Result := False;
327 ImageCount := 1;
328 FLoadedMipMapCount := 1;
329 FLoadedDepth := 1;
330 FLoadedVolume := False;
331 FLoadedCubeMap := False;
333 with GetIO, Hdr, Hdr.Desc.PixelFormat do
334 begin
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),
340 smFromCurrent);
342 SrcFormat := ifUnknown;
343 NeedsSwapChannels := False;
344 // Get image data format
345 if (Flags and DDPF_FOURCC) = DDPF_FOURCC then
346 begin
347 // Handle FourCC and large ARGB formats
348 case FourCC of
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;
359 end;
360 end
361 else if (Flags and DDPF_RGB) = DDPF_RGB then
362 begin
363 // Handle RGB formats
364 if (Flags and DDPF_ALPHAPIXELS) = DDPF_ALPHAPIXELS then
365 begin
366 // Handle RGB with alpha formats
367 case BitCount of
368 16:
369 begin
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;
376 end;
377 32:
378 begin
379 SrcFormat := ifA8R8G8B8;
380 if BlueMask = $00FF0000 then
381 NeedsSwapChannels := True;
382 end;
383 end;
384 end
385 else
386 begin
387 // Handle RGB without alpha formats
388 case BitCount of
389 8:
390 if MasksEqual(Desc.PixelFormat,
391 GetFormatInfo(ifR3G3B2).PixelFormat) then
392 SrcFormat := ifR3G3B2;
393 16:
394 begin
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;
404 end;
405 24: SrcFormat := ifR8G8B8;
406 32:
407 begin
408 SrcFormat := ifX8R8G8B8;
409 if BlueMask = $00FF0000 then
410 NeedsSwapChannels := True;
411 end;
412 end;
413 end;
414 end
415 else if (Flags and DDPF_LUMINANCE) = DDPF_LUMINANCE then
416 begin
417 // Handle luminance formats
418 if (Flags and DDPF_ALPHAPIXELS) = DDPF_ALPHAPIXELS then
419 begin
420 // Handle luminance with alpha formats
421 if BitCount = 16 then
422 SrcFormat := ifA8Gray8;
423 end
424 else
425 begin
426 // Handle luminance without alpha formats
427 case BitCount of
428 8: SrcFormat := ifGray8;
429 16: SrcFormat := ifGray16;
430 end;
431 end;
432 end
433 else if (Flags and DDPF_BUMPLUMINANCE) = DDPF_BUMPLUMINANCE then
434 begin
435 // Handle mixed bump-luminance formats like D3DFMT_X8L8V8U8
436 case BitCount of
437 32:
438 if BlueMask = $00FF0000 then
439 begin
440 SrcFormat := ifX8R8G8B8; // D3DFMT_X8L8V8U8
441 NeedsSwapChannels := True;
442 end;
443 end;
444 end
445 else if (Flags and DDPF_BUMPDUDV) = DDPF_BUMPDUDV then
446 begin
447 // Handle bumpmap formats like D3DFMT_Q8W8V8U8
448 case BitCount of
449 16: SrcFormat := ifA8Gray8; // D3DFMT_V8U8
450 32:
451 if AlphaMask = $FF000000 then
452 begin
453 SrcFormat := ifA8R8G8B8; // D3DFMT_Q8W8V8U8
454 NeedsSwapChannels := True;
455 end;
456 64: SrcFormat := ifA16B16G16R16; // D3DFMT_Q16W16V16U16
457 end;
458 end;
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
469 begin
470 FLoadedMipMapCount := Desc.MipMaps;
471 ImageCount := Desc.MipMaps;
472 end;
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
477 begin
478 FLoadedVolume := True;
479 FLoadedDepth := Desc.Depth;
480 ImageCount := GetVolumeLevelCount(Desc.Depth, ImageCount);
481 end;
483 // File stores cube texture
484 if (Desc.Caps.Caps2 and DDSCAPS2_CUBEMAP) = DDSCAPS2_CUBEMAP then
485 begin
486 FLoadedCubeMap := True;
487 I := 0;
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);
494 FLoadedDepth := I;
495 ImageCount := ImageCount * I;
496 end;
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
507 UseAsLinear := True;
508 // Main image pitch or linear size
509 PitchOrLinear := Desc.PitchOrLinearSize;
511 for I := 0 to ImageCount - 1 do
512 begin
513 // Compute dimensions of surrent subimage based on texture type and
514 // number of mipmaps
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
520 begin
521 // Compute pitch or linear size for mipmap levels, or even for main image
522 // since some formats do not fill pitch nor size
523 if UseAsLinear then
524 PitchOrLinear := FmtInfo.GetPixelsSize(SrcFormat, CurrentWidth, CurrentHeight)
525 else
526 PitchOrLinear := (CurrentWidth * FmtInfo.BytesPerPixel + 3) div 4 * 4; // must be DWORD aligned
527 end;
529 if UseAsLinear then
530 LoadSize := PitchOrLinear
531 else
532 LoadSize := CurrentHeight * PitchOrLinear;
534 if UseAsLinear or (LoadSize = Images[I].Size) then
535 begin
536 // If DDS does not use Pitch we can simply copy data
537 Read(Handle, Images[I].Bits, LoadSize)
538 end
539 else
540 begin
541 // If DDS uses Pitch we must load aligned scanlines
542 // and then remove padding
543 GetMem(Data, LoadSize);
544 try
545 Read(Handle, Data, LoadSize);
546 RemovePadBytes(Data, Images[I].Bits, CurrentWidth, CurrentHeight,
547 FmtInfo.BytesPerPixel, PitchOrLinear);
548 finally
549 FreeMem(Data);
550 end;
551 end;
553 if NeedsSwapChannels then
554 SwapChannels(Images[I], ChannelRed, ChannelBlue);
555 end;
556 Result := True;
557 end;
558 end;
560 function TDDSFileFormat.SaveData(Handle: TImagingHandle;
561 const Images: TDynImageDataArray; Index: LongInt): Boolean;
562 var
563 Hdr: TDDSFileHeader;
564 MainImage, ImageToSave: TImageData;
565 I, MainIdx, Len, ImageCount: LongInt;
566 J: LongWord;
567 FmtInfo: TImageFormatInfo;
568 MustBeFreed: Boolean;
569 Is2DTexture, IsCubeMap, IsVolume: Boolean;
570 MipMapCount, CurrentWidth, CurrentHeight: LongInt;
571 NeedsResize: Boolean;
572 NeedsConvert: Boolean;
573 begin
574 Result := False;
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;
591 if IsCubeMap then
592 begin
593 // Check if we have enough images on Input to save cube map
594 if Len < FSaveDepth * FSaveMipMapCount then
595 IsCubeMap := False;
596 end
597 else if IsVolume then
598 begin
599 // Check if we have enough images on Input to save volume texture
600 if Len < GetVolumeLevelCount(FSaveDepth, FSaveMipMapCount) then
601 IsVolume := False;
602 end;
604 Is2DTexture := not IsCubeMap and not IsVolume;
605 if Is2DTexture then
606 begin
607 // Get number of mipmaps used with 2D texture
608 MipMapCount := Min(Len, GetNumMipMapLevels(Images[MainIdx].Width, Images[MainIdx].Height));
609 end;
611 // we create compatible main image and fill headers
612 if MakeCompatible(Images[MainIdx], MainImage, MustBeFreed) then
613 with GetIO, MainImage, Hdr do
614 try
615 FmtInfo := GetFormatInfo(Format);
616 Magic := DDSMagic;
617 Desc.Size := SizeOf(Desc);
618 Desc.Width := Width;
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
627 begin
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;
632 end;
634 if IsCubeMap then
635 begin
636 // Set proper cube map flags - number of stored faces is taken
637 // from FSaveDepth
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
642 begin
643 Desc.Caps.Caps2 := Desc.Caps.Caps2 or J;
644 J := J shl 1;
645 end;
646 ImageCount := FSaveDepth * FSaveMipMapCount;
647 end
648 else if IsVolume then
649 begin
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);
656 end;
658 // Now we set DDS pixel format for main image
659 if FmtInfo.IsSpecial or FmtInfo.IsFloatingPoint or
660 (FmtInfo.BytesPerPixel > 4) then
661 begin
662 Desc.PixelFormat.Flags := DDPF_FOURCC;
663 case Format of
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;
674 end;
675 end
676 else if FmtInfo.HasGrayChannel then
677 begin
678 Desc.PixelFormat.Flags := DDPF_LUMINANCE;
679 Desc.PixelFormat.BitCount := FmtInfo.BytesPerPixel * 8;
680 case Format of
681 ifGray8: Desc.PixelFormat.RedMask := 255;
682 ifGray16: Desc.PixelFormat.RedMask := 65535;
683 ifA8Gray8:
684 begin
685 Desc.PixelFormat.Flags := Desc.PixelFormat.Flags or DDPF_ALPHAPIXELS;
686 Desc.PixelFormat.RedMask := 255;
687 Desc.PixelFormat.AlphaMask := 65280;
688 end;
689 end;
690 end
691 else
692 begin
693 Desc.PixelFormat.Flags := DDPF_RGB;
694 Desc.PixelFormat.BitCount := FmtInfo.BytesPerPixel * 8;
695 if FmtInfo.HasAlphaChannel then
696 begin
697 Desc.PixelFormat.Flags := Desc.PixelFormat.Flags or DDPF_ALPHAPIXELS;
698 Desc.PixelFormat.AlphaMask := $FF000000;
699 end;
700 if FmtInfo.BytesPerPixel > 2 then
701 begin
702 Desc.PixelFormat.RedMask := $00FF0000;
703 Desc.PixelFormat.GreenMask := $0000FF00;
704 Desc.PixelFormat.BlueMask := $000000FF;
705 end
706 else
707 begin
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;
712 end;
713 end;
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
721 // simensions too.
722 for I := MainIdx + 1 to MainIdx + ImageCount - 1 do
723 begin
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
733 begin
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);
738 if NeedsConvert then
739 ConvertImage(ImageToSave, Format);
740 if NeedsResize then
741 ResizeImage(ImageToSave, CurrentWidth, CurrentHeight, rfBilinear);
742 end
743 else
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);
751 end;
753 Result := True;
754 finally
755 if MustBeFreed then
756 FreeImage(MainImage);
757 end;
758 end;
760 procedure TDDSFileFormat.ConvertToSupported(var Image: TImageData;
761 const Info: TImageFormatInfo);
762 var
763 ConvFormat: TImageFormat;
764 begin
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
769 begin
770 if Info.Format = ifA16R16G16B16F then
771 // only swap channels here
772 ConvFormat := ifA16B16G16R16F
773 else
774 // convert other floating point formats to A32B32G32R32F
775 ConvFormat := ifA32B32G32R32F
776 end
777 else if Info.HasGrayChannel then
778 begin
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
785 else
786 // convert 16-64bit grayscales to Gray16
787 ConvFormat := ifGray16;
788 end
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
794 else
795 // convert the other formats to X8R8G8B8
796 ConvFormat := ifX8R8G8B8;
798 ConvertImage(Image, ConvFormat);
799 end;
801 function TDDSFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
802 var
803 Hdr: TDDSFileHeader;
804 ReadCount: LongInt;
805 begin
806 Result := False;
807 if Handle <> nil then
808 with GetIO do
809 begin
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);
814 end;
815 end;
817 initialization
818 RegisterImageFileFormat(TDDSFileFormat);
821 File Notes:
823 -- TODOS ----------------------------------------------------
824 - nothing now
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
863 end.