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 |