ElderImageryCif.pas 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. {
  2. Vampyre Imaging Library
  3. by Marek Mauder
  4. https://github.com/galfar/imaginglib
  5. https://imaginglib.sourceforge.io
  6. - - - - -
  7. This Source Code Form is subject to the terms of the Mozilla Public
  8. License, v. 2.0. If a copy of the MPL was not distributed with this
  9. file, You can obtain one at https://mozilla.org/MPL/2.0.
  10. }
  11. { This unit contains image format loader/saver for Daggerfall
  12. multi-image format CIF.}
  13. unit ElderImageryCif;
  14. {$I ImagingOptions.inc}
  15. interface
  16. uses
  17. ImagingTypes, Imaging, ImagingIO, ElderImagery;
  18. type
  19. { Class for loading and saving of multi-images in CIF format. It is
  20. 8 bit indexed format found in Daggerfall. It is basically a sequence of
  21. images in IMG (see TIMGFileFormat) stored in one file (with exception
  22. of Weapo*.cif files which are little bit more complex). As with IMG files
  23. CIF files can be RLE compressed and there are also special CIFs without header.
  24. Total number of frames in file is known after the whole file was parsed
  25. so exact file size must be known prior to loading.}
  26. TCIFFileFormat = class(TElderFileFormat)
  27. protected
  28. procedure Define; override;
  29. function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
  30. OnlyFirstLevel: Boolean): Boolean; override;
  31. function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
  32. Index: LongInt): Boolean; override;
  33. end;
  34. const
  35. { Info about special CIFs without header.}
  36. NoHeaderCIFInfos: array[0..6] of TNoHeaderFileInfo = (
  37. (Size: 2601; Width: 17; Height: 17), // MPOP.RCI
  38. (Size: 3168; Width: 44; Height: 9), // NOTE.RCI
  39. (Size: 4356; Width: 22; Height: 22), // SPOP.RCI
  40. (Size: 10752; Width: 32; Height: 16), // BUTTONS.RCI
  41. (Size: 49152; Width: 64; Height: 64), // CHLD00I0.RCI
  42. (Size: 249856; Width: 64; Height: 64), // FACES.CIF
  43. (Size: 2060295; Width: 64; Height: 64)); // TFAC00I0.RCI
  44. implementation
  45. const
  46. SCIFFormatName = 'Daggerfall MultiImage';
  47. SCIFMasks = '*.cif,*.rci';
  48. resourcestring
  49. SInvalidImageSize = 'Size of image in IMG/CIF format cannot exceed 65535 bytes. %s';
  50. type
  51. { Header for CIF group files.}
  52. TCIFGroup = packed record
  53. Width: Word;
  54. Height: Word;
  55. XOff: Word;
  56. YOff: Word;
  57. Unk: Word;
  58. ImageSize: Word; // Size of Image data (but not always)
  59. Offsets: array[0..31] of Word; // Offsets from beginning of header to
  60. // image datas. Last offset points to next
  61. // group header
  62. end;
  63. { TCIFFileFormat class implementation }
  64. procedure TCIFFileFormat.Define;
  65. begin
  66. inherited;
  67. FName := SCIFFormatName;
  68. AddMasks(SCIFMasks);
  69. end;
  70. function TCIFFileFormat.LoadData(Handle: TImagingHandle;
  71. var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
  72. var
  73. Hdr: TImgHeader;
  74. Group: TCIFGroup;
  75. Data: Pointer;
  76. IsWeapon, ISW9, IsStandard, IsFirst: Boolean;
  77. InputSize, I, FrameWidth, FrameHeight, OldPos, Index, BufferSize: LongInt;
  78. HasHeader: Boolean;
  79. function AddImage(Width, Height: LongInt): LongInt;
  80. begin
  81. Result := Length(Images);
  82. SetLength(Images, Length(Images) + 1);
  83. NewImage(Width, Height, ifIndex8, Images[Result]);
  84. Move(FARGBPalette[0], Images[Result].Palette[0], Length(FPalette) * SizeOf(TColor32Rec));
  85. end;
  86. begin
  87. SetLength(Images, 0);
  88. with GetIO do
  89. begin
  90. InputSize := GetInputSize(GetIO, Handle);
  91. HasHeader := True;
  92. IsWeapon := False;
  93. IsW9 := False;
  94. IsFirst := True;
  95. FrameWidth := 0;
  96. FrameHeight := 0;
  97. // Check if this is one of special CIF with no header
  98. I := FindNoHeaderInfo(InputSize, NoHeaderCIFInfos);
  99. if I >= 0 then
  100. begin
  101. // It is no-header CIF
  102. FrameWidth := NoHeaderCIFInfos[I].Width;
  103. FrameHeight := NoHeaderCIFInfos[I].Height;
  104. HasHeader := False;
  105. end;
  106. if HasHeader then
  107. begin
  108. OldPos := Tell(Handle);
  109. // CIF has header so use its values
  110. Read(Handle, @Hdr, SizeOf(Hdr));
  111. if Hdr.Unk = $15 then
  112. begin
  113. // This file is weapon09.cif (shooting arrows)
  114. IsWeapon := True;
  115. IsW9 := True;
  116. end;
  117. if Tell(Handle) + Hdr.ImageSize < InputSize then
  118. begin
  119. Seek(Handle, Hdr.ImageSize, smFromCurrent);
  120. Read(Handle, @Group, SizeOf(Group));
  121. if Group.Offsets[0] = 76 then
  122. // CIF is regular weapon file
  123. IsWeapon := True;
  124. end;
  125. Seek(Handle, OldPos, smFromBeginning);
  126. end;
  127. IsStandard := HasHeader and (not IsWeapon);
  128. while not Eof(Handle) do
  129. begin
  130. if IsStandard then
  131. begin
  132. // Handle CIFs in standard format with header
  133. Read(Handle, @Hdr, SizeOf(Hdr));
  134. Index := AddImage(Hdr.Width, Hdr.Height);
  135. if Hdr.Unk <> 2 then
  136. begin
  137. // Read uncompressed data
  138. Read(Handle, Images[Index].Bits, Hdr.ImageSize);
  139. end
  140. else
  141. begin
  142. GetMem(Data, Hdr.ImageSize);
  143. try
  144. // Read RLE compressed data
  145. Read(Handle, Data, Hdr.ImageSize);
  146. DagRLEDecode(Data, Images[Index].Size, Images[Index].Bits);
  147. finally
  148. FreeMem(Data);
  149. end;
  150. end;
  151. end
  152. else if not HasHeader then
  153. begin
  154. // Handle CIFs in standard format without header
  155. if Tell(Handle) + FrameWidth * FrameHeight <= InputSize then
  156. begin
  157. Index := AddImage(FrameWidth, FrameHeight);
  158. Read(Handle, Images[Index].Bits, Images[Index].Size);
  159. end
  160. else
  161. Break;
  162. end
  163. else if IsWeapon then
  164. begin
  165. // Handle CIFs with weapon animations
  166. if IsFirst and (not IsW9) then
  167. begin
  168. // First frame is std IMG file, next ones are not
  169. // but if IsW9 is true this first frame is missing
  170. Read(Handle, @Hdr, SizeOf(Hdr));
  171. Index := AddImage(Hdr.Width, Hdr.Height);
  172. Read(Handle, Images[Index].Bits, Images[Index].Size);
  173. IsFirst := False;
  174. end
  175. else
  176. begin
  177. OldPos := Tell(Handle);
  178. // Read next group
  179. Read(Handle, @Group, SizeOf(Group));
  180. // Read images in group
  181. I := 0;
  182. while Group.Offsets[I] <> 0 do
  183. begin
  184. BufferSize := Group.Offsets[I + 1] - Group.Offsets[I];
  185. if BufferSize < 0 then
  186. BufferSize := Group.Offsets[31] - Group.Offsets[I];
  187. Seek(Handle, OldPos + Group.Offsets[I], smFromBeginning);
  188. Index := AddImage(Group.Width, Group.Height);
  189. // Read current image from current group and decode it
  190. GetMem(Data, BufferSize);
  191. try
  192. Read(Handle, Data, BufferSize);
  193. DagRLEDecode(Data, Images[Index].Size, Images[Index].Bits);
  194. Inc(I);
  195. finally
  196. FreeMem(Data);
  197. end;
  198. end;
  199. Seek(Handle, OldPos + Group.Offsets[31], smFromBeginning);
  200. end;
  201. end;
  202. end;
  203. Result := True;
  204. end;
  205. end;
  206. function TCIFFileFormat.SaveData(Handle: TImagingHandle;
  207. const Images: TDynImageDataArray; Index: LongInt): Boolean;
  208. var
  209. Hdr: TImgHeader;
  210. ImageToSave: TImageData;
  211. MustBeFreed: Boolean;
  212. I: LongInt;
  213. begin
  214. Result := False;
  215. for I := FFirstIdx to FLastIdx do
  216. begin
  217. if MakeCompatible(Images[I], ImageToSave, MustBeFreed) then
  218. with GetIO, ImageToSave do
  219. try
  220. FillChar(Hdr, SizeOf(Hdr), 0);
  221. Hdr.Width := Width;
  222. Hdr.Height := Height;
  223. // Hdr.ImageSize is Word so max size of image in bytes can be 65535
  224. if Width * Height > High(Word) then
  225. RaiseImaging(SInvalidImageSize, [ImageToStr(ImageToSave)]);
  226. Hdr.ImageSize := Width * Height;
  227. Write(Handle, @Hdr, SizeOf(Hdr));
  228. Write(Handle, Bits, Hdr.ImageSize);
  229. finally
  230. if MustBeFreed then
  231. FreeImage(ImageToSave);
  232. end
  233. else
  234. Exit;
  235. end;
  236. Result := True;
  237. end;
  238. {
  239. File Notes:
  240. -- TODOS ----------------------------------------------------
  241. - nothing now
  242. -- 0.21 Changes/Bug Fixes -----------------------------------
  243. - Initial version created based on my older code (fixed few things).
  244. }
  245. end.