ImagingTarga.pas 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  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, CPixel, Cnt: LongInt;
  95. Bpp, Rle: Byte;
  96. Buffer, Dest, Src: PByte;
  97. BufSize: LongInt;
  98. begin
  99. with GetIO, Images[0] do
  100. begin
  101. // Allocates buffer large enough to hold the worst case
  102. // RLE compressed data and reads then from input
  103. BufSize := Width * Height * FmtInfo.BytesPerPixel;
  104. BufSize := BufSize + BufSize div 2 + 1;
  105. GetMem(Buffer, BufSize);
  106. Src := Buffer;
  107. Dest := Bits;
  108. BufSize := Read(Handle, Buffer, BufSize);
  109. Cnt := Width * Height;
  110. Bpp := FmtInfo.BytesPerPixel;
  111. CPixel := 0;
  112. while CPixel < Cnt do
  113. begin
  114. Rle := Src^;
  115. Inc(Src);
  116. if Rle < 128 then
  117. begin
  118. // Process uncompressed pixel
  119. Rle := Rle + 1;
  120. CPixel := CPixel + Rle;
  121. for I := 0 to Rle - 1 do
  122. begin
  123. // Copy pixel from src to dest
  124. case Bpp of
  125. 1: Dest^ := Src^;
  126. 2: PWord(Dest)^ := PWord(Src)^;
  127. 3: PColor24Rec(Dest)^ := PColor24Rec(Src)^;
  128. 4: PUInt32(Dest)^ := PUInt32(Src)^;
  129. end;
  130. Inc(Src, Bpp);
  131. Inc(Dest, Bpp);
  132. end;
  133. end
  134. else
  135. begin
  136. // Process compressed pixels
  137. Rle := Rle - 127;
  138. CPixel := CPixel + Rle;
  139. // Copy one pixel from src to dest (many times there)
  140. for I := 0 to Rle - 1 do
  141. begin
  142. case Bpp of
  143. 1: Dest^ := Src^;
  144. 2: PWord(Dest)^ := PWord(Src)^;
  145. 3: PColor24Rec(Dest)^ := PColor24Rec(Src)^;
  146. 4: PUInt32(Dest)^ := PUInt32(Src)^;
  147. end;
  148. Inc(Dest, Bpp);
  149. end;
  150. Inc(Src, Bpp);
  151. end;
  152. end;
  153. // set position in source to real end of compressed data
  154. Seek(Handle, -(BufSize - (PtrUInt(Src) - PtrUInt(Buffer))),
  155. smFromCurrent);
  156. FreeMem(Buffer);
  157. end;
  158. end;
  159. begin
  160. SetLength(Images, 1);
  161. with GetIO, Images[0] do
  162. begin
  163. // Read targa header
  164. Read(Handle, @Hdr, SizeOf(Hdr));
  165. // Skip image ID info
  166. Seek(Handle, Hdr.IDLength, smFromCurrent);
  167. // Determine image format
  168. Format := ifUnknown;
  169. case Hdr.ImageType of
  170. 1, 9: Format := ifIndex8;
  171. 2, 10: case Hdr.PixelSize of
  172. 15: Format := ifX1R5G5B5;
  173. 16: Format := ifA1R5G5B5;
  174. 24: Format := ifR8G8B8;
  175. 32: Format := ifA8R8G8B8;
  176. end;
  177. 3, 11: Format := ifGray8;
  178. end;
  179. // Format was not assigned by previous testing (it should be in
  180. // well formed targas), so formats which reflects bit dept are selected
  181. if Format = ifUnknown then
  182. case Hdr.PixelSize of
  183. 8: Format := ifGray8;
  184. 15: Format := ifX1R5G5B5;
  185. 16: Format := ifA1R5G5B5;
  186. 24: Format := ifR8G8B8;
  187. 32: Format := ifA8R8G8B8;
  188. end;
  189. NewImage(Hdr.Width, Hdr.Height, Format, Images[0]);
  190. FmtInfo := GetFormatInfo(Format);
  191. if (Hdr.ColorMapType = 1) and (Hdr.ImageType in [1, 9]) then
  192. begin
  193. // Read palette
  194. PSize := Hdr.ColorMapLength * (Hdr.ColorEntrySize shr 3);
  195. GetMem(Pal, PSize);
  196. try
  197. Read(Handle, Pal, PSize);
  198. // Process palette
  199. PalSize := Iff(Hdr.ColorMapLength > FmtInfo.PaletteEntries,
  200. FmtInfo.PaletteEntries, Hdr.ColorMapLength);
  201. for I := 0 to PalSize - 1 do
  202. case Hdr.ColorEntrySize of
  203. 24:
  204. with Palette[I] do
  205. begin
  206. A := $FF;
  207. R := PPalette24(Pal)[I].R;
  208. G := PPalette24(Pal)[I].G;
  209. B := PPalette24(Pal)[I].B;
  210. end;
  211. // I've never seen tga with these palettes so they are untested
  212. 16:
  213. with Palette[I] do
  214. begin
  215. A := (PWordArray(Pal)[I] and $8000) shr 12;
  216. R := (PWordArray(Pal)[I] and $FC00) shr 7;
  217. G := (PWordArray(Pal)[I] and $03E0) shr 2;
  218. B := (PWordArray(Pal)[I] and $001F) shl 3;
  219. end;
  220. 32:
  221. with Palette[I] do
  222. begin
  223. A := PPalette32(Pal)[I].A;
  224. R := PPalette32(Pal)[I].R;
  225. G := PPalette32(Pal)[I].G;
  226. B := PPalette32(Pal)[I].B;
  227. end;
  228. end;
  229. finally
  230. FreeMemNil(Pal);
  231. end;
  232. end;
  233. case Hdr.ImageType of
  234. 0, 1, 2, 3:
  235. // Load uncompressed mode images
  236. Read(Handle, Bits, Size);
  237. 9, 10, 11:
  238. // Load RLE compressed mode images
  239. LoadRLE;
  240. end;
  241. // Check if there is alpha channel present in A1R5GB5 images, if it is not
  242. // change format to X1R5G5B5
  243. if Format = ifA1R5G5B5 then
  244. begin
  245. if not Has16BitImageAlpha(Width * Height, Bits) then
  246. Format := ifX1R5G5B5;
  247. end;
  248. // We must find true end of file and set input' position to it
  249. // paint programs appends extra info at the end of Targas
  250. // some of them multiple times (PSP Pro 8)
  251. repeat
  252. ExtFound := False;
  253. FooterFound := False;
  254. if Read(Handle, @WordValue, 2) = 2 then
  255. begin
  256. // 495 = size of Extension Area
  257. if WordValue = 495 then
  258. begin
  259. Seek(Handle, 493, smFromCurrent);
  260. ExtFound := True;
  261. end
  262. else
  263. Seek(Handle, -2, smFromCurrent);
  264. end;
  265. if Read(Handle, @Foo, SizeOf(Foo)) = SizeOf(Foo) then
  266. begin
  267. if Foo.Signature = STargaSignature then
  268. FooterFound := True
  269. else
  270. Seek(Handle, -SizeOf(Foo), smFromCurrent);
  271. end;
  272. until (not ExtFound) and (not FooterFound);
  273. // Some editors save targas flipped
  274. if Hdr.Desc < 31 then
  275. FlipImage(Images[0]);
  276. Result := True;
  277. end;
  278. end;
  279. function TTargaFileFormat.SaveData(Handle: TImagingHandle;
  280. const Images: TDynImageDataArray; Index: LongInt): Boolean;
  281. var
  282. I: LongInt;
  283. Hdr: TTargaHeader;
  284. FmtInfo: TImageFormatInfo;
  285. Pal: PPalette24;
  286. ImageToSave: TImageData;
  287. MustBeFreed: Boolean;
  288. procedure SaveRLE;
  289. var
  290. Dest: PByte;
  291. WidthBytes, Written, I, Total, DestSize: LongInt;
  292. function CountDiff(Data: PByte; Bpp, PixelCount: Longint): LongInt;
  293. var
  294. Pixel: UInt32;
  295. NextPixel: UInt32;
  296. N: LongInt;
  297. begin
  298. N := 0;
  299. Pixel := 0;
  300. NextPixel := 0;
  301. if PixelCount = 1 then
  302. begin
  303. Result := PixelCount;
  304. Exit;
  305. end;
  306. case Bpp of
  307. 1: Pixel := Data^;
  308. 2: Pixel := PWord(Data)^;
  309. 3: PColor24Rec(@Pixel)^ := PColor24Rec(Data)^;
  310. 4: Pixel := PUInt32(Data)^;
  311. end;
  312. while PixelCount > 1 do
  313. begin
  314. Inc(Data, Bpp);
  315. case Bpp of
  316. 1: NextPixel := Data^;
  317. 2: NextPixel := PWord(Data)^;
  318. 3: PColor24Rec(@NextPixel)^ := PColor24Rec(Data)^;
  319. 4: NextPixel := PUInt32(Data)^;
  320. end;
  321. if NextPixel = Pixel then
  322. Break;
  323. Pixel := NextPixel;
  324. N := N + 1;
  325. PixelCount := PixelCount - 1;
  326. end;
  327. if NextPixel = Pixel then
  328. Result := N
  329. else
  330. Result := N + 1;
  331. end;
  332. function CountSame(Data: PByte; Bpp, PixelCount: LongInt): LongInt;
  333. var
  334. Pixel: UInt32;
  335. NextPixel: UInt32;
  336. N: LongInt;
  337. begin
  338. N := 1;
  339. Pixel := 0;
  340. NextPixel := 0;
  341. case Bpp of
  342. 1: Pixel := Data^;
  343. 2: Pixel := PWord(Data)^;
  344. 3: PColor24Rec(@Pixel)^ := PColor24Rec(Data)^;
  345. 4: Pixel := PUInt32(Data)^;
  346. end;
  347. PixelCount := PixelCount - 1;
  348. while PixelCount > 0 do
  349. begin
  350. Inc(Data, Bpp);
  351. case Bpp of
  352. 1: NextPixel := Data^;
  353. 2: NextPixel := PWord(Data)^;
  354. 3: PColor24Rec(@NextPixel)^ := PColor24Rec(Data)^;
  355. 4: NextPixel := PUInt32(Data)^;
  356. end;
  357. if NextPixel <> Pixel then
  358. Break;
  359. N := N + 1;
  360. PixelCount := PixelCount - 1;
  361. end;
  362. Result := N;
  363. end;
  364. procedure RleCompressLine(Data: PByte; PixelCount, Bpp: LongInt; Dest:
  365. PByte; out Written: LongInt);
  366. const
  367. MaxRun = 128;
  368. var
  369. DiffCount: LongInt;
  370. SameCount: LongInt;
  371. RleBufSize: LongInt;
  372. begin
  373. RleBufSize := 0;
  374. while PixelCount > 0 do
  375. begin
  376. DiffCount := CountDiff(Data, Bpp, PixelCount);
  377. SameCount := CountSame(Data, Bpp, PixelCount);
  378. if (DiffCount > MaxRun) then
  379. DiffCount := MaxRun;
  380. if (SameCount > MaxRun) then
  381. SameCount := MaxRun;
  382. if (DiffCount > 0) then
  383. begin
  384. Dest^ := Byte(DiffCount - 1);
  385. Inc(Dest);
  386. PixelCount := PixelCount - DiffCount;
  387. RleBufSize := RleBufSize + (DiffCount * Bpp) + 1;
  388. Move(Data^, Dest^, DiffCount * Bpp);
  389. Inc(Data, DiffCount * Bpp);
  390. Inc(Dest, DiffCount * Bpp);
  391. end;
  392. if SameCount > 1 then
  393. begin
  394. Dest^ := Byte((SameCount - 1) or $80);
  395. Inc(Dest);
  396. PixelCount := PixelCount - SameCount;
  397. RleBufSize := RleBufSize + Bpp + 1;
  398. Inc(Data, (SameCount - 1) * Bpp);
  399. case Bpp of
  400. 1: Dest^ := Data^;
  401. 2: PWord(Dest)^ := PWord(Data)^;
  402. 3: PColor24Rec(Dest)^ := PColor24Rec(Data)^;
  403. 4: PUInt32(Dest)^ := PUInt32(Data)^;
  404. end;
  405. Inc(Data, Bpp);
  406. Inc(Dest, Bpp);
  407. end;
  408. end;
  409. Written := RleBufSize;
  410. end;
  411. begin
  412. with ImageToSave do
  413. begin
  414. // Allocate enough space to hold the worst case compression
  415. // result and then compress source's scanlines
  416. WidthBytes := Width * FmtInfo.BytesPerPixel;
  417. DestSize := WidthBytes * Height;
  418. DestSize := DestSize + DestSize div 2 + 1;
  419. GetMem(Dest, DestSize);
  420. Total := 0;
  421. try
  422. for I := 0 to Height - 1 do
  423. begin
  424. RleCompressLine(@PByteArray(Bits)[I * WidthBytes], Width,
  425. FmtInfo.BytesPerPixel, @PByteArray(Dest)[Total], Written);
  426. Total := Total + Written;
  427. end;
  428. GetIO.Write(Handle, Dest, Total);
  429. finally
  430. FreeMem(Dest);
  431. end;
  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
  462. if FmtInfo.HasGrayChannel then
  463. Hdr.ImageType := Iff(FUseRLE, 11, 3)
  464. else
  465. Hdr.ImageType := Iff(FUseRLE, 10, 2);
  466. Write(Handle, @Hdr, SizeOf(Hdr));
  467. // Write palette
  468. if FmtInfo.PaletteEntries > 0 then
  469. begin
  470. GetMem(Pal, FmtInfo.PaletteEntries * SizeOf(TColor24Rec));
  471. try
  472. for I := 0 to FmtInfo.PaletteEntries - 1 do
  473. with Pal[I] do
  474. begin
  475. R := Palette[I].R;
  476. G := Palette[I].G;
  477. B := Palette[I].B;
  478. end;
  479. Write(Handle, Pal, FmtInfo.PaletteEntries * SizeOf(TColor24Rec));
  480. finally
  481. FreeMemNil(Pal);
  482. end;
  483. end;
  484. if FUseRLE then
  485. // Save rle compressed mode images
  486. SaveRLE
  487. else
  488. // Save uncompressed mode images
  489. Write(Handle, Bits, Size);
  490. Result := True;
  491. finally
  492. if MustBeFreed then
  493. FreeImage(ImageToSave);
  494. end;
  495. end;
  496. procedure TTargaFileFormat.ConvertToSupported(var Image: TImageData;
  497. const Info: TImageFormatInfo);
  498. var
  499. ConvFormat: TImageFormat;
  500. begin
  501. if Info.HasGrayChannel then
  502. // Convert all grayscale images to Gray8 (preserve alpha of AxGrayx formats)
  503. ConvFormat := IffFormat(not Info.HasAlphaChannel, ifGray8, ifA8R8G8B8)
  504. else if Info.IsIndexed then
  505. // Convert all indexed images to Index8
  506. ConvFormat := ifIndex8
  507. else if Info.HasAlphaChannel then
  508. // Convert images with alpha channel to A8R8G8B8
  509. ConvFormat := ifA8R8G8B8
  510. else if Info.UsePixelFormat then
  511. // Convert 16bit images (without alpha channel) to A1R5G5B5
  512. ConvFormat := ifA1R5G5B5
  513. else
  514. // Convert all other formats to R8G8B8
  515. ConvFormat := ifR8G8B8;
  516. ConvertImage(Image, ConvFormat);
  517. end;
  518. function TTargaFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
  519. var
  520. Hdr: TTargaHeader;
  521. ReadCount: LongInt;
  522. begin
  523. Result := False;
  524. if Handle <> nil then
  525. begin
  526. ReadCount := GetIO.Read(Handle, @Hdr, SizeOf(Hdr));
  527. GetIO.Seek(Handle, -ReadCount, smFromCurrent);
  528. Result := (ReadCount >= SizeOf(Hdr)) and
  529. (Hdr.ImageType in [0, 1, 2, 3, 9, 10, 11]) and
  530. (Hdr.PixelSize in [1, 8, 15, 16, 24, 32]) and
  531. (Hdr.ColorEntrySize in [0, 16, 24, 32]);
  532. end;
  533. end;
  534. initialization
  535. RegisterImageFileFormat(TTargaFileFormat);
  536. {
  537. File Notes:
  538. -- TODOS ----------------------------------------------------
  539. - nothing now
  540. -- 0.21 Changes/Bug Fixes -----------------------------------
  541. - MakeCompatible method moved to base class, put ConvertToSupported here.
  542. GetSupportedFormats removed, it is now set in constructor.
  543. - Made public properties for options registered to SetOption/GetOption
  544. functions.
  545. - Changed extensions to filename masks.
  546. - Changed SaveData, LoadData, and MakeCompatible methods according
  547. to changes in base class in Imaging unit.
  548. -- 0.17 Changes/Bug Fixes -----------------------------------
  549. - 16 bit images are usually without alpha but some has alpha
  550. channel and there is no indication of it - so I have added
  551. a check: if all pixels of image are with alpha = 0 image is treated
  552. as X1R5G5B5 otherwise as A1R5G5B5
  553. - fixed problems with some nonstandard 15 bit images
  554. }
  555. end.