Statistics
| Branch: | Tag: | Revision:

root / Imaging / ImagingDds.pas @ 0:95bd93c28625

History | View | Annotate | Download (30.9 kB)

1
{
2
  $Id: ImagingDds.pas 100 2007-06-28 21:09:52Z galfar $
3
  Vampyre Imaging Library
4
  by Marek Mauder
5
  http://imaginglib.sourceforge.net
6
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
11
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.
15
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.
25
26
  For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
27
}
28
29
{ This unit contains image format loader/saver for DirectDraw Surface images.}
30
unit ImagingDds;
31
32
{$I ImagingOptions.inc}
33
34
interface
35
36
uses
37
  ImagingTypes, Imaging, ImagingUtility, ImagingFormats;
38
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;
96
97
implementation
98
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];
106
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
118
  { Some D3DFORMAT values used in DDS files as FourCC value.}
119
  D3DFMT_A16B16G16R16  = 36;
120
  D3DFMT_R32F          = 114;
121
  D3DFMT_A32B32G32R32F = 116;
122
  D3DFMT_R16F          = 111;
123
  D3DFMT_A16B16G16R16F = 113;
124
125
  { Constans used by TDDSurfaceDesc2.Flags.}
126
  DDSD_CAPS            = $00000001;
127
  DDSD_HEIGHT          = $00000002;
128
  DDSD_WIDTH           = $00000004;
129
  DDSD_PITCH           = $00000008;
130
  DDSD_PIXELFORMAT     = $00001000;
131
  DDSD_MIPMAPCOUNT     = $00020000;
132
  DDSD_LINEARSIZE      = $00080000;
133
  DDSD_DEPTH           = $00800000;
134
135
  { Constans used by TDDSPixelFormat.Flags.}
136
  DDPF_ALPHAPIXELS     = $00000001;    // used by formats which contain alpha
137
  DDPF_FOURCC          = $00000004;    // used by DXT and large ARGB formats
138
  DDPF_RGB             = $00000040;    // used by RGB formats
139
  DDPF_LUMINANCE       = $00020000;    // used by formats like D3DFMT_L16
140
  DDPF_BUMPLUMINANCE   = $00040000;    // used by mixed signed-unsigned formats
141
  DDPF_BUMPDUDV        = $00080000;    // used by signed formats
142
143
  { Constans used by TDDSCaps.Caps1.}
144
  DDSCAPS_COMPLEX      = $00000008;
145
  DDSCAPS_TEXTURE      = $00001000;
146
  DDSCAPS_MIPMAP       = $00400000;
147
148
  { Constans used by TDDSCaps.Caps2.}
149
  DDSCAPS2_CUBEMAP     = $00000200;
150
  DDSCAPS2_POSITIVEX   = $00000400;
151
  DDSCAPS2_NEGATIVEX   = $00000800;
152
  DDSCAPS2_POSITIVEY   = $00001000;
153
  DDSCAPS2_NEGATIVEY   = $00002000;
154
  DDSCAPS2_POSITIVEZ   = $00004000;
155
  DDSCAPS2_NEGATIVEZ   = $00008000;
156
  DDSCAPS2_VOLUME      = $00200000;
157
158
  { Flags for TDDSurfaceDesc2.Flags used when saving DDS file.}
159
  DDS_SAVE_FLAGS = DDSD_CAPS or DDSD_PIXELFORMAT or DDSD_WIDTH or
160
    DDSD_HEIGHT or DDSD_LINEARSIZE;
161
162
type
163
  { Stores the pixel format information.}
164
  TDDPixelFormat = packed record
165
    Size: LongWord;       // Size of the structure = 32 bytes
166
    Flags: LongWord;      // Flags to indicate valid fields
167
    FourCC: LongWord;     // Four-char code for compressed textures (DXT)
168
    BitCount: LongWord;   // Bits per pixel if uncomp. usually 16,24 or 32
169
    RedMask: LongWord;    // Bit mask for the Red component
170
    GreenMask: LongWord;  // Bit mask for the Green component
171
    BlueMask: LongWord;   // Bit mask for the Blue component
172
    AlphaMask: LongWord;  // Bit mask for the Alpha component
173
  end;
174
175
  { Specifies capabilities of surface.}
176
  TDDSCaps = packed record
177
    Caps1: LongWord;      // Should always include DDSCAPS_TEXTURE
178
    Caps2: LongWord;      // For cubic environment maps
179
    Reserved: array[0..1] of LongWord; // Reserved
