ImagingDds.pas 31 KB

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