2 $Id: ImagingTarga.pas 139 2008-09-18 02:01:42Z galfar $
3 Vampyre Imaging Library
5 http://imaginglib.sourceforge.net
7 The contents of this file are used with permission, subject to the Mozilla
8 Public License Version 1.1 (the "License"); you may not use this file except
9 in compliance with the License. You may obtain a copy of the License at
10 http://www.mozilla.org/MPL/MPL-1.1.html
12 Software distributed under the License is distributed on an "AS IS" basis,
13 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
14 the specific language governing rights and limitations under the License.
16 Alternatively, the contents of this file may be used under the terms of the
17 GNU Lesser General Public License (the "LGPL License"), in which case the
18 provisions of the LGPL License are applicable instead of those above.
19 If you wish to allow use of your version of this file only under the terms
20 of the LGPL License and not to allow others to use your version of this file
21 under the MPL, indicate your decision by deleting the provisions above and
22 replace them with the notice and other provisions required by the LGPL
23 License. If you do not delete the provisions above, a recipient may use
24 your version of this file under either the MPL or the LGPL License.
26 For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
29 { This unit contains image format loader/saver for Targa images.}
32 {$I ImagingOptions.inc}
37 ImagingTypes
, Imaging
, ImagingFormats
, ImagingUtility
;
40 { Class for loading and saving Truevision Targa images.
41 It can load/save 8bit indexed or grayscale, 16 bit RGB or grayscale,
42 24 bit RGB and 32 bit ARGB images with or without RLE compression.}
43 TTargaFileFormat
= class(TImageFileFormat
)
46 function LoadData(Handle
: TImagingHandle
; var Images
: TDynImageDataArray
;
47 OnlyFirstLevel
: Boolean): Boolean; override;
48 function SaveData(Handle
: TImagingHandle
; const Images
: TDynImageDataArray
;
49 Index
: LongInt): Boolean; override;
50 procedure ConvertToSupported(var Image
: TImageData
;
51 const Info
: TImageFormatInfo
); override;
53 constructor Create
; override;
54 function TestFormat(Handle
: TImagingHandle
): Boolean; override;
56 { Controls that RLE compression is used during saving. Accessible trough
57 ImagingTargaRLE option.}
58 property UseRLE
: LongBool read FUseRLE write FUseRLE
;
64 STargaFormatName
= 'Truevision Targa Image';
65 STargaMasks
= '*.tga';
66 TargaSupportedFormats
: TImageFormats
= [ifIndex8
, ifGray8
, ifA1R5G5B5
,
67 ifR8G8B8
, ifA8R8G8B8
];
68 TargaDefaultRLE
= False;
71 STargaSignature
= 'TRUEVISION-XFILE';
75 TTargaHeader
= packed record
90 { Footer at the end of TGA file.}
91 TTargaFooter
= packed record
92 ExtOff
: LongWord; // Extension Area Offset
93 DevDirOff
: LongWord; // Developer Directory Offset
94 Signature
: TChar16
; // TRUEVISION-XFILE
95 Reserved
: Byte; // ASCII period '.'
100 { TTargaFileFormat class implementation }
102 constructor TTargaFileFormat
.Create
;
105 FName
:= STargaFormatName
;
108 FIsMultiImageFormat
:= False;
109 FSupportedFormats
:= TargaSupportedFormats
;
111 FUseRLE
:= TargaDefaultRLE
;
113 AddMasks(STargaMasks
);
114 RegisterOption(ImagingTargaRLE
, @FUseRLE
);
117 function TTargaFileFormat
.LoadData(Handle
: TImagingHandle
;
118 var Images
: TDynImageDataArray
; OnlyFirstLevel
: Boolean): Boolean;
122 FooterFound
, ExtFound
: Boolean;
123 I
, PSize
, PalSize
: LongWord;
125 FmtInfo
: TImageFormatInfo
;
130 I
, CPixel
, Cnt
: LongInt;
132 Buffer
, Dest
, Src
: PByte;
135 with GetIO
, Images
[0] do
137 // Alocates buffer large enough to hold the worst case
138 // RLE compressed data and reads then from input
139 BufSize
:= Width
* Height
* FmtInfo
.BytesPerPixel
;
140 BufSize
:= BufSize
+ BufSize
div 2 + 1;
141 GetMem(Buffer
, BufSize
);
144 BufSize
:= Read(Handle
, Buffer
, BufSize
);
146 Cnt
:= Width
* Height
;
147 Bpp
:= FmtInfo
.BytesPerPixel
;
149 while CPixel
< Cnt
do
155 // Process uncompressed pixel
157 CPixel
:= CPixel
+ Rle
;
158 for I
:= 0 to Rle
- 1 do
160 // Copy pixel from src to dest
163 2: PWord(Dest
)^ := PWord(Src
)^;
164 3: PColor24Rec(Dest
)^ := PColor24Rec(Src
)^;
165 4: PLongWord(Dest
)^ := PLongWord(Src
)^;
173 // Process compressed pixels
175 CPixel
:= CPixel
+ Rle
;
176 // Copy one pixel from src to dest (many times there)
177 for I
:= 0 to Rle
- 1 do
181 2: PWord(Dest
)^ := PWord(Src
)^;
182 3: PColor24Rec(Dest
)^ := PColor24Rec(Src
)^;
183 4: PLongWord(Dest
)^ := PLongWord(Src
)^;
190 // set position in source to real end of compressed data
191 Seek(Handle
, -(BufSize
- LongInt(LongWord(Src
) - LongWord(Buffer
))),
198 SetLength(Images
, 1);
199 with GetIO
, Images
[0] do
202 Read(Handle
, @Hdr
, SizeOf(Hdr
));
203 // Skip image ID info
204 Seek(Handle
, Hdr
.IDLength
, smFromCurrent
);
205 // Determine image format
207 case Hdr
.ImageType
of
208 1, 9: Format
:= ifIndex8
;
209 2, 10: case Hdr
.PixelSize
of
210 15: Format
:= ifX1R5G5B5
;
211 16: Format
:= ifA1R5G5B5
;
212 24: Format
:= ifR8G8B8
;
213 32: Format
:= ifA8R8G8B8
;
215 3, 11: Format
:= ifGray8
;
217 // Format was not assigned by previous testing (it should be in
218 // well formed targas), so formats which reflects bit dept are selected
219 if Format
= ifUnknown
then
220 case Hdr
.PixelSize
of
221 8: Format
:= ifGray8
;
222 15: Format
:= ifX1R5G5B5
;
223 16: Format
:= ifA1R5G5B5
;
224 24: Format
:= ifR8G8B8
;
225 32: Format
:= ifA8R8G8B8
;
227 NewImage(Hdr
.Width
, Hdr
.Height
, Format
, Images
[0]);
228 FmtInfo
:= GetFormatInfo(Format
);
230 if (Hdr
.ColorMapType
= 1) and (Hdr
.ImageType
in [1, 9]) then
233 PSize
:= Hdr
.ColorMapLength
* (Hdr
.ColorEntrySize
shr 3);
236 Read(Handle
, Pal
, PSize
);
238 PalSize
:= Iff(Hdr
.ColorMapLength
> FmtInfo
.PaletteEntries
,
239 FmtInfo
.PaletteEntries
, Hdr
.ColorMapLength
);
240 for I
:= 0 to PalSize
- 1 do
241 case Hdr
.ColorEntrySize
of
246 R
:= PPalette24(Pal
)[I
].R
;
247 G
:= PPalette24(Pal
)[I
].G
;
248 B
:= PPalette24(Pal
)[I
].B
;
250 // I've never seen tga with these palettes so they are untested
254 A
:= (PWordArray(Pal
)[I
] and $8000) shr 12;
255 R
:= (PWordArray(Pal
)[I
] and $FC00) shr 7;
256 G
:= (PWordArray(Pal
)[I
] and $03E0) shr 2;
257 B
:= (PWordArray(Pal
)[I
] and $001F) shl 3;
262 A
:= PPalette32(Pal
)[I
].A
;
263 R
:= PPalette32(Pal
)[I
].R
;
264 G
:= PPalette32(Pal
)[I
].G
;
265 B
:= PPalette32(Pal
)[I
].B
;
273 case Hdr
.ImageType
of
275 // Load uncompressed mode images
276 Read(Handle
, Bits
, Size
);
278 // Load RLE compressed mode images
282 // Check if there is alpha channel present in A1R5GB5 images, if it is not
283 // change format to X1R5G5B5
284 if Format
= ifA1R5G5B5
then
286 if not Has16BitImageAlpha(Width
* Height
, Bits
) then
287 Format
:= ifX1R5G5B5
;
290 // We must find true end of file and set input' position to it
291 // paint programs appends extra info at the end of Targas
292 // some of them multiple times (PSP Pro 8)
295 FooterFound
:= False;
297 if Read(Handle
, @WordValue
, 2) = 2 then
299 // 495 = size of Extension Area
300 if WordValue
= 495 then
302 Seek(Handle
, 493, smFromCurrent
);
306 Seek(Handle
, -2, smFromCurrent
);
309 if Read(Handle
, @Foo
, SizeOf(Foo
)) = SizeOf(Foo
) then
311 if Foo
.Signature
= STargaSignature
then
314 Seek(Handle
, -SizeOf(Foo
), smFromCurrent
);
316 until (not ExtFound
) and (not FooterFound
);
318 // Some editors save targas flipped
319 if Hdr
.Desc
< 31 then
320 FlipImage(Images
[0]);
326 function TTargaFileFormat
.SaveData(Handle
: TImagingHandle
;
327 const Images
: TDynImageDataArray
; Index
: LongInt): Boolean;
331 FmtInfo
: TImageFormatInfo
;
333 ImageToSave
: TImageData
;
334 MustBeFreed
: Boolean;
339 WidthBytes
, Written
, I
, Total
, DestSize
: LongInt;
341 function CountDiff(Data
: PByte; Bpp
, PixelCount
: Longint): LongInt;
350 if PixelCount
= 1 then
352 Result
:= PixelCount
;
357 2: Pixel
:= PWord(Data
)^;
358 3: PColor24Rec(@Pixel
)^ := PColor24Rec(Data
)^;
359 4: Pixel
:= PLongWord(Data
)^;
361 while PixelCount
> 1 do
365 1: NextPixel
:= Data
^;
366 2: NextPixel
:= PWord(Data
)^;
367 3: PColor24Rec(@NextPixel
)^ := PColor24Rec(Data
)^;
368 4: NextPixel
:= PLongWord(Data
)^;
370 if NextPixel
= Pixel
then
374 PixelCount
:= PixelCount
- 1;
376 if NextPixel
= Pixel
then
382 function CountSame(Data
: PByte; Bpp
, PixelCount
: LongInt): LongInt;
393 2: Pixel
:= PWord(Data
)^;
394 3: PColor24Rec(@Pixel
)^ := PColor24Rec(Data
)^;
395 4: Pixel
:= PLongWord(Data
)^;
397 PixelCount
:= PixelCount
- 1;
398 while PixelCount
> 0 do
402 1: NextPixel
:= Data
^;
403 2: NextPixel
:= PWord(Data
)^;
404 3: PColor24Rec(@NextPixel
)^ := PColor24Rec(Data
)^;
405 4: NextPixel
:= PLongWord(Data
)^;
407 if NextPixel
<> Pixel
then
410 PixelCount
:= PixelCount
- 1;
415 procedure RleCompressLine(Data
: PByte; PixelCount
, Bpp
: LongInt; Dest
:
416 PByte; var Written
: LongInt);
425 while PixelCount
> 0 do
427 DiffCount
:= CountDiff(Data
, Bpp
, PixelCount
);
428 SameCount
:= CountSame(Data
, Bpp
, PixelCount
);
429 if (DiffCount
> MaxRun
) then
431 if (SameCount
> MaxRun
) then
433 if (DiffCount
> 0) then
435 Dest
^ := Byte(DiffCount
- 1);
437 PixelCount
:= PixelCount
- DiffCount
;
438 RleBufSize
:= RleBufSize
+ (DiffCount
* Bpp
) + 1;
439 Move(Data
^, Dest
^, DiffCount
* Bpp
);
440 Inc(Data
, DiffCount
* Bpp
);
441 Inc(Dest
, DiffCount
* Bpp
);
443 if SameCount
> 1 then
445 Dest
^ := Byte((SameCount
- 1) or $80);
447 PixelCount
:= PixelCount
- SameCount
;
448 RleBufSize
:= RleBufSize
+ Bpp
+ 1;
449 Inc(Data
, (SameCount
- 1) * Bpp
);
452 2: PWord(Dest
)^ := PWord(Data
)^;
453 3: PColor24Rec(Dest
)^ := PColor24Rec(Data
)^;
454 4: PLongWord(Dest
)^ := PLongWord(Data
)^;
460 Written
:= RleBufSize
;
466 // Allocate enough space to hold the worst case compression
467 // result and then compress source's scanlines
468 WidthBytes
:= Width
* FmtInfo
.BytesPerPixel
;
469 DestSize
:= WidthBytes
* Height
;
470 DestSize
:= DestSize
+ DestSize
div 2 + 1;
471 GetMem(Dest
, DestSize
);
474 for I
:= 0 to Height
- 1 do
476 RleCompressLine(@PByteArray(Bits
)[I
* WidthBytes
], Width
,
477 FmtInfo
.BytesPerPixel
, @PByteArray(Dest
)[Total
], Written
);
478 Total
:= Total
+ Written
;
480 GetIO
.Write(Handle
, Dest
, Total
);
489 if MakeCompatible(Images
[Index
], ImageToSave
, MustBeFreed
) then
490 with GetIO
, ImageToSave
do
492 FmtInfo
:= GetFormatInfo(Format
);
494 FillChar(Hdr
, SizeOf(Hdr
), 0);
496 Hdr
.ColorMapType
:= Iff(FmtInfo
.PaletteEntries
> 0, 1, 0);
498 Hdr
.Height
:= Height
;
499 Hdr
.PixelSize
:= FmtInfo
.BytesPerPixel
* 8;
500 Hdr
.ColorMapLength
:= FmtInfo
.PaletteEntries
;
501 Hdr
.ColorEntrySize
:= Iff(FmtInfo
.PaletteEntries
> 0, 24, 0);
502 Hdr
.ColorMapOff
:= 0;
503 // This indicates that targa is stored in top-left format
504 // as our images -> no flipping is needed.
506 // Set alpha channel size in descriptor (mostly ignored by other software though)
507 if Format
= ifA8R8G8B8
then
508 Hdr
.Desc
:= Hdr
.Desc
or 8
509 else if Format
= ifA1R5G5B5
then
510 Hdr
.Desc
:= Hdr
.Desc
or 1;
513 if FmtInfo
.IsIndexed
then
514 Hdr
.ImageType
:= Iff(FUseRLE
, 9, 1)
516 if FmtInfo
.HasGrayChannel
then
517 Hdr
.ImageType
:= Iff(FUseRLE
, 11, 3)
519 Hdr
.ImageType
:= Iff(FUseRLE
, 10, 2);
521 Write(Handle
, @Hdr
, SizeOf(Hdr
));
524 if FmtInfo
.PaletteEntries
> 0 then
526 GetMem(Pal
, FmtInfo
.PaletteEntries
* SizeOf(TColor24Rec
));
528 for I
:= 0 to FmtInfo
.PaletteEntries
- 1 do
535 Write(Handle
, Pal
, FmtInfo
.PaletteEntries
* SizeOf(TColor24Rec
));
542 // Save rle compressed mode images
545 // Save uncompressed mode images
546 Write(Handle
, Bits
, Size
);
551 FreeImage(ImageToSave
);
555 procedure TTargaFileFormat
.ConvertToSupported(var Image
: TImageData
;
556 const Info
: TImageFormatInfo
);
558 ConvFormat
: TImageFormat
;
560 if Info
.HasGrayChannel
then
561 // Convert all grayscale images to Gray8 (preserve alpha of AxGrayx formats)
562 ConvFormat
:= IffFormat(not Info
.HasAlphaChannel
, ifGray8
, ifA8R8G8B8
)
563 else if Info
.IsIndexed
then
564 // Convert all indexed images to Index8
565 ConvFormat
:= ifIndex8
566 else if Info
.HasAlphaChannel
then
567 // Convert images with alpha channel to A8R8G8B8
568 ConvFormat
:= ifA8R8G8B8
569 else if Info
.UsePixelFormat
then
570 // Convert 16bit images (without alpha channel) to A1R5G5B5
571 ConvFormat
:= ifA1R5G5B5
573 // Convert all other formats to R8G8B8
574 ConvFormat
:= ifR8G8B8
;
576 ConvertImage(Image
, ConvFormat
);
579 function TTargaFileFormat
.TestFormat(Handle
: TImagingHandle
): Boolean;
585 if Handle
<> nil then
587 ReadCount
:= GetIO
.Read(Handle
, @Hdr
, SizeOf(Hdr
));
588 GetIO
.Seek(Handle
, -ReadCount
, smFromCurrent
);
589 Result
:= (ReadCount
>= SizeOf(Hdr
)) and
590 (Hdr
.ImageType
in [0, 1, 2, 3, 9, 10, 11]) and
591 (Hdr
.PixelSize
in [1, 8, 15, 16, 24, 32]) and
592 (Hdr
.ColorEntrySize
in [0, 16, 24, 32]);
597 RegisterImageFileFormat(TTargaFileFormat
);
602 -- TODOS ----------------------------------------------------
605 -- 0.21 Changes/Bug Fixes -----------------------------------
606 - MakeCompatible method moved to base class, put ConvertToSupported here.
607 GetSupportedFormats removed, it is now set in constructor.
608 - Made public properties for options registered to SetOption/GetOption
610 - Changed extensions to filename masks.
611 - Changed SaveData, LoadData, and MakeCompatible methods according
612 to changes in base class in Imaging unit.
614 -- 0.17 Changes/Bug Fixes -----------------------------------
615 - 16 bit images are usually without alpha but some has alpha
616 channel and there is no indication of it - so I have added
617 a check: if all pixels of image are with alpha = 0 image is treated
618 as X1R5G5B5 otherwise as A1R5G5B5
619 - fixed problems with some nonstandard 15 bit images