ElderImageryImg.pas 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  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 IMG file format used
  12. in Daggerfall and other old Bethesda games.}
  13. unit ElderImageryImg;
  14. {$I ImagingOptions.inc}
  15. interface
  16. uses
  17. ImagingTypes, Imaging, ImagingIO, ElderImagery;
  18. type
  19. { Class for loading and saving of images in IMG format. It is
  20. 8 bit indexed format found in Daggerfall, Arena, Terminator: FS,
  21. and maybe other old Bethesda games. Files can be RLE compressed
  22. and may contain palette although most images use external palettes.
  23. Some files have no header at all so exact file size must be known
  24. prior to loading (otherwise no-header files wont be recognized or whole
  25. image could be identified as CIF as they use the same header).}
  26. TIMGFileFormat = 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 images without header.}
  36. NoHeaderIMGInfos: array[0..18] of TNoHeaderFileInfo = (
  37. (Size: 64; Width: 8; Height: 8), // Arena file
  38. (Size: 90; Width: 9; Height: 10), // Arena file
  39. (Size: 128; Width: 8; Height: 16), // Arena file
  40. (Size: 720; Width: 9; Height: 80),
  41. (Size: 990; Width: 45; Height: 22),
  42. (Size: 1720; Width: 43; Height: 40),
  43. (Size: 2140; Width: 107; Height: 20),
  44. (Size: 2916; Width: 81; Height: 36),
  45. (Size: 3200; Width: 40; Height: 80),
  46. (Size: 3938; Width: 179; Height: 22),
  47. (Size: 4096; Width: 64; Height: 64), // Textures from TES: Arena
  48. (Size: 4280; Width: 107; Height: 40),
  49. (Size: 4508; Width: 322; Height: 14),
  50. (Size: 20480; Width: 320; Height: 64),
  51. (Size: 26496; Width: 184; Height: 144),
  52. (Size: 64000; Width: 320; Height: 200),
  53. (Size: 64768; Width: 320; Height: 200), // These contain palette
  54. (Size: 68800; Width: 320; Height: 215),
  55. (Size: 112128; Width: 512; Height: 219));
  56. implementation
  57. const
  58. SIMGFormatName = 'Daggerfall Image';
  59. SIMGMasks = '*.img';
  60. resourcestring
  61. SInvalidImageSize = 'Size of image in IMG format cannot exceed 65535 bytes. %s';
  62. { TIMGFileFormat class implementation }
  63. procedure TIMGFileFormat.Define;
  64. begin
  65. inherited;
  66. FFeatures := [ffLoad, ffSave];
  67. FName := SIMGFormatName;
  68. AddMasks(SIMGMasks);
  69. end;
  70. function TIMGFileFormat.LoadData(Handle: TImagingHandle;
  71. var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
  72. var
  73. Hdr: TImgHeader;
  74. PalUsed: TPalette24Size256;
  75. Data: Pointer;
  76. IsRLE: Boolean;
  77. InputSize, I: LongInt;
  78. procedure SetSize(W, H: LongInt);
  79. begin
  80. Images[0].Width := W;
  81. Images[0].Height := H;
  82. Images[0].Size := W * H;
  83. end;
  84. begin
  85. Result := False;
  86. SetLength(Images, 1);
  87. with GetIO, Images[0] do
  88. begin
  89. InputSize := GetInputSize(GetIO, Handle);
  90. Format := ifIndex8;
  91. IsRLE := False;
  92. // Check if this is one of special images with no header
  93. I := FindNoHeaderInfo(InputSize, NoHeaderIMGInfos);
  94. if I >= 0 then
  95. begin
  96. // It is no-header image
  97. NewImage(NoHeaderIMGInfos[I].Width, NoHeaderIMGInfos[I].Height, ifIndex8, Images[0]);
  98. end
  99. else
  100. begin
  101. // Image has header so use its values
  102. Read(Handle, @Hdr, SizeOf(Hdr));
  103. NewImage(Hdr.Width, Hdr.Height, ifIndex8, Images[0]);
  104. IsRLE := Hdr.Unk = 2;
  105. end;
  106. if (Hdr.Unk = 260) or (Hdr.Unk = 264) then
  107. begin
  108. // Compressed data from Arena:
  109. // compression algorithm is unknown to me now
  110. // if Unk = 264 then after header is word size of original data
  111. // if Unk = 260 no size after head
  112. Exit;
  113. end;
  114. if not IsRLE then
  115. begin
  116. // Read uncompressed data
  117. GetMem(Bits, Size);
  118. Read(Handle, Bits, Size);
  119. end
  120. else
  121. begin
  122. GetMem(Data, Hdr.ImageSize);
  123. try
  124. // Read compressed data
  125. Read(Handle, Data, Hdr.ImageSize);
  126. DagRLEDecode(Data, Size, Bits);
  127. finally
  128. FreeMem(Data);
  129. end;
  130. end;
  131. // Palette handling
  132. GetMem(Palette, 256 * SizeOf(TColor32Rec));
  133. if (InputSize = Tell(Handle) + 768) then
  134. begin
  135. // Some IMG files has embedded palette
  136. Read(Handle, @PalUsed, 768);
  137. for I := Low(PalUsed) to High(PalUsed) do
  138. begin
  139. Palette[I].A := $FF;
  140. Palette[I].R := PalUsed[I].B;
  141. Palette[I].G := PalUsed[I].G;
  142. Palette[I].B := PalUsed[I].R;
  143. end;
  144. Palette[0].A := 0;
  145. end
  146. else
  147. Move(FARGBPalette[0], Palette[0], Length(FPalette) * SizeOf(TColor32Rec));
  148. Result := True;
  149. end;
  150. end;
  151. function TIMGFileFormat.SaveData(Handle: TImagingHandle;
  152. const Images: TDynImageDataArray; Index: LongInt): Boolean;
  153. var
  154. Hdr: TImgHeader;
  155. ImageToSave: TImageData;
  156. MustBeFreed: Boolean;
  157. begin
  158. Result := False;
  159. if MakeCompatible(Images[Index], ImageToSave, MustBeFreed) then
  160. with GetIO, ImageToSave do
  161. try
  162. FillChar(Hdr, SizeOf(Hdr), 0);
  163. Hdr.Width := Width;
  164. Hdr.Height := Height;
  165. // Hdr.ImageSize is Word so max size of image in bytes can be 65535
  166. if Width * Height > High(Word) then
  167. RaiseImaging(SInvalidImageSize, [ImageToStr(ImageToSave)]);
  168. Hdr.ImageSize := Width * Height;
  169. Write(Handle, @Hdr, SizeOf(Hdr));
  170. Write(Handle, Bits, Hdr.ImageSize);
  171. Result := True;
  172. finally
  173. if MustBeFreed then
  174. FreeImage(ImageToSave);
  175. end;
  176. end;
  177. {
  178. File Notes:
  179. -- TODOS ----------------------------------------------------
  180. - nothing now
  181. -- 0.21 Changes/Bug Fixes -----------------------------------
  182. - Initial version created based on my older code (fixed few things).
  183. }
  184. end.