180
  end;
181
182
  { Record describing DDS file contents.}
183
  TDDSurfaceDesc2 = packed record
184
    Size: LongWord;       // Size of the structure = 124 Bytes
185
    Flags: LongWord;      // Flags to indicate valid fields
186
    Height: LongWord;     // Height of the main image in pixels
187
    Width: LongWord;      // Width of the main image in pixels
188
    PitchOrLinearSize: LongWord; // For uncomp formats number of bytes per
189
                          // scanline. For comp it is the size in
190
                          // bytes of the main image
191
    Depth: LongWord;      // Only for volume text depth of the volume
192
    MipMaps: LongInt;     // Total number of levels in the mipmap chain
193
    Reserved1: array[0..10] of LongWord; // Reserved
194
    PixelFormat: TDDPixelFormat; // Format of the pixel data
195
    Caps: TDDSCaps;       // Capabilities
196
    Reserved2: LongWord;  // Reserved
197
  end;
198
199
  { DDS file header.}
200
  TDDSFileHeader = packed record
201
    Magic: LongWord;       // File format magic
202
    Desc: TDDSurfaceDesc2; // Surface description
203
  end;
204
205
206
{ TDDSFileFormat class implementation }
207
208
constructor TDDSFileFormat.Create;
209
begin
210
  inherited Create;
211
  FName := SDDSFormatName;
212
  FCanLoad := True;
213
  FCanSave := True;
214
  FIsMultiImageFormat := True;
215
  FSupportedFormats := DDSSupportedFormats;
216
217
  FSaveCubeMap := False;
218
  FSaveVolume := False;
219
  FSaveMipMapCount := 1;
220
  FSaveDepth := 1;
221
222
  AddMasks(SDDSMasks);
223
224
  RegisterOption(ImagingDDSLoadedCubeMap, @FLoadedCubeMap);
225
  RegisterOption(ImagingDDSLoadedVolume, @FLoadedVolume);
226
  RegisterOption(ImagingDDSLoadedMipMapCount, @FLoadedMipMapCount);
227
  RegisterOption(ImagingDDSLoadedDepth, @FLoadedDepth);
228
  RegisterOption(ImagingDDSSaveCubeMap, @FSaveCubeMap);
229
  RegisterOption(ImagingDDSSaveVolume, @FSaveVolume);
230
  RegisterOption(ImagingDDSSaveMipMapCount, @FSaveMipMapCount);
231
  RegisterOption(ImagingDDSSaveDepth, @FSaveDepth);
232
end;
233
234
procedure TDDSFileFormat.CheckOptionsValidity;
235
begin
236
  if FSaveCubeMap then
237
    FSaveVolume := False;
238
  if FSaveVolume then
239
    FSaveCubeMap := False;
240
  if FSaveDepth < 1 then
241
    FSaveDepth := 1;
242
  if FSaveMipMapCount < 1 then
243
    FSaveMipMapCount := 1;
244
end;
245
246
procedure TDDSFileFormat.ComputeSubDimensions(Idx, Width, Height, MipMaps, Depth: LongInt;
247
  IsCubeMap, IsVolume: Boolean; var CurWidth, CurHeight: LongInt);
248
var
249
  I, Last, Shift: LongInt;
250
begin
251
  CurWidth := Width;
252
  CurHeight := Height;
253
  if MipMaps > 1 then
254
  begin
255
    if not IsVolume then
256
    begin
257
      if IsCubeMap then
258
      begin
259
        // Cube maps are stored like this
260
        // Face 0 mimap 0
261
        // Face 0 mipmap 1
262
        // ...
263
        // Face 1 mipmap 0
264
        // Face 1 mipmap 1
265
        // ...
266
267
        // Modify index so later in for loop we iterate less times
268
        Idx := Idx - ((Idx div MipMaps) * MipMaps);
269
      end;
270
      for I := 0 to Idx - 1 do
271
      begin
272
        CurWidth := ClampInt(CurWidth shr 1, 1, CurWidth);
273
        CurHeight := ClampInt(CurHeight shr 1, 1, CurHeight);
274
      end;
275
    end
276
    else
277
    begin
278
      // Volume textures are stored in DDS files like this:
279
      // Slice 0 mipmap 0
280
      // Slice 1 mipmap 0
281
      // Slice 2 mipmap 0
282
      // Slice 3 mipmap 0
283
      // Slice 0 mipmap 1
284
      // Slice 1 mipmap 1
