ImagingTarga.pas 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  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 Targa images.}
  12. unit ImagingTarga;
  13. {$I ImagingOptions.inc}
  14. interface
  15. uses
  16. ImagingTypes, Imaging, ImagingFormats, ImagingUtility;
  17. type
  18. { Class for loading and saving Truevision Targa images.
  19. It can load/save 8bit indexed or grayscale, 16 bit RGB or grayscale,
  20. 24 bit RGB and 32 bit ARGB images with or without RLE compression.}
  21. TTargaFileFormat = class(TImageFileFormat)
  22. protected
  23. FUseRLE: LongBool;
  24. procedure Define; override;
  25. function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
  26. OnlyFirstLevel: Boolean): Boolean; override;
  27. function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
  28. Index: LongInt): Boolean; override;
  29. procedure ConvertToSupported(var Image: TImageData;
  30. const Info: TImageFormatInfo); override;
  31. public
  32. function TestFormat(Handle: TImagingHandle): Boolean; override;
  33. published
  34. { Controls that RLE compression is used during saving. Accessible trough
  35. ImagingTargaRLE option.}
  36. property UseRLE: LongBool read FUseRLE write FUseRLE;
  37. end;
  38. implementation
  39. const
  40. STargaFormatName = 'Truevision Targa Image';
  41. STargaMasks = '*.tga';
  42. TargaSupportedFormats: TImageFormats = [ifIndex8, ifGray8, ifA1R5G5B5,
  43. ifR8G8B8, ifA8R8G8B8];
  44. TargaDefaultRLE = False;
  45. const
  46. STargaSignature = 'TRUEVISION-XFILE';
  47. type
  48. { Targa file header }
  49. TTargaHeader = packed record
  50. IDLength: Byte;
  51. ColorMapType: Byte;
  52. ImageType: Byte;
  53. ColorMapOff: Word;
  54. ColorMapLength: Word;
  55. ColorEntrySize: Byte;
  56. XOrg: SmallInt;
  57. YOrg: SmallInt;
  58. Width: SmallInt;
  59. Height: SmallInt;
  60. PixelSize: Byte;
  61. Desc: Byte;
  62. end;
  63. { Footer at the end of TGA file }
  64. TTargaFooter = packed record
  65. ExtOff: UInt32; // Extension Area Offset
  66. DevDirOff: UInt32; // Developer Directory Offset
  67. Signature: TChar16; // TRUEVISION-XFILE
  68. Reserved: Byte; // ASCII period '.'
  69. NullChar: Byte; // 0
  70. end;
  71. { TTargaFileFormat class implementation }
  72. procedure TTargaFileFormat.Define;
  73. begin
  74. inherited;
  75. FName := STargaFormatName;
  76. FFeatures := [ffLoad, ffSave];
  77. FSupportedFormats := TargaSupportedFormats;
  78. FUseRLE := TargaDefaultRLE;
  79. AddMasks(STargaMasks);
  80. RegisterOption(ImagingTargaRLE, @FUseRLE);
  81. end;
  82. function TTargaFileFormat.LoadData(Handle: TImagingHandle;
  83. var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
  84. var
  85. Hdr: TTargaHeader;
  86. Foo: TTargaFooter;
  87. FooterFound, ExtFound: Boolean;
  88. I, PSize, PalSize: Integer;
  89. Pal: Pointer;
  90. FmtInfo: TImageFormatInfo;
  91. WordValue: Word;
  92. procedure LoadRLE;
  93. var
  94. I: Integer;
  95. CurrentPixel, CountPixels: NativeInt;
  96. Bpp, Rle: Byte;
  97. Dest, Src: PByte;
  98. Buffer: TDynByteArray;
  99. BufSize: NativeInt;
  100. BytesConsumedFromBuffer, SeekOffset: NativeInt;
  101. begin
  102. with GetIO, Images[0] do
  103. begin
  104. // Allocates buffer large enough to hold the worst case
  105. // RLE compressed data and reads then from input
  106. BufSize := Width * Height * FmtInfo.BytesPerPixel;
  107. BufSize := BufSize + BufSize div 2 + 1;
  108. SetLength(Buffer, BufSize);
  109. BufSize := Read(Handle, Buffer, BufSize);
  110. Src := @Buffer[0];
  111. Dest := Bits;
  112. CountPixels := Width * Height;
  113. Bpp := FmtInfo.BytesPerPixel;
  114. CurrentPixel := 0;
  115. while CurrentPixel < CountPixels do
  116. begin
  117. Rle := Src^;
  118. Inc(Src);
  119. if Rle < 128 then
  120. begin
  121. // Process uncompressed pixel
  122. Rle := Rle + 1;
  123. CurrentPixel := CurrentPixel + Rle;
  124. for I := 0 to Rle - 1 do
  125. begin
  126. // Copy pixel from src to dest
  127. case Bpp of
  128. 1: Dest^ := Src^;
  129. 2: PWord(Dest)^ := PWord(Src)^;
  130. 3: PColor24Rec(Dest)^ := PColor24Rec(Src)^;
  131. 4: PUInt32(Dest)^ := PUInt32(Src)^;
  132. end;
  133. Inc(Src, Bpp);
  134. Inc(Dest, Bpp);
  135. end;
  136. end
  137. else
  138. begin
  139. // Process compressed pixels
  140. Rle := Rle - 127;
  141. CurrentPixel := CurrentPixel + Rle;
  142. // Copy one pixel from src to dest (many times there)
  143. for I := 0 to Rle - 1 do
  144. begin
  145. case Bpp of
  146. 1: Dest^ := Src^;
  147. 2: PWord(Dest)^ := PWord(Src)^;
  148. 3: PColor24Rec(Dest)^ := PColor24Rec(Src)^;
  149. 4: PUInt32(Dest)^ := PUInt32(Src)^;
  150. end;
  151. Inc(Dest, Bpp);
  152. end;
  153. Inc(Src, Bpp);
  154. end;
  155. end;
  156. // Set position in source to the real end of compressed data
  157. BytesConsumedFromBuffer := PtrUInt(Src) - PtrUInt(Buffer);
  158. SeekOffset := BytesConsumedFromBuffer - BufSize;
  159. Assert(SeekOffset <= 0);
  160. Seek(Handle, SeekOffset, smFromCurrent);
  161. end;
  162. end;
  163. begin
  164. SetLength(Images, 1);
  165. with GetIO, Images[0] do
  166. begin
  167. // Read targa header
  168. Read(Handle, @Hdr, SizeOf(Hdr));
  169. // Skip image ID info
  170. Seek(Handle, Hdr.IDLength, smFromCurrent);
  171. // Determine image format
  172. Format := ifUnknown;
  173. case Hdr.ImageType of
  174. 1, 9: Format := ifIndex8;
  175. 2, 10: case Hdr.PixelSize of
  176. 15: Format := ifX1R5G5B5;
  177. 16: Format := ifA1R5G5B5;
  178. 24: Format := ifR8G8B8;
  179. 32: Format := ifA8R8G8B8;
  180. end;
  181. 3, 11: Format := ifGray8;
  182. end;
  183. // Format was not assigned by previous testing (it should be in
  184. // well formed Targas), so format which reflect the bit depth is selected
  185. if Format = ifUnknown then
  186. case Hdr.PixelSize of
  187. 8: Format := ifGray8;
  188. 15: Format := ifX1R5G5B5;
  189. 16: Format := ifA1R5G5B5;
  190. 24: Format := ifR8G8B8;
  191. 32: Format := ifA8R8G8B8;
  192. end;
  193. NewImage(Hdr.Width, Hdr.Height, Format, Images[0]);
  194. FmtInfo := GetFormatInfo(Format);
  195. if (Hdr.ColorMapType = 1) and (Hdr.ImageType in [1, 9]) then
  196. begin
  197. // Read palette
  198. PSize := Hdr.ColorMapLength * (Hdr.ColorEntrySize shr 3);
  199. GetMem(Pal, PSize);
  200. try
  201. Read(Handle, Pal, PSize);
  202. // Process palette
  203. PalSize := Iff(Hdr.ColorMapLength > FmtInfo.PaletteEntries,
  204. FmtInfo.PaletteEntries, Hdr.ColorMapLength);
  205. for I := 0 to PalSize - 1 do
  206. case Hdr.ColorEntrySize of
  207. 24:
  208. with Palette[I] do
  209. begin
  210. A := $FF;
  211. R := PPalette24(Pal)[I].R;
  212. G := PPalette24(Pal)[I].G;
  213. B := PPalette24(Pal)[I].B;
  214. end;
  215. // I've never seen tga with these palettes so they are untested
  216. 16:
  217. with Palette[I] do
  218. begin
  219. A := (PWordArray(Pal)[I] and $8000) shr 12;
  220. R := (PWordArray(Pal)[I] and $FC00) shr 7;
  221. G := (PWordArray(Pal)[I] and $03E0) shr 2;
  222. B := (PWordArray(Pal)[I] and $001F) shl 3;
  223. end;
  224. 32:
  225. with Palette[I] do
  226. begin
  227. A := PPalette32(Pal)[I].A;
  228. R := PPalette32(Pal)[I].R;
  229. G := PPalette32(Pal)[I].G;
  230. B := PPalette32(Pal)[I].B;
  231. end;
  232. end;
  233. finally
  234. FreeMemNil(Pal);
  235. end;
  236. end;
  237. case Hdr.ImageType of
  238. 0, 1, 2, 3:
  239. // Load uncompressed mode images
  240. Read(Handle, Bits, Size);
  241. 9, 10, 11:
  242. // Load RLE compressed mode images
  243. LoadRLE;
  244. end;
  245. // Check if there is alpha channel present in A1R5GB5 images, if it is not
  246. // change format to X1R5G5B5
  247. if Format = ifA1R5G5B5 then
  248. begin
  249. if not Has16BitImageAlpha(Width * Height, Bits) then
  250. Format := ifX1R5G5B5;
  251. end;
  252. // We must find true end of file and set input's position to it.
  253. // Some paint programs appends extra info at the end of Targas,
  254. // some of them multiple times (PSP Pro 8)
  255. repeat
  256. ExtFound := False;
  257. FooterFound := False;
  258. if Read(Handle, @WordValue, 2) = 2 then
  259. begin
  260. // 495 = size of Extension Area
  261. if WordValue = 495 then
  262. begin
  263. Seek(Handle, 493, smFromCurrent);
  264. ExtFound := True;
  265. end
  266. else
  267. Seek(Handle, -2, smFromCurrent);
  268. end;
  269. if Read(Handle, @Foo, SizeOf(Foo)) = SizeOf(Foo) then
  270. begin
  271. if Foo.Signature = STargaSignature then
  272. FooterFound := True
  273. else
  274. Seek(Handle, -SizeOf(Foo), smFromCurrent);
  275. end;
  276. until (not ExtFound) and (not FooterFound);
  277. // Some editors save Targas flipped
  278. if Hdr.Desc < 31 then
  279. FlipImage(Images[0]);
  280. Result := True;
  281. end;
  282. end;
  283. function TTargaFileFormat.SaveData(Handle: TImagingHandle;
  284. const Images: TDynImageDataArray; Index: LongInt): Boolean;
  285. var
  286. I: LongInt;
  287. Hdr: TTargaHeader;
  288. FmtInfo: TImageFormatInfo;
  289. Pal: PPalette24;
  290. ImageToSave: TImageData;
  291. MustBeFreed: Boolean;
  292. procedure SaveRLE;
  293. var
  294. DestBuffer: TDynByteArray;
  295. WidthBytes, Written, I, Total, DestSize: LongInt;
  296. function CountDiff(Data: PByte; Bpp, PixelCount: Longint): LongInt;
  297. var
  298. Pixel: UInt32;
  299. NextPixel: UInt32;
  300. N: LongInt;
  301. begin
  302. N := 0;
  303. Pixel := 0;
  304. NextPixel := 0;
  305. if PixelCount = 1 then
  306. begin
  307. Result := PixelCount;
  308. Exit;
  309. end;
  310. case Bpp of
  311. 1: Pixel := Data^;
  312. 2: Pixel := PWord(Data)^;
  313. 3: PColor24Rec(@Pixel)^ := PColor24Rec(Data)^;
  314. 4: Pixel := PUInt32(Data)^;
  315. end;
  316. while PixelCount > 1 do
  317. begin
  318. Inc(Data, Bpp);
  319. case Bpp of
  320. 1: NextPixel := Data^;
  321. 2: NextPixel := PWord(Data)^;
  322. 3: PColor24Rec(@NextPixel)^ := PColor24Rec(Data)^;
  323. 4: NextPixel := PUInt32(Data)^;
  324. end;
  325. if NextPixel = Pixel then
  326. Break;
  327. Pixel := NextPixel;
  328. N := N + 1;
  329. PixelCount := PixelCount - 1;
  330. end;
  331. if NextPixel = Pixel then
  332. Result := N
  333. else
  334. Result := N + 1;
  335. end;
  336. function CountSame(Data: PByte; Bpp, PixelCount: LongInt): LongInt;
  337. var
  338. Pixel: UInt32;
  339. NextPixel: UInt32;
  340. N: LongInt;
  341. begin
  342. N := 1;
  343. Pixel := 0;
  344. NextPixel := 0;
  345. case Bpp of
  346. 1: Pixel := Data^;
  347. 2: Pixel := PWord(Data)^;
  348. 3: PColor24Rec(@Pixel)^ := PColor24Rec(Data)^;
  349. 4: Pixel := PUInt32(Data)^;
  350. end;
  351. PixelCount := PixelCount - 1;
  352. while PixelCount > 0 do
  353. begin
  354. Inc(Data, Bpp);
  355. case Bpp of
  356. 1: NextPixel := Data^;
  357. 2: NextPixel := PWord(Data)^;
  358. 3: PColor24Rec(@NextPixel)^ := PColor24Rec(Data)^;
  359. 4: NextPixel := PUInt32(Data)^;
  360. end;
  361. if NextPixel <> Pixel then
  362. Break;
  363. N := N + 1;
  364. PixelCount := PixelCount - 1;
  365. end;
  366. Result := N;
  367. end;
  368. procedure RleCompressLine(Data: PByte; PixelCount, Bpp: LongInt; Dest:
  369. PByte; out Written: LongInt);
  370. const
  371. MaxRun = 128;
  372. var
  373. DiffCount: LongInt;
  374. SameCount: LongInt;
  375. RleBufSize: LongInt;
  376. begin
  377. RleBufSize := 0;
  378. while PixelCount > 0 do
  379. begin
  380. DiffCount := CountDiff(Data, Bpp, PixelCount);
  381. SameCount := CountSame(Data, Bpp, PixelCount);
  382. if (DiffCount > MaxRun) then
  383. DiffCount := MaxRun;
  384. if (SameCount > MaxRun) then
  385. SameCount := MaxRun;
  386. if (DiffCount > 0) then
  387. begin
  388. Dest^ := Byte(DiffCount - 1);
  389. Inc(Dest);
  390. PixelCount := PixelCount - DiffCount;
  391. RleBufSize := RleBufSize + (DiffCount * Bpp) + 1;
  392. Move(Data^, Dest^, DiffCount * Bpp);
  393. Inc(Data, DiffCount * Bpp);
  394. Inc(Dest, DiffCount * Bpp);
  395. end;
  396. if SameCount > 1 then
  397. begin
  398. Dest^ := Byte((SameCount - 1) or $80);
  399. Inc(Dest);
  400. PixelCount := PixelCount - SameCount;
  401. RleBufSize := RleBufSize + Bpp + 1;
  402. Inc(Data, (SameCount - 1) * Bpp);
  403. case Bpp of
  404. 1: Dest^ := Data^;
  405. 2: PWord(Dest)^ := PWord(Data)^;
  406. 3: PColor24Rec(Dest)^ := PColor24Rec(Data)^;
  407. 4: PUInt32(Dest)^ := PUInt32(Data)^;
  408. end;
  409. Inc(Data, Bpp);
  410. Inc(Dest, Bpp);
  411. end;
  412. end;
  413. Written := RleBufSize;
  414. end;
  415. begin
  416. with ImageToSave do
  417. begin
  418. // Allocate enough space to hold the worst case compression
  419. // result and then compress source's scanlines
  420. WidthBytes := Width * FmtInfo.BytesPerPixel;
  421. DestSize := WidthBytes * Height;
  422. DestSize := DestSize + DestSize div 2 + 1;
  423. SetLength(DestBuffer, DestSize);
  424. Total := 0;
  425. for I := 0 to Height - 1 do
  426. begin
  427. RleCompressLine(@PByteArray(Bits)[I * WidthBytes], Width,
  428. FmtInfo.BytesPerPixel, @DestBuffer[Total], Written);
  429. Total := Total + Written;
  430. end;
  431. GetIO.Write(Handle, DestBuffer, Total);
  432. end;
  433. end;
  434. begin
  435. Result := False;
  436. if MakeCompatible(Images[Index], ImageToSave, MustBeFreed) then
  437. with GetIO, ImageToSave do
  438. try
  439. FmtInfo := GetFormatInfo(Format);
  440. // Fill targa header
  441. FillChar(Hdr, SizeOf(Hdr), 0);
  442. Hdr.IDLength := 0;
  443. Hdr.ColorMapType := Iff(FmtInfo.PaletteEntries > 0, 1, 0);
  444. Hdr.Width := Width;
  445. Hdr.Height := Height;
  446. Hdr.PixelSize := FmtInfo.BytesPerPixel * 8;
  447. Hdr.ColorMapLength := FmtInfo.PaletteEntries;
  448. Hdr.ColorEntrySize := Iff(FmtInfo.PaletteEntries > 0, 24, 0);
  449. Hdr.ColorMapOff := 0;
  450. // This indicates that targa is stored in top-left format
  451. // as our images -> no flipping is needed.
  452. Hdr.Desc := 32;
  453. // Set alpha channel size in descriptor (mostly ignored by other software though)
  454. if Format = ifA8R8G8B8 then
  455. Hdr.Desc := Hdr.Desc or 8
  456. else if Format = ifA1R5G5B5 then
  457. Hdr.Desc := Hdr.Desc or 1;
  458. // Choose image type
  459. if FmtInfo.IsIndexed then
  460. Hdr.ImageType := Iff(FUseRLE, 9, 1)
  461. else if FmtInfo.HasGrayChannel then
  462. Hdr.ImageType := Iff(FUseRLE, 11, 3)
  463. else
  464. Hdr.ImageType := Iff(FUseRLE, 10, 2);
  465. Write(Handle, @Hdr, SizeOf(Hdr));
  466. // Write palette
  467. if FmtInfo.PaletteEntries > 0 then
  468. begin
  469. GetMem(Pal, FmtInfo.PaletteEntries * SizeOf(TColor24Rec));
  470. try
  471. for I := 0 to FmtInfo.PaletteEntries - 1 do
  472. with Pal[I] do
  473. begin
  474. R := Palette[I].R;
  475. G := Palette[I].G;
  476. B := Palette[I].B;
  477. end;
  478. Write(Handle, Pal, FmtInfo.PaletteEntries * SizeOf(TColor24Rec));
  479. finally
  480. FreeMemNil(Pal);
  481. end;
  482. end;
  483. if FUseRLE then
  484. // Save RLE compressed mode images
  485. SaveRLE
  486. else
  487. // Save uncompressed mode images
  488. Write(Handle, Bits, Size);
  489. Result := True;
  490. finally
  491. if MustBeFreed then
  492. FreeImage(ImageToSave);
  493. end;
  494. end;
  495. procedure TTargaFileFormat.ConvertToSupported(var Image: TImageData;
  496. const Info: TImageFormatInfo);
  497. var
  498. ConvFormat: TImageFormat;
  499. begin
  500. if Info.HasGrayChannel then
  501. // Convert all grayscale images to Gray8 (preserve alpha of AxGrayx formats)
  502. ConvFormat := IffFormat(not Info.HasAlphaChannel, ifGray8, ifA8R8G8B8)
  503. else if Info.IsIndexed then
  504. // Convert all indexed images to Index8
  505. ConvFormat := ifIndex8
  506. else if Info.HasAlphaChannel then
  507. // Convert images with alpha channel to A8R8G8B8
  508. ConvFormat := ifA8R8G8B8
  509. else if Info.UsePixelFormat then
  510. // Convert 16bit images (without alpha channel) to A1R5G5B5
  511. ConvFormat := ifA1R5G5B5
  512. else
  513. // Convert all other formats to R8G8B8
  514. ConvFormat := ifR8G8B8;
  515. ConvertImage(Image, ConvFormat);
  516. end;
  517. function TTargaFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
  518. var
  519. Hdr: TTargaHeader;
  520. ReadCount: LongInt;
  521. begin
  522. Result := False;
  523. if Handle <> nil then
  524. begin
  525. ReadCount := GetIO.Read(Handle, @Hdr, SizeOf(Hdr));
  526. GetIO.Seek(Handle, -ReadCount, smFromCurrent);
  527. Result := (ReadCount >= SizeOf(Hdr)) and
  528. (Hdr.ImageType in [0, 1, 2, 3, 9, 10, 11]) and
  529. (Hdr.PixelSize in [1, 8, 15, 16, 24, 32]) and
  530. (Hdr.ColorEntrySize in [0, 16, 24, 32]);
  531. end;
  532. end;
  533. initialization
  534. RegisterImageFileFormat(TTargaFileFormat);
  535. {
  536. File Notes:
  537. * More recent changes are in VCS history *
  538. -- 0.21 Changes/Bug Fixes -----------------------------------
  539. - MakeCompatible method moved to base class, put ConvertToSupported here.
  540. GetSupportedFormats removed, it is now set in constructor.
  541. - Made public properties for options registered to SetOption/GetOption
  542. functions.
  543. - Changed extensions to filename masks.
  544. - Changed SaveData, LoadData, and MakeCompatible methods according
  545. to changes in base class in Imaging unit.
  546. -- 0.17 Changes/Bug Fixes -----------------------------------
  547. - 16 bit images are usually without alpha but some has alpha
  548. channel and there is no indication of it - so I have added
  549. a check: if all pixels of image are with alpha = 0 image is treated
  550. as X1R5G5B5 otherwise as A1R5G5B5
  551. - fixed problems with some nonstandard 15 bit images
  552. }
  553. end.