ImagingDds.pas 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835
  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. Data := nil;
  290. ImageCount := 1;
  291. FLoadedMipMapCount := 1;
  292. FLoadedDepth := 1;
  293. FLoadedVolume := False;
  294. FLoadedCubeMap := False;
  295. with GetIO, Hdr, Hdr.Desc.PixelFormat do
  296. begin
  297. Read(Handle, @Hdr, SizeOF(Hdr));
  298. {
  299. // Set position to the end of the header (for possible future versions
  300. // ith larger header)
  301. Seek(Handle, Hdr.Desc.Size + SizeOf(Hdr.Magic) - SizeOf(Hdr),
  302. smFromCurrent);
  303. }
  304. SrcFormat := ifUnknown;
  305. NeedsSwapChannels := False;
  306. // Get image data format
  307. if (Flags and DDPF_FOURCC) = DDPF_FOURCC then
  308. begin
  309. // Handle FourCC and large ARGB formats
  310. case FourCC of
  311. D3DFMT_A16B16G16R16: SrcFormat := ifA16B16G16R16;
  312. D3DFMT_R32F: SrcFormat := ifR32F;
  313. D3DFMT_A32B32G32R32F: SrcFormat := ifA32B32G32R32F;
  314. D3DFMT_R16F: SrcFormat := ifR16F;
  315. D3DFMT_A16B16G16R16F: SrcFormat := ifA16B16G16R16F;
  316. FOURCC_DXT1: SrcFormat := ifDXT1;
  317. FOURCC_DXT3: SrcFormat := ifDXT3;
  318. FOURCC_DXT5: SrcFormat := ifDXT5;
  319. end;
  320. end
  321. else if (Flags and DDPF_RGB) = DDPF_RGB then
  322. begin
  323. // Handle RGB formats
  324. if (Flags and DDPF_ALPHAPIXELS) = DDPF_ALPHAPIXELS then
  325. begin
  326. // Handle RGB with alpha formats
  327. case BitCount of
  328. 16:
  329. begin
  330. if MasksEqual(Desc.PixelFormat,
  331. GetFormatInfo(ifA4R4G4B4).PixelFormat) then
  332. SrcFormat := ifA4R4G4B4;
  333. if MasksEqual(Desc.PixelFormat,
  334. GetFormatInfo(ifA1R5G5B5).PixelFormat) then
  335. SrcFormat := ifA1R5G5B5;
  336. end;
  337. 32:
  338. begin
  339. SrcFormat := ifA8R8G8B8;
  340. if BlueMask = $00FF0000 then
  341. NeedsSwapChannels := True;
  342. end;
  343. end;
  344. end
  345. else
  346. begin
  347. // Handle RGB without alpha formats
  348. case BitCount of
  349. 8:
  350. if MasksEqual(Desc.PixelFormat,
  351. GetFormatInfo(ifR3G3B2).PixelFormat) then
  352. SrcFormat := ifR3G3B2;
  353. 16:
  354. begin
  355. if MasksEqual(Desc.PixelFormat,
  356. GetFormatInfo(ifX4R4G4B4).PixelFormat) then
  357. SrcFormat := ifX4R4G4B4;
  358. if MasksEqual(Desc.PixelFormat,
  359. GetFormatInfo(ifX1R5G5B5).PixelFormat) then
  360. SrcFormat := ifX1R5G5B5;
  361. if MasksEqual(Desc.PixelFormat,
  362. GetFormatInfo(ifR5G6B5).PixelFormat) then
  363. SrcFormat := ifR5G6B5;
  364. end;
  365. 24: SrcFormat := ifR8G8B8;
  366. 32:
  367. begin
  368. SrcFormat := ifX8R8G8B8;
  369. if BlueMask = $00FF0000 then
  370. NeedsSwapChannels := True;
  371. end;
  372. end;
  373. end;
  374. end
  375. else if (Flags and DDPF_LUMINANCE) = DDPF_LUMINANCE then
  376. begin
  377. // Handle luminance formats
  378. if (Flags and DDPF_ALPHAPIXELS) = DDPF_ALPHAPIXELS then
  379. begin
  380. // Handle luminance with alpha formats
  381. if BitCount = 16 then
  382. SrcFormat := ifA8Gray8;
  383. end
  384. else
  385. begin
  386. // Handle luminance without alpha formats
  387. case BitCount of
  388. 8: SrcFormat := ifGray8;
  389. 16: SrcFormat := ifGray16;
  390. end;
  391. end;
  392. end
  393. else if (Flags and DDPF_BUMPLUMINANCE) = DDPF_BUMPLUMINANCE then
  394. begin
  395. // Handle mixed bump-luminance formats like D3DFMT_X8L8V8U8
  396. case BitCount of
  397. 32:
  398. if BlueMask = $00FF0000 then
  399. begin
  400. SrcFormat := ifX8R8G8B8; // D3DFMT_X8L8V8U8
  401. NeedsSwapChannels := True;
  402. end;
  403. end;
  404. end
  405. else if (Flags and DDPF_BUMPDUDV) = DDPF_BUMPDUDV then
  406. begin
  407. // Handle bumpmap formats like D3DFMT_Q8W8V8U8
  408. case BitCount of
  409. 16: SrcFormat := ifA8Gray8; // D3DFMT_V8U8
  410. 32:
  411. if AlphaMask = $FF000000 then
  412. begin
  413. SrcFormat := ifA8R8G8B8; // D3DFMT_Q8W8V8U8
  414. NeedsSwapChannels := True;
  415. end;
  416. 64: SrcFormat := ifA16B16G16R16; // D3DFMT_Q16W16V16U16
  417. end;
  418. end;
  419. // If DDS format is not supported we will exit
  420. if SrcFormat = ifUnknown then Exit;
  421. // File contains mipmaps for each subimage.
  422. { Some DDS writers ignore setting proper Caps and Flags so
  423. this check is not usable:
  424. if ((Desc.Caps.Caps1 and DDSCAPS_MIPMAP) = DDSCAPS_MIPMAP) and
  425. ((Desc.Flags and DDSD_MIPMAPCOUNT) = DDSD_MIPMAPCOUNT) then}
  426. if Desc.MipMaps > 1 then
  427. begin
  428. FLoadedMipMapCount := Desc.MipMaps;
  429. ImageCount := Desc.MipMaps;
  430. end;
  431. // File stores volume texture
  432. if ((Desc.Caps.Caps2 and DDSCAPS2_VOLUME) = DDSCAPS2_VOLUME) and
  433. ((Desc.Flags and DDSD_DEPTH) = DDSD_DEPTH) then
  434. begin
  435. FLoadedVolume := True;
  436. FLoadedDepth := Desc.Depth;
  437. ImageCount := GetVolumeLevelCount(Desc.Depth, ImageCount);
  438. end;
  439. // File stores cube texture
  440. if (Desc.Caps.Caps2 and DDSCAPS2_CUBEMAP) = DDSCAPS2_CUBEMAP then
  441. begin
  442. FLoadedCubeMap := True;
  443. I := 0;
  444. if (Desc.Caps.Caps2 and DDSCAPS2_POSITIVEX) = DDSCAPS2_POSITIVEX then Inc(I);
  445. if (Desc.Caps.Caps2 and DDSCAPS2_POSITIVEY) = DDSCAPS2_POSITIVEY then Inc(I);
  446. if (Desc.Caps.Caps2 and DDSCAPS2_POSITIVEZ) = DDSCAPS2_POSITIVEZ then Inc(I);
  447. if (Desc.Caps.Caps2 and DDSCAPS2_NEGATIVEX) = DDSCAPS2_NEGATIVEX then Inc(I);
  448. if (Desc.Caps.Caps2 and DDSCAPS2_NEGATIVEY) = DDSCAPS2_NEGATIVEY then Inc(I);
  449. if (Desc.Caps.Caps2 and DDSCAPS2_NEGATIVEZ) = DDSCAPS2_NEGATIVEZ then Inc(I);
  450. FLoadedDepth := I;
  451. ImageCount := ImageCount * I;
  452. end;
  453. // Allocate and load all images in file
  454. FmtInfo := GetFormatInfo(SrcFormat);
  455. SetLength(Images, ImageCount);
  456. for I := 0 to ImageCount - 1 do
  457. begin
  458. // Compute dimensions of surrent subimage based on texture type and
  459. // number of mipmaps
  460. ComputeSubDimensions(I, Desc.Width, Desc.Height, Desc.MipMaps, Desc.Depth,
  461. FloadedCubeMap, FLoadedVolume, CurrentWidth, CurrentHeight);
  462. NewImage(CurrentWidth, CurrentHeight, SrcFormat, Images[I]);
  463. // Compute the pitch or get if from file if present
  464. UseAsPitch := (Desc.Flags and DDSD_PITCH) = DDSD_PITCH;
  465. UseAsLinear := (Desc.Flags and DDSD_LINEARSIZE) = DDSD_LINEARSIZE;
  466. if (I = 0) and (UseAsPitch or UseAsLinear) then
  467. PitchOrLinear := Desc.PitchOrLinearSize
  468. else
  469. PitchOrLinear := FmtInfo.GetPixelsSize(SrcFormat, CurrentWidth, CurrentHeight);
  470. if UseAsPitch then
  471. LoadSize := CurrentHeight * PitchOrLinear
  472. else
  473. LoadSize := PitchOrLinear;
  474. if not UseAsPitch then
  475. begin
  476. // If DDS does not use Pitch we can simply copy data
  477. Read(Handle, Images[I].Bits, LoadSize)
  478. end
  479. else
  480. try
  481. // If DDS uses Pitch we must load aligned scanlines
  482. // and then remove padding
  483. GetMem(Data, LoadSize);
  484. Read(Handle, Data, LoadSize);
  485. RemovePadBytes(Data, Images[I].Bits, CurrentWidth, CurrentHeight,
  486. FmtInfo.BytesPerPixel, PitchOrLinear);
  487. finally
  488. FreeMem(Data);
  489. end;
  490. if NeedsSwapChannels then
  491. SwapChannels(Images[I], ChannelRed, ChannelBlue);
  492. end;
  493. Result := True;
  494. end;
  495. end;
  496. function TDDSFileFormat.SaveData(Handle: TImagingHandle;
  497. const Images: TDynImageDataArray; Index: LongInt): Boolean;
  498. var
  499. Hdr: TDDSFileHeader;
  500. MainImage, ImageToSave: TImageData;
  501. I, MainIdx, Len, ImageCount: LongInt;
  502. J: LongWord;
  503. FmtInfo: TImageFormatInfo;
  504. MustBeFreed: Boolean;
  505. Is2DTexture, IsCubeMap, IsVolume: Boolean;
  506. MipMapCount, CurrentWidth, CurrentHeight: LongInt;
  507. NeedsResize: Boolean;
  508. NeedsConvert: Boolean;
  509. begin
  510. Result := False;
  511. MainIdx := FFirstIdx;
  512. Len := FLastIdx - MainIdx + 1;
  513. // Some DDS saving rules:
  514. // 2D textures: Len is used as mipmap count (FSaveMipMapCount not used!).
  515. // Cube maps: FSaveDepth * FSaveMipMapCount images are used, if Len is
  516. // smaller than this file is saved as regular 2D texture.
  517. // Volume maps: GetVolumeLevelCount(FSaveDepth, FSaveMipMapCount) images are
  518. // used, if Len is smaller than this file is
  519. // saved as regular 2D texture.
  520. IsCubeMap := FSaveCubeMap;
  521. IsVolume := FSaveVolume;
  522. MipMapCount := FSaveMipMapCount;
  523. if IsCubeMap then
  524. begin
  525. // Check if we have enough images on Input to save cube map
  526. if Len < FSaveDepth * FSaveMipMapCount then
  527. IsCubeMap := False;
  528. end
  529. else if IsVolume then
  530. begin
  531. // Check if we have enough images on Input to save volume texture
  532. if Len < GetVolumeLevelCount(FSaveDepth, FSaveMipMapCount) then
  533. IsVolume := False;
  534. end;
  535. Is2DTexture := not IsCubeMap and not IsVolume;
  536. if Is2DTexture then
  537. begin
  538. // Get number of mipmaps used with 2D texture
  539. MipMapCount := Min(Len, GetNumMipMapLevels(Images[MainIdx].Width, Images[MainIdx].Height));
  540. end;
  541. // we create compatible main image and fill headers
  542. if MakeCompatible(Images[MainIdx], MainImage, MustBeFreed) then
  543. with GetIO, MainImage, Hdr do
  544. try
  545. FmtInfo := GetFormatInfo(Format);
  546. FillChar(Hdr, Sizeof(Hdr), 0);
  547. Magic := DDSMagic;
  548. Desc.Size := SizeOf(Desc);
  549. Desc.Width := Width;
  550. Desc.Height := Height;
  551. Desc.Flags := DDS_SAVE_FLAGS;
  552. Desc.Caps.Caps1 := DDSCAPS_TEXTURE;
  553. Desc.PixelFormat.Size := SizeOf(Desc.PixelFormat);
  554. Desc.PitchOrLinearSize := MainImage.Size;
  555. ImageCount := MipMapCount;
  556. if MipMapCount > 1 then
  557. begin
  558. // Set proper flags if we have some mipmaps to be saved
  559. Desc.Flags := Desc.Flags or DDSD_MIPMAPCOUNT;
  560. Desc.Caps.Caps1 := Desc.Caps.Caps1 or DDSCAPS_MIPMAP;
  561. Desc.MipMaps := MipMapCount;
  562. end;
  563. if IsCubeMap then
  564. begin
  565. // Set proper cube map flags - number of stored faces is taken
  566. // from FSaveDepth
  567. Desc.Caps.Caps1 := Desc.Caps.Caps1 or DDSCAPS_COMPLEX;
  568. Desc.Caps.Caps2 := Desc.Caps.Caps2 or DDSCAPS2_CUBEMAP;
  569. J := DDSCAPS2_POSITIVEX;
  570. for I := 0 to FSaveDepth - 1 do
  571. begin
  572. Desc.Caps.Caps2 := Desc.Caps.Caps2 or J;
  573. J := J shl 1;
  574. end;
  575. ImageCount := FSaveDepth * FSaveMipMapCount;
  576. end
  577. else if IsVolume then
  578. begin
  579. // Set proper flags for volume texture
  580. Desc.Flags := Desc.Flags or DDSD_DEPTH;
  581. Desc.Caps.Caps1 := Desc.Caps.Caps1 or DDSCAPS_COMPLEX;
  582. Desc.Caps.Caps2 := Desc.Caps.Caps2 or DDSCAPS2_VOLUME;
  583. Desc.Depth := FSaveDepth;
  584. ImageCount := GetVolumeLevelCount(FSaveDepth, FSaveMipMapCount);
  585. end;
  586. // Now we set DDS pixel format for main image
  587. if FmtInfo.IsSpecial or FmtInfo.IsFloatingPoint or
  588. (FmtInfo.BytesPerPixel > 4) then
  589. begin
  590. Desc.PixelFormat.Flags := DDPF_FOURCC;
  591. case Format of
  592. ifA16B16G16R16: Desc.PixelFormat.FourCC := D3DFMT_A16B16G16R16;
  593. ifR32F: Desc.PixelFormat.FourCC := D3DFMT_R32F;
  594. ifA32B32G32R32F: Desc.PixelFormat.FourCC := D3DFMT_A32B32G32R32F;
  595. ifR16F: Desc.PixelFormat.FourCC := D3DFMT_R16F;
  596. ifA16B16G16R16F: Desc.PixelFormat.FourCC := D3DFMT_A16B16G16R16F;
  597. ifDXT1: Desc.PixelFormat.FourCC := FOURCC_DXT1;
  598. ifDXT3: Desc.PixelFormat.FourCC := FOURCC_DXT3;
  599. ifDXT5: Desc.PixelFormat.FourCC := FOURCC_DXT5;
  600. end;
  601. end
  602. else if FmtInfo.HasGrayChannel then
  603. begin
  604. Desc.PixelFormat.Flags := DDPF_LUMINANCE;
  605. Desc.PixelFormat.BitCount := FmtInfo.BytesPerPixel * 8;
  606. case Format of
  607. ifGray8: Desc.PixelFormat.RedMask := 255;
  608. ifGray16: Desc.PixelFormat.RedMask := 65535;
  609. ifA8Gray8:
  610. begin
  611. Desc.PixelFormat.Flags := Desc.PixelFormat.Flags or DDPF_ALPHAPIXELS;
  612. Desc.PixelFormat.RedMask := 255;
  613. Desc.PixelFormat.AlphaMask := 65280;
  614. end;
  615. end;
  616. end
  617. else
  618. begin
  619. Desc.PixelFormat.Flags := DDPF_RGB;
  620. Desc.PixelFormat.BitCount := FmtInfo.BytesPerPixel * 8;
  621. if FmtInfo.HasAlphaChannel then
  622. begin
  623. Desc.PixelFormat.Flags := Desc.PixelFormat.Flags or DDPF_ALPHAPIXELS;
  624. Desc.PixelFormat.AlphaMask := $FF000000;
  625. end;
  626. if FmtInfo.BytesPerPixel > 2 then
  627. begin
  628. Desc.PixelFormat.RedMask := $00FF0000;
  629. Desc.PixelFormat.GreenMask := $0000FF00;
  630. Desc.PixelFormat.BlueMask := $000000FF;
  631. end
  632. else
  633. begin
  634. Desc.PixelFormat.AlphaMask := FmtInfo.PixelFormat.ABitMask;
  635. Desc.PixelFormat.RedMask := FmtInfo.PixelFormat.RBitMask;
  636. Desc.PixelFormat.GreenMask := FmtInfo.PixelFormat.GBitMask;
  637. Desc.PixelFormat.BlueMask := FmtInfo.PixelFormat.BBitMask;
  638. end;
  639. end;
  640. // Header and main image are written to output
  641. Write(Handle, @Hdr, SizeOf(Hdr));
  642. Write(Handle, MainImage.Bits, MainImage.Size);
  643. // Write the rest of the images and convert them to
  644. // the same format as main image if necessary and ensure proper mipmap
  645. // simensions too.
  646. for I := MainIdx + 1 to MainIdx + ImageCount - 1 do
  647. begin
  648. // Get proper dimensions for this level
  649. ComputeSubDimensions(I, Desc.Width, Desc.Height, Desc.MipMaps, Desc.Depth,
  650. IsCubeMap, IsVolume, CurrentWidth, CurrentHeight);
  651. // Check if input image for this level has the right size and format
  652. NeedsResize := not ((Images[I].Width = CurrentWidth) and (Images[I].Height = CurrentHeight));
  653. NeedsConvert := not (Images[I].Format = Format);
  654. if NeedsResize or NeedsConvert then
  655. begin
  656. // Input image must be resized or converted to different format
  657. // to become valid mipmap level
  658. InitImage(ImageToSave);
  659. CloneImage(Images[I], ImageToSave);
  660. if NeedsConvert then
  661. ConvertImage(ImageToSave, Format);
  662. if NeedsResize then
  663. ResizeImage(ImageToSave, CurrentWidth, CurrentHeight, rfBilinear);
  664. end
  665. else
  666. // Input image can be used without any changes
  667. ImageToSave := Images[I];
  668. // Write level data and release temp image if necessary
  669. Write(Handle, ImageToSave.Bits, ImageToSave.Size);
  670. if Images[I].Bits <> ImageToSave.Bits then
  671. FreeImage(ImageToSave);
  672. end;
  673. Result := True;
  674. finally
  675. if MustBeFreed then
  676. FreeImage(MainImage);
  677. end;
  678. end;
  679. procedure TDDSFileFormat.ConvertToSupported(var Image: TImageData;
  680. const Info: TImageFormatInfo);
  681. var
  682. ConvFormat: TImageFormat;
  683. begin
  684. if Info.IsIndexed or Info.IsSpecial then
  685. // convert indexed and unsupported special formatd to A8R8G8B8
  686. ConvFormat := ifA8R8G8B8
  687. else if Info.IsFloatingPoint then
  688. begin
  689. if Info.Format = ifA16R16G16B16F then
  690. // only swap channels here
  691. ConvFormat := ifA16B16G16R16F
  692. else
  693. // convert other floating point formats to A32B32G32R32F
  694. ConvFormat := ifA32B32G32R32F
  695. end
  696. else if Info.HasGrayChannel then
  697. begin
  698. if Info.HasAlphaChannel then
  699. // convert grayscale with alpha to A8Gray8
  700. ConvFormat := ifA8Gray8
  701. else if Info.BytesPerPixel = 1 then
  702. // convert 8bit grayscale to Gray8
  703. ConvFormat := ifGray8
  704. else
  705. // convert 16-64bit grayscales to Gray16
  706. ConvFormat := ifGray16;
  707. end
  708. else if Info.BytesPerPixel > 4 then
  709. ConvFormat := ifA16B16G16R16
  710. else if Info.HasAlphaChannel then
  711. // convert the other images with alpha channel to A8R8G8B8
  712. ConvFormat := ifA8R8G8B8
  713. else
  714. // convert the other formats to X8R8G8B8
  715. ConvFormat := ifX8R8G8B8;
  716. ConvertImage(Image, ConvFormat);
  717. end;
  718. function TDDSFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
  719. var
  720. Hdr: TDDSFileHeader;
  721. ReadCount: LongInt;
  722. begin
  723. Result := False;
  724. if Handle <> nil then
  725. with GetIO do
  726. begin
  727. ReadCount := Read(Handle, @Hdr, SizeOf(Hdr));
  728. Seek(Handle, -ReadCount, smFromCurrent);
  729. Result := (Hdr.Magic = DDSMagic) and (ReadCount = SizeOf(Hdr)) and
  730. ((Hdr.Desc.Caps.Caps1 and DDSCAPS_TEXTURE) = DDSCAPS_TEXTURE);
  731. end;
  732. end;
  733. initialization
  734. RegisterImageFileFormat(TDDSFileFormat);
  735. {
  736. File Notes:
  737. -- TODOS ----------------------------------------------------
  738. - nothing now
  739. -- 0.21 Changes/Bug Fixes -----------------------------------
  740. - Changed saving behaviour a bit: mipmaps are inlcuded automatically for
  741. 2D textures if input image array has more than 1 image (no need to
  742. set SaveMipMapCount manually).
  743. - Mipmap levels are now saved with proper dimensions when saving DDS files.
  744. - Made some changes to not be so strict when loading DDS files.
  745. Many programs seem to save them in non-standard format
  746. (by MS DDS File Reference).
  747. - Added missing ifX8R8G8B8 to SupportedFormats, MakeCompatible failed
  748. when image was converted to this format (inside).
  749. - MakeCompatible method moved to base class, put ConvertToSupported here.
  750. GetSupportedFormats removed, it is now set in constructor.
  751. - Fixed bug that sometimes saved non-standard DDS files and another
  752. one that caused crash when these files were loaded.
  753. - Changed extensions to filename masks.
  754. - Changed SaveData, LoadData, and MakeCompatible methods according
  755. to changes in base class in Imaging unit.
  756. -- 0.19 Changes/Bug Fixes -----------------------------------
  757. - added support for half-float image formats
  758. - change in LoadData to allow support for more images
  759. in one stream loading
  760. -- 0.17 Changes/Bug Fixes -----------------------------------
  761. - fixed bug in TestFormat which does not recognize many DDS files
  762. - changed pitch/linearsize handling in DDS loading code to
  763. load DDS files produced by NVidia's Photoshop plugin
  764. }
  765. end.