285
      // Slice 0 mipmap 2
286
      // Slice 0 mipmap 3 ...
287
      Shift := 0;
288
      Last := Depth;
289
      while Idx > Last - 1 do
290
      begin
291
        CurWidth := ClampInt(CurWidth shr 1, 1, CurWidth);
292
        CurHeight := ClampInt(CurHeight shr 1, 1, CurHeight);
293
        if (CurWidth = 1) and (CurHeight = 1) then
294
          Break;
295
        Inc(Shift);
296
        Inc(Last, ClampInt(Depth shr Shift, 1, Depth));
297
      end;
298
    end;
299
  end;
300
end;
301
302
function TDDSFileFormat.LoadData(Handle: TImagingHandle;
303
  var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
304
var
305
  Hdr: TDDSFileHeader;
306
  SrcFormat: TImageFormat;
307
  FmtInfo: TImageFormatInfo;
308
  NeedsSwapChannels: Boolean;
309
  CurrentWidth, CurrentHeight, ImageCount, LoadSize, I, PitchOrLinear: LongInt;
310
  Data: PByte;
311
  UseAsPitch: Boolean;
312
  UseAsLinear: Boolean;
313
314
  function MasksEqual(const DDPF: TDDPixelFormat; PF: PPixelFormatInfo): Boolean;
315
  begin
316
    Result := (DDPF.AlphaMask = PF.ABitMask) and
317
      (DDPF.RedMask = PF.RBitMask) and (DDPF.GreenMask = PF.GBitMask) and
318
      (DDPF.BlueMask = PF.BBitMask);
319
  end;
320
321
begin
322
  Result := False;
323
  ImageCount := 1;
324
  FLoadedMipMapCount := 1;
325
  FLoadedDepth := 1;
326
  FLoadedVolume := False;
327
  FLoadedCubeMap := False;
328
329
  with GetIO, Hdr, Hdr.Desc.PixelFormat do
330
  begin
331
    Read(Handle, @Hdr, SizeOF(Hdr));
332
    {
333
    // Set position to the end of the header (for possible future versions
334
    // ith larger header)
335
    Seek(Handle, Hdr.Desc.Size + SizeOf(Hdr.Magic) - SizeOf(Hdr),
336
      smFromCurrent);
337
    }
338
    SrcFormat := ifUnknown;
339
    NeedsSwapChannels := False;
340
    // Get image data format
341
    if (Flags and DDPF_FOURCC) = DDPF_FOURCC then
342
    begin
343
      // Handle FourCC and large ARGB formats
344
      case FourCC of
345
        D3DFMT_A16B16G16R16: SrcFormat := ifA16B16G16R16;
346
        D3DFMT_R32F: SrcFormat := ifR32F;
347
        D3DFMT_A32B32G32R32F: SrcFormat := ifA32B32G32R32F;
348
        D3DFMT_R16F: SrcFormat := ifR16F;
349
        D3DFMT_A16B16G16R16F: SrcFormat := ifA16B16G16R16F;
350
        FOURCC_DXT1: SrcFormat := ifDXT1;
351
        FOURCC_DXT3: SrcFormat := ifDXT3;
352
        FOURCC_DXT5: SrcFormat := ifDXT5;
353
      end;
354
    end
355
    else if (Flags and DDPF_RGB) = DDPF_RGB then
356
    begin
357
      // Handle RGB formats
358
      if (Flags and DDPF_ALPHAPIXELS) = DDPF_ALPHAPIXELS then
359
      begin
360
        // Handle RGB with alpha formats
361
        case BitCount of
362
          16:
363
            begin
364
              if MasksEqual(Desc.PixelFormat,
365
                GetFormatInfo(ifA4R4G4B4).PixelFormat) then
366
                SrcFormat := ifA4R4G4B4;
367
              if MasksEqual(Desc.PixelFormat,
368
                GetFormatInfo(ifA1R5G5B5).PixelFormat) then
369
                SrcFormat := ifA1R5G5B5;
370
            end;
371
          32:
372
            begin
373
              SrcFormat := ifA8R8G8B8;
374
              if BlueMask = $00FF0000 then
375
                NeedsSwapChannels := True;
376
            end;
377
        end;
378
      end
379
      else
380
      begin
381
        // Handle RGB without alpha formats
382
        case BitCount of
383
          8:
384
            if MasksEqual(Desc.PixelFormat,
385
              GetFormatInfo(ifR3G3B2).PixelFormat) then
386
              SrcFormat := ifR3G3B2;
387
          16:
388
            begin
389
              if MasksEqual(Desc.PixelFormat,
390
                GetFormatInfo(ifX4R4G4B4).PixelFormat) then
391
                SrcFormat := ifX4R4G4B4;
392
              if MasksEqual(Desc.PixelFormat,
393
                GetFormatInfo(ifX1R5G5B5).PixelFormat) then
394
                SrcFormat := ifX1R5G5B5;
395
              if MasksEqual(Desc.PixelFormat,
396
                GetFormatInfo(ifR5G6B5).PixelFormat) then
397
                SrcFormat := ifR5G6B5;
398
            end;
399
          24: SrcFormat := ifR8G8B8;
400
          32:
401
            begin
402
              SrcFormat := ifX8R8G8B8;
403
              if BlueMask = $00FF0000 then
404
                NeedsSwapChannels := True;
405
            end;
406
        end;
407
      end;
408
    end
409
    else if (Flags and DDPF_LUMINANCE) = DDPF_LUMINANCE then
410
    begin
411
      // Handle luminance formats
412
      if (Flags and DDPF_ALPHAPIXELS) = DDPF_ALPHAPIXELS then
413
      begin
414
        // Handle luminance with alpha formats
415
        if BitCount = 16 then
416
          SrcFormat := ifA8Gray8;
417
      end
418
      else
419
      begin
420
        // Handle luminance without alpha formats
421
        case BitCount of
422
          8: SrcFormat := ifGray8;
423
          16: SrcFormat := ifGray16;
424
        end;
425
      end;
426
    end
427
    else if (Flags and DDPF_BUMPLUMINANCE) = DDPF_BUMPLUMINANCE then
428
    begin
429
      // Handle mixed bump-luminance formats like D3DFMT_X8L8V8U8
430
      case BitCount of
431
        32:
432
          if BlueMask = $00FF0000 then
433
          begin
434
            SrcFormat := ifX8R8G8B8; // D3DFMT_X8L8V8U8
435
            NeedsSwapChannels := True;
436
          end;
437
      end;
438
    end
439
    else if (Flags and DDPF_BUMPDUDV) = DDPF_BUMPDUDV then
440
    begin
441
      // Handle bumpmap formats like D3DFMT_Q8W8V8U8
442
      case BitCount of
443
        16: SrcFormat := ifA8Gray8; // D3DFMT_V8U8
444
        32:
445
          if AlphaMask = $FF000000 then
446
          begin
447
            SrcFormat := ifA8R8G8B8; // D3DFMT_Q8W8V8U8
448
            NeedsSwapChannels := True;
449
          end;
450
        64: SrcFormat := ifA16B16G16R16; // D3DFMT_Q16W16V16U16
451
      end;
452
    end;
453
454
    // If DDS format is not supported we will exit
455
    if SrcFormat = ifUnknown then Exit;
456
457
    // File contains mipmaps for each subimage.
458
    { Some DDS writers ignore setting proper Caps and Flags so
459
      this check is not usable:
460
    if ((Desc.Caps.Caps1 and DDSCAPS_MIPMAP) = DDSCAPS_MIPMAP) and
461
      ((Desc.Flags and DDSD_MIPMAPCOUNT) = DDSD_MIPMAPCOUNT) then}
462
    if Desc.MipMaps > 1 then
463
    begin
464
      FLoadedMipMapCount := Desc.MipMaps;
465
      ImageCount := Desc.MipMaps;
466
    end;
467
468
    // File stores volume texture
469
    if ((Desc.Caps.Caps2 and DDSCAPS2_VOLUME) = DDSCAPS2_VOLUME) and
470
      ((Desc.Flags and DDSD_DEPTH) = DDSD_DEPTH) then
471
    begin
472
      FLoadedVolume := True;
473
      FLoadedDepth := Desc.Depth;
474
      ImageCount := GetVolumeLevelCount(Desc.Depth, ImageCount);
475
    end;
476
477
    // File stores cube texture
478
    if (Desc.Caps.Caps2 and DDSCAPS2_CUBEMAP) = DDSCAPS2_CUBEMAP then
479
    begin
480
      FLoadedCubeMap := True;
481
      I := 0;
482
      if (Desc.Caps.Caps2 and DDSCAPS2_POSITIVEX) = DDSCAPS2_POSITIVEX then Inc(I);
483
      if (Desc.Caps.Caps2 and DDSCAPS2_POSITIVEY) = DDSCAPS2_POSITIVEY then Inc(I);
484
      if (Desc.Caps.Caps2 and DDSCAPS2_POSITIVEZ) = DDSCAPS2_POSITIVEZ then Inc(I);
485
      if (Desc.Caps.Caps2 and DDSCAPS2_NEGATIVEX) = DDSCAPS2_NEGATIVEX then Inc(I);
486
      if (Desc.Caps.Caps2 and DDSCAPS2_NEGATIVEY) = DDSCAPS2_NEGATIVEY then Inc(I);
487
      if (Desc.Caps.Caps2 and DDSCAPS2_NEGATIVEZ) = DDSCAPS2_NEGATIVEZ then Inc(I);
488
      FLoadedDepth := I;
489
      ImageCount := ImageCount * I;
490
    end;
491
492
    // Allocate and load all images in file
493
    FmtInfo := GetFormatInfo(SrcFormat);
494
    SetLength(Images, ImageCount);
495
496
    // Compute the pitch or get if from file if present
497
    UseAsPitch := (Desc.Flags and DDSD_PITCH) = DDSD_PITCH;
498
    UseAsLinear := (Desc.Flags and DDSD_LINEARSIZE) = DDSD_LINEARSIZE;
499
    // Use linear as default if none is set
500
    if not UseAsPitch and not UseAsLinear then
501
      UseAsLinear := True;
502
    // Main image pitch or linear size
503
    PitchOrLinear := Desc.PitchOrLinearSize;
504
505
    for I := 0 to ImageCount - 1 do
506
    begin
507
      // Compute dimensions of surrent subimage based on texture type and
508
      // number of mipmaps
509
      ComputeSubDimensions(I, Desc.Width, Desc.Height, Desc.MipMaps, Desc.Depth,
510
        FloadedCubeMap, FLoadedVolume, CurrentWidth, CurrentHeight);
511
      NewImage(CurrentWidth, CurrentHeight, SrcFormat, Images[I]);
512
513
      if (I > 0) or (PitchOrLinear = 0) then
514
      begin
515
        // Compute pitch or linear size for mipmap levels, or even for main image
516
        // since some formats do not fill pitch nor size
517
        if UseAsLinear then
518
          PitchOrLinear := FmtInfo.GetPixelsSize(SrcFormat, CurrentWidth, CurrentHeight)
519
        else
520
          PitchOrLinear := (CurrentWidth * FmtInfo.BytesPerPixel + 3) div 4 * 4; // must be DWORD aligned
521
      end;
522
523
      if UseAsLinear then
524
        LoadSize := PitchOrLinear
525
      else
526
        LoadSize := CurrentHeight * PitchOrLinear;
527
528
      if UseAsLinear or (LoadSize = Images[I].Size) then
529
      begin
530
        // If DDS does not use Pitch we can simply copy data
531
        Read(Handle, Images[I].Bits, LoadSize)
532
      end
533
      else
534
      begin
535
        // If DDS uses Pitch we must load aligned scanlines
536
        // and then remove padding
537
        GetMem(Data, LoadSize);
538
        try
539
          Read(Handle, Data, LoadSize);
540
          RemovePadBytes(Data, Images[I].Bits, CurrentWidth, CurrentHeight,
541
            FmtInfo.BytesPerPixel, PitchOrLinear);
542
       finally
543
          FreeMem(Data);
544
        end;
545
      end;
546
547
      if NeedsSwapChannels then
548
        SwapChannels(Images[I], ChannelRed, ChannelBlue);
549
    end;
550
    Result := True;
551
  end;
552
end;
553
554
function TDDSFileFormat.SaveData(Handle: TImagingHandle;
555
  const Images: TDynImageDataArray; Index: LongInt): Boolean;
556
var
557
  Hdr: TDDSFileHeader;
558
  MainImage, ImageToSave: TImageData;
559
  I, MainIdx, Len, ImageCount: LongInt;
560
  J: LongWord;
561
  FmtInfo: TImageFormatInfo;
562
  MustBeFreed: Boolean;
563
  Is2DTexture, IsCubeMap, IsVolume: Boolean;
564
  MipMapCount, CurrentWidth, CurrentHeight: LongInt;
565
  NeedsResize: Boolean;
566
  NeedsConvert: Boolean;
567
begin
568
  Result := False;
569
  FillChar(Hdr, Sizeof(Hdr), 0);
570
571
  MainIdx := FFirstIdx;
572
  Len := FLastIdx - MainIdx + 1;
573
  // Some DDS saving rules:
574
  //   2D textures: Len is used as mipmap count (FSaveMipMapCount not used!).
575
  //   Cube maps:   FSaveDepth * FSaveMipMapCount images are used, if Len is
576
  //                smaller than this file is saved as regular 2D texture.
577
  //   Volume maps: GetVolumeLevelCount(FSaveDepth, FSaveMipMapCount) images are
578
  //                used, if Len is smaller than this file is
579
  //                saved as regular 2D texture.
580
581
  IsCubeMap := FSaveCubeMap;
582
  IsVolume := FSaveVolume;
583
  MipMapCount := FSaveMipMapCount;
584
585
  if IsCubeMap then
586
  begin
587
    // Check if we have enough images on Input to save cube map
588
    if Len < FSaveDepth * FSaveMipMapCount then
589
      IsCubeMap := False;
590
  end
591
  else if IsVolume then
592
  begin
593
    // Check if we have enough images on Input to save volume texture
594
    if Len < GetVolumeLevelCount(FSaveDepth, FSaveMipMapCount) then
595
      IsVolume := False;
596
  end;
597
598
  Is2DTexture := not IsCubeMap and not IsVolume;
599
  if Is2DTexture then
600
  begin
601
    // Get number of mipmaps used with 2D texture
602
    MipMapCount := Min(Len, GetNumMipMapLevels(Images[MainIdx].Width, Images[MainIdx].Height));
603
  end;
604
605
  // we create compatible main image and fill headers
606
  if MakeCompatible(Images[MainIdx], MainImage, MustBeFreed) then
607
  with GetIO, MainImage, Hdr do
608
  try
609
    FmtInfo := GetFormatInfo(Format);
610
    Magic := DDSMagic;
611
    Desc.Size := SizeOf(Desc);
612
    Desc.Width := Width;
613
    Desc.Height := Height;
614
    Desc.Flags := DDS_SAVE_FLAGS;
615
    Desc.Caps.Caps1 := DDSCAPS_TEXTURE;
616
    Desc.PixelFormat.Size := SizeOf(Desc.PixelFormat);
617
    Desc.PitchOrLinearSize := MainImage.Size;
618
    ImageCount := MipMapCount;
619
620
    if MipMapCount > 1 then
621
    begin
622
      // Set proper flags if we have some mipmaps to be saved
623
      Desc.Flags := Desc.Flags or DDSD_MIPMAPCOUNT;
624
      Desc.Caps.Caps1 := Desc.Caps.Caps1 or DDSCAPS_MIPMAP or DDSCAPS_COMPLEX;
625
      Desc.MipMaps := MipMapCount;
626
    end;
627
628
    if IsCubeMap then
629
    begin
630
      // Set proper cube map flags - number of stored faces is taken
631
      // from FSaveDepth
632
      Desc.Caps.Caps1 := Desc.Caps.Caps1 or DDSCAPS_COMPLEX;
633
      Desc.Caps.Caps2 := Desc.Caps.Caps2 or DDSCAPS2_CUBEMAP;
634
      J := DDSCAPS2_POSITIVEX;
635
      for I := 0 to FSaveDepth - 1 do
636
      begin
637
        Desc.Caps.Caps2 := Desc.Caps.Caps2 or J;
638
        J := J shl 1;
639
      end;
640
      ImageCount := FSaveDepth * FSaveMipMapCount;
641
    end
642
    else if IsVolume then
643
    begin
644
      // Set proper flags for volume texture
645
      Desc.Flags := Desc.Flags or DDSD_DEPTH;
646
      Desc.Caps.Caps1 := Desc.Caps.Caps1 or DDSCAPS_COMPLEX;
647
      Desc.Caps.Caps2 := Desc.Caps.Caps2 or DDSCAPS2_VOLUME;
648
      Desc.Depth := FSaveDepth;
649
      ImageCount := GetVolumeLevelCount(FSaveDepth, FSaveMipMapCount);
650
    end;
651
652
    // Now we set DDS pixel format for main image
653
    if FmtInfo.IsSpecial or FmtInfo.IsFloatingPoint or
654
      (FmtInfo.BytesPerPixel > 4) then
655
    begin
656
      Desc.PixelFormat.Flags := DDPF_FOURCC;
657
      case Format of
658
        ifA16B16G16R16:  Desc.PixelFormat.FourCC := D3DFMT_A16B16G16R16;
659
        ifR32F:          Desc.PixelFormat.FourCC := D3DFMT_R32F;
660
        ifA32B32G32R32F: Desc.PixelFormat.FourCC := D3DFMT_A32B32G32R32F;
661
        ifR16F:          Desc.PixelFormat.FourCC := D3DFMT_R16F;
662
        ifA16B16G16R16F: Desc.PixelFormat.FourCC := D3DFMT_A16B16G16R16F;
663
        ifDXT1:          Desc.PixelFormat.FourCC := FOURCC_DXT1;
664
        ifDXT3:          Desc.PixelFormat.FourCC := FOURCC_DXT3;
665
        ifDXT5:          Desc.PixelFormat.FourCC := FOURCC_DXT5;
666
      end;
667
    end
668
    else if FmtInfo.HasGrayChannel then
669
    begin
670
      Desc.PixelFormat.Flags := DDPF_LUMINANCE;
671
      Desc.PixelFormat.BitCount := FmtInfo.BytesPerPixel * 8;
672
      case Format of
673
        ifGray8:  Desc.PixelFormat.RedMask := 255;
674
        ifGray16: Desc.PixelFormat.RedMask := 65535;
675
        ifA8Gray8:
676
          begin
677
            Desc.PixelFormat.Flags := Desc.PixelFormat.Flags or DDPF_ALPHAPIXELS;
678
            Desc.PixelFormat.RedMask := 255;
679
            Desc.PixelFormat.AlphaMask := 65280;
680
          end;
681
      end;
682
    end
683
    else
684
    begin
685
      Desc.PixelFormat.Flags := DDPF_RGB;
686
      Desc.PixelFormat.BitCount := FmtInfo.BytesPerPixel * 8;
687
      if FmtInfo.HasAlphaChannel then
688
      begin
689
        Desc.PixelFormat.Flags := Desc.PixelFormat.Flags or DDPF_ALPHAPIXELS;
690
        Desc.PixelFormat.AlphaMask := $FF000000;
691
      end;
692
      if FmtInfo.BytesPerPixel > 2 then
693
      begin
694
        Desc.PixelFormat.RedMask :=   $00FF0000;
695
        Desc.PixelFormat.GreenMask := $0000FF00;
696
        Desc.PixelFormat.BlueMask :=  $000000FF;
697
      end
698
      else
699
      begin
700
        Desc.PixelFormat.AlphaMask := FmtInfo.PixelFormat.ABitMask;
701
        Desc.PixelFormat.RedMask := FmtInfo.PixelFormat.RBitMask;
702
        Desc.PixelFormat.GreenMask := FmtInfo.PixelFormat.GBitMask;
703
        Desc.PixelFormat.BlueMask := FmtInfo.PixelFormat.BBitMask;
704
      end;
705
    end;
706
707
    // Header and main image are written to output
708
    Write(Handle, @Hdr, SizeOf(Hdr));
709
    Write(Handle, MainImage.Bits, MainImage.Size);
710
711
    // Write the rest of the images and convert them to
712
    // the same format as main image if necessary and ensure proper mipmap
713
    // simensions too.
714
    for I := MainIdx + 1 to MainIdx + ImageCount - 1 do
715
    begin
716
      // Get proper dimensions for this level
717
      ComputeSubDimensions(I, Desc.Width, Desc.Height, Desc.MipMaps, Desc.Depth,
718
        IsCubeMap, IsVolume, CurrentWidth, CurrentHeight);
719
720
      // Check if input image for this level has the right size and format
721
      NeedsResize := not ((Images[I].Width = CurrentWidth) and (Images[I].Height = CurrentHeight));
722
      NeedsConvert := not (Images[I].Format = Format);
723
724
      if NeedsResize or NeedsConvert then
725
      begin
726
        // Input image must be resized or converted to different format
727
        // to become valid mipmap level
728
        InitImage(ImageToSave);
729
        CloneImage(Images[I], ImageToSave);
730
        if NeedsConvert then
731
          ConvertImage(ImageToSave, Format);
732
        if NeedsResize then
733
          ResizeImage(ImageToSave, CurrentWidth, CurrentHeight, rfBilinear);
734
      end
735
      else
736
        // Input image can be used without any changes
737
        ImageToSave := Images[I];
738
739
      // Write level data and release temp image if necessary
740
      Write(Handle, ImageToSave.Bits, ImageToSave.Size);
741
      if Images[I].Bits <> ImageToSave.Bits then
742
        FreeImage(ImageToSave);
743
    end;
744
745
    Result := True;
746
  finally
747
    if MustBeFreed then
748
      FreeImage(MainImage);
749
  end;
750
end;
751
752
procedure TDDSFileFormat.ConvertToSupported(var Image: TImageData;
753
  const Info: TImageFormatInfo);
754
var
755
  ConvFormat: TImageFormat;
756
begin
757
  if Info.IsIndexed or Info.IsSpecial then
758
    // convert indexed and unsupported special formatd to A8R8G8B8
759
    ConvFormat := ifA8R8G8B8
760
  else if Info.IsFloatingPoint then
761
  begin
762
    if Info.Format = ifA16R16G16B16F then
763
      // only swap channels here
764
      ConvFormat := ifA16B16G16R16F
765
    else
766
      // convert other floating point formats to A32B32G32R32F
767
      ConvFormat := ifA32B32G32R32F
768
  end
769
  else if Info.HasGrayChannel then
770
  begin
771
    if Info.HasAlphaChannel then
772
      // convert grayscale with alpha to A8Gray8
773
      ConvFormat := ifA8Gray8
774
    else if Info.BytesPerPixel = 1 then
775
      // convert 8bit grayscale to Gray8
776
      ConvFormat := ifGray8
777
    else
778
      // convert 16-64bit grayscales to Gray16
779
      ConvFormat := ifGray16;
780
  end
781
  else if Info.BytesPerPixel > 4 then
782
    ConvFormat := ifA16B16G16R16
783
  else if Info.HasAlphaChannel then
784
    // convert the other images with alpha channel to A8R8G8B8
785
    ConvFormat := ifA8R8G8B8
786
  else
787
    // convert the other formats to X8R8G8B8
788
    ConvFormat := ifX8R8G8B8;
789
790
  ConvertImage(Image, ConvFormat);
791
end;
792
793
function TDDSFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
794
var
795
  Hdr: TDDSFileHeader;
796
  ReadCount: LongInt;
797
begin
798
  Result := False;
799
  if Handle <> nil then
800
    with GetIO do
801
    begin
802
      ReadCount := Read(Handle, @Hdr, SizeOf(Hdr));
803
      Seek(Handle, -ReadCount, smFromCurrent);
804
      Result := (Hdr.Magic = DDSMagic) and (ReadCount = SizeOf(Hdr)) and
805
        ((Hdr.Desc.Caps.Caps1 and DDSCAPS_TEXTURE) = DDSCAPS_TEXTURE);
806
    end;
807
end;
808
809
initialization
810
  RegisterImageFileFormat(TDDSFileFormat);
811
812
{
813
  File Notes:
814
815
  -- TODOS ----------------------------------------------------
816
    - nothing now
817
818
  -- 0.23 Changes/Bug Fixes -----------------------------------
819
    - Saved DDS with mipmaps now correctly defineds COMPLEX flag.
820
    - Fixed loading of RGB DDS files that use pitch and have mipmaps -
821
      mipmaps were loaded wrongly.
822
823
  -- 0.21 Changes/Bug Fixes -----------------------------------
824
    - Changed saving behaviour a bit: mipmaps are inlcuded automatically for
825
      2D textures if input image array has more than 1 image (no need to
826
      set SaveMipMapCount manually).
827
    - Mipmap levels are now saved with proper dimensions when saving DDS files.
828
    - Made some changes to not be so strict when loading DDS files.
829
      Many programs seem to save them in non-standard format
830
      (by MS DDS File Reference).
831
    - Added missing ifX8R8G8B8 to SupportedFormats, MakeCompatible failed
832
      when image was converted to this format (inside).
833
    - MakeCompatible method moved to base class, put ConvertToSupported here.
834
      GetSupportedFormats removed, it is now set in constructor.
835
    - Fixed bug that sometimes saved non-standard DDS files and another
836
      one that caused crash when these files were loaded.
837
    - Changed extensions to filename masks.
838
    - Changed SaveData, LoadData, and MakeCompatible methods according
839
      to changes in base class in Imaging unit.
840
841
  -- 0.19 Changes/Bug Fixes -----------------------------------
842
    - added support for half-float image formats
843
    - change in LoadData to allow support for more images
844
      in one stream loading
845
846
  -- 0.17 Changes/Bug Fixes -----------------------------------
847
    - fixed bug in TestFormat which does not recognize many DDS files
848
    - changed pitch/linearsize handling in DDS loading code to
849
      load DDS files produced by NVidia's Photoshop plugin
850
}
851
852
end.
853