| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623 |
- {
- $Id$
- Vampyre Imaging Library
- by Marek Mauder
- http://imaginglib.sourceforge.net
- The contents of this file are used with permission, subject to the Mozilla
- Public License Version 1.1 (the "License"); you may not use this file except
- in compliance with the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/MPL-1.1.html
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
- the specific language governing rights and limitations under the License.
- Alternatively, the contents of this file may be used under the terms of the
- GNU Lesser General Public License (the "LGPL License"), in which case the
- provisions of the LGPL License are applicable instead of those above.
- If you wish to allow use of your version of this file only under the terms
- of the LGPL License and not to allow others to use your version of this file
- under the MPL, indicate your decision by deleting the provisions above and
- replace them with the notice and other provisions required by the LGPL
- License. If you do not delete the provisions above, a recipient may use
- your version of this file under either the MPL or the LGPL License.
- For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
- }
- { This unit contains image format loader/saver for Targa images.}
- unit ImagingTarga;
- {$I ImagingOptions.inc}
- interface
- uses
- ImagingTypes, Imaging, ImagingFormats, ImagingUtility;
- type
- { Class for loading and saving Truevision Targa images.
- It can load/save 8bit indexed or grayscale, 16 bit RGB or grayscale,
- 24 bit RGB and 32 bit ARGB images with or without RLE compression.}
- TTargaFileFormat = class(TImageFileFormat)
- protected
- FUseRLE: LongBool;
- function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
- OnlyFirstLevel: Boolean): Boolean; override;
- function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
- Index: LongInt): Boolean; override;
- procedure ConvertToSupported(var Image: TImageData;
- const Info: TImageFormatInfo); override;
- public
- constructor Create; override;
- function TestFormat(Handle: TImagingHandle): Boolean; override;
- published
- { Controls that RLE compression is used during saving. Accessible trough
- ImagingTargaRLE option.}
- property UseRLE: LongBool read FUseRLE write FUseRLE;
- end;
- implementation
- const
- STargaFormatName = 'Truevision Targa Image';
- STargaMasks = '*.tga';
- TargaSupportedFormats: TImageFormats = [ifIndex8, ifGray8, ifA1R5G5B5,
- ifR8G8B8, ifA8R8G8B8];
- TargaDefaultRLE = False;
- const
- STargaSignature = 'TRUEVISION-XFILE';
- type
- { Targa file header.}
- TTargaHeader = packed record
- IDLength: Byte;
- ColorMapType: Byte;
- ImageType: Byte;
- ColorMapOff: Word;
- ColorMapLength: Word;
- ColorEntrySize: Byte;
- XOrg: SmallInt;
- YOrg: SmallInt;
- Width: SmallInt;
- Height: SmallInt;
- PixelSize: Byte;
- Desc: Byte;
- end;
- { Footer at the end of TGA file.}
- TTargaFooter = packed record
- ExtOff: LongWord; // Extension Area Offset
- DevDirOff: LongWord; // Developer Directory Offset
- Signature: TChar16; // TRUEVISION-XFILE
- Reserved: Byte; // ASCII period '.'
- NullChar: Byte; // 0
- end;
- { TTargaFileFormat class implementation }
- constructor TTargaFileFormat.Create;
- begin
- inherited Create;
- FName := STargaFormatName;
- FCanLoad := True;
- FCanSave := True;
- FIsMultiImageFormat := False;
- FSupportedFormats := TargaSupportedFormats;
- FUseRLE := TargaDefaultRLE;
- AddMasks(STargaMasks);
- RegisterOption(ImagingTargaRLE, @FUseRLE);
- end;
- function TTargaFileFormat.LoadData(Handle: TImagingHandle;
- var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
- var
- Hdr: TTargaHeader;
- Foo: TTargaFooter;
- FooterFound, ExtFound: Boolean;
- I, PSize, PalSize: LongWord;
- Pal: Pointer;
- FmtInfo: TImageFormatInfo;
- WordValue: Word;
- procedure LoadRLE;
- var
- I, CPixel, Cnt: LongInt;
- Bpp, Rle: Byte;
- Buffer, Dest, Src: PByte;
- BufSize: LongInt;
- begin
- with GetIO, Images[0] do
- begin
- // Alocates buffer large enough to hold the worst case
- // RLE compressed data and reads then from input
- BufSize := Width * Height * FmtInfo.BytesPerPixel;
- BufSize := BufSize + BufSize div 2 + 1;
- GetMem(Buffer, BufSize);
- Src := Buffer;
- Dest := Bits;
- BufSize := Read(Handle, Buffer, BufSize);
- Cnt := Width * Height;
- Bpp := FmtInfo.BytesPerPixel;
- CPixel := 0;
- while CPixel < Cnt do
- begin
- Rle := Src^;
- Inc(Src);
- if Rle < 128 then
- begin
- // Process uncompressed pixel
- Rle := Rle + 1;
- CPixel := CPixel + Rle;
- for I := 0 to Rle - 1 do
- begin
- // Copy pixel from src to dest
- case Bpp of
- 1: Dest^ := Src^;
- 2: PWord(Dest)^ := PWord(Src)^;
- 3: PColor24Rec(Dest)^ := PColor24Rec(Src)^;
- 4: PLongWord(Dest)^ := PLongWord(Src)^;
- end;
- Inc(Src, Bpp);
- Inc(Dest, Bpp);
- end;
- end
- else
- begin
- // Process compressed pixels
- Rle := Rle - 127;
- CPixel := CPixel + Rle;
- // Copy one pixel from src to dest (many times there)
- for I := 0 to Rle - 1 do
- begin
- case Bpp of
- 1: Dest^ := Src^;
- 2: PWord(Dest)^ := PWord(Src)^;
- 3: PColor24Rec(Dest)^ := PColor24Rec(Src)^;
- 4: PLongWord(Dest)^ := PLongWord(Src)^;
- end;
- Inc(Dest, Bpp);
- end;
- Inc(Src, Bpp);
- end;
- end;
- // set position in source to real end of compressed data
- Seek(Handle, -(BufSize - LongInt(LongWord(Src) - LongWord(Buffer))),
- smFromCurrent);
- FreeMem(Buffer);
- end;
- end;
- begin
- SetLength(Images, 1);
- with GetIO, Images[0] do
- begin
- // Read targa header
- Read(Handle, @Hdr, SizeOf(Hdr));
- // Skip image ID info
- Seek(Handle, Hdr.IDLength, smFromCurrent);
- // Determine image format
- Format := ifUnknown;
- case Hdr.ImageType of
- 1, 9: Format := ifIndex8;
- 2, 10: case Hdr.PixelSize of
- 15: Format := ifX1R5G5B5;
- 16: Format := ifA1R5G5B5;
- 24: Format := ifR8G8B8;
- 32: Format := ifA8R8G8B8;
- end;
- 3, 11: Format := ifGray8;
- end;
- // Format was not assigned by previous testing (it should be in
- // well formed targas), so formats which reflects bit dept are selected
- if Format = ifUnknown then
- case Hdr.PixelSize of
- 8: Format := ifGray8;
- 15: Format := ifX1R5G5B5;
- 16: Format := ifA1R5G5B5;
- 24: Format := ifR8G8B8;
- 32: Format := ifA8R8G8B8;
- end;
- NewImage(Hdr.Width, Hdr.Height, Format, Images[0]);
- FmtInfo := GetFormatInfo(Format);
- if (Hdr.ColorMapType = 1) and (Hdr.ImageType in [1, 9]) then
- begin
- // Read palette
- PSize := Hdr.ColorMapLength * (Hdr.ColorEntrySize shr 3);
- GetMem(Pal, PSize);
- try
- Read(Handle, Pal, PSize);
- // Process palette
- PalSize := Iff(Hdr.ColorMapLength > FmtInfo.PaletteEntries,
- FmtInfo.PaletteEntries, Hdr.ColorMapLength);
- for I := 0 to PalSize - 1 do
- case Hdr.ColorEntrySize of
- 24:
- with Palette[I] do
- begin
- A := $FF;
- R := PPalette24(Pal)[I].R;
- G := PPalette24(Pal)[I].G;
- B := PPalette24(Pal)[I].B;
- end;
- // I've never seen tga with these palettes so they are untested
- 16:
- with Palette[I] do
- begin
- A := (PWordArray(Pal)[I] and $8000) shr 12;
- R := (PWordArray(Pal)[I] and $FC00) shr 7;
- G := (PWordArray(Pal)[I] and $03E0) shr 2;
- B := (PWordArray(Pal)[I] and $001F) shl 3;
- end;
- 32:
- with Palette[I] do
- begin
- A := PPalette32(Pal)[I].A;
- R := PPalette32(Pal)[I].R;
- G := PPalette32(Pal)[I].G;
- B := PPalette32(Pal)[I].B;
- end;
- end;
- finally
- FreeMemNil(Pal);
- end;
- end;
- case Hdr.ImageType of
- 0, 1, 2, 3:
- // Load uncompressed mode images
- Read(Handle, Bits, Size);
- 9, 10, 11:
- // Load RLE compressed mode images
- LoadRLE;
- end;
- // Check if there is alpha channel present in A1R5GB5 images, if it is not
- // change format to X1R5G5B5
- if Format = ifA1R5G5B5 then
- begin
- if not Has16BitImageAlpha(Width * Height, Bits) then
- Format := ifX1R5G5B5;
- end;
- // We must find true end of file and set input' position to it
- // paint programs appends extra info at the end of Targas
- // some of them multiple times (PSP Pro 8)
- repeat
- ExtFound := False;
- FooterFound := False;
- if Read(Handle, @WordValue, 2) = 2 then
- begin
- // 495 = size of Extension Area
- if WordValue = 495 then
- begin
- Seek(Handle, 493, smFromCurrent);
- ExtFound := True;
- end
- else
- Seek(Handle, -2, smFromCurrent);
- end;
- if Read(Handle, @Foo, SizeOf(Foo)) = SizeOf(Foo) then
- begin
- if Foo.Signature = STargaSignature then
- FooterFound := True
- else
- Seek(Handle, -SizeOf(Foo), smFromCurrent);
- end;
- until (not ExtFound) and (not FooterFound);
- // Some editors save targas flipped
- if Hdr.Desc < 31 then
- FlipImage(Images[0]);
- Result := True;
- end;
- end;
- function TTargaFileFormat.SaveData(Handle: TImagingHandle;
- const Images: TDynImageDataArray; Index: LongInt): Boolean;
- var
- I: LongInt;
- Hdr: TTargaHeader;
- FmtInfo: TImageFormatInfo;
- Pal: PPalette24;
- ImageToSave: TImageData;
- MustBeFreed: Boolean;
- procedure SaveRLE;
- var
- Dest: PByte;
- WidthBytes, Written, I, Total, DestSize: LongInt;
- function CountDiff(Data: PByte; Bpp, PixelCount: Longint): LongInt;
- var
- Pixel: LongWord;
- NextPixel: LongWord;
- N: LongInt;
- begin
- N := 0;
- Pixel := 0;
- NextPixel := 0;
- if PixelCount = 1 then
- begin
- Result := PixelCount;
- Exit;
- end;
- case Bpp of
- 1: Pixel := Data^;
- 2: Pixel := PWord(Data)^;
- 3: PColor24Rec(@Pixel)^ := PColor24Rec(Data)^;
- 4: Pixel := PLongWord(Data)^;
- end;
- while PixelCount > 1 do
- begin
- Inc(Data, Bpp);
- case Bpp of
- 1: NextPixel := Data^;
- 2: NextPixel := PWord(Data)^;
- 3: PColor24Rec(@NextPixel)^ := PColor24Rec(Data)^;
- 4: NextPixel := PLongWord(Data)^;
- end;
- if NextPixel = Pixel then
- Break;
- Pixel := NextPixel;
- N := N + 1;
- PixelCount := PixelCount - 1;
- end;
- if NextPixel = Pixel then
- Result := N
- else
- Result := N + 1;
- end;
- function CountSame(Data: PByte; Bpp, PixelCount: LongInt): LongInt;
- var
- Pixel: LongWord;
- NextPixel: LongWord;
- N: LongInt;
- begin
- N := 1;
- Pixel := 0;
- NextPixel := 0;
- case Bpp of
- 1: Pixel := Data^;
- 2: Pixel := PWord(Data)^;
- 3: PColor24Rec(@Pixel)^ := PColor24Rec(Data)^;
- 4: Pixel := PLongWord(Data)^;
- end;
- PixelCount := PixelCount - 1;
- while PixelCount > 0 do
- begin
- Inc(Data, Bpp);
- case Bpp of
- 1: NextPixel := Data^;
- 2: NextPixel := PWord(Data)^;
- 3: PColor24Rec(@NextPixel)^ := PColor24Rec(Data)^;
- 4: NextPixel := PLongWord(Data)^;
- end;
- if NextPixel <> Pixel then
- Break;
- N := N + 1;
- PixelCount := PixelCount - 1;
- end;
- Result := N;
- end;
- procedure RleCompressLine(Data: PByte; PixelCount, Bpp: LongInt; Dest:
- PByte; var Written: LongInt);
- const
- MaxRun = 128;
- var
- DiffCount: LongInt;
- SameCount: LongInt;
- RleBufSize: LongInt;
- begin
- RleBufSize := 0;
- while PixelCount > 0 do
- begin
- DiffCount := CountDiff(Data, Bpp, PixelCount);
- SameCount := CountSame(Data, Bpp, PixelCount);
- if (DiffCount > MaxRun) then
- DiffCount := MaxRun;
- if (SameCount > MaxRun) then
- SameCount := MaxRun;
- if (DiffCount > 0) then
- begin
- Dest^ := Byte(DiffCount - 1);
- Inc(Dest);
- PixelCount := PixelCount - DiffCount;
- RleBufSize := RleBufSize + (DiffCount * Bpp) + 1;
- Move(Data^, Dest^, DiffCount * Bpp);
- Inc(Data, DiffCount * Bpp);
- Inc(Dest, DiffCount * Bpp);
- end;
- if SameCount > 1 then
- begin
- Dest^ := Byte((SameCount - 1) or $80);
- Inc(Dest);
- PixelCount := PixelCount - SameCount;
- RleBufSize := RleBufSize + Bpp + 1;
- Inc(Data, (SameCount - 1) * Bpp);
- case Bpp of
- 1: Dest^ := Data^;
- 2: PWord(Dest)^ := PWord(Data)^;
- 3: PColor24Rec(Dest)^ := PColor24Rec(Data)^;
- 4: PLongWord(Dest)^ := PLongWord(Data)^;
- end;
- Inc(Data, Bpp);
- Inc(Dest, Bpp);
- end;
- end;
- Written := RleBufSize;
- end;
- begin
- with ImageToSave do
- begin
- // Allocate enough space to hold the worst case compression
- // result and then compress source's scanlines
- WidthBytes := Width * FmtInfo.BytesPerPixel;
- DestSize := WidthBytes * Height;
- DestSize := DestSize + DestSize div 2 + 1;
- GetMem(Dest, DestSize);
- Total := 0;
- try
- for I := 0 to Height - 1 do
- begin
- RleCompressLine(@PByteArray(Bits)[I * WidthBytes], Width,
- FmtInfo.BytesPerPixel, @PByteArray(Dest)[Total], Written);
- Total := Total + Written;
- end;
- GetIO.Write(Handle, Dest, Total);
- finally
- FreeMem(Dest);
- end;
- end;
- end;
- begin
- Result := False;
- if MakeCompatible(Images[Index], ImageToSave, MustBeFreed) then
- with GetIO, ImageToSave do
- try
- FmtInfo := GetFormatInfo(Format);
- // Fill targa header
- FillChar(Hdr, SizeOf(Hdr), 0);
- Hdr.IDLength := 0;
- Hdr.ColorMapType := Iff(FmtInfo.PaletteEntries > 0, 1, 0);
- Hdr.Width := Width;
- Hdr.Height := Height;
- Hdr.PixelSize := FmtInfo.BytesPerPixel * 8;
- Hdr.ColorMapLength := FmtInfo.PaletteEntries;
- Hdr.ColorEntrySize := Iff(FmtInfo.PaletteEntries > 0, 24, 0);
- Hdr.ColorMapOff := 0;
- // This indicates that targa is stored in top-left format
- // as our images -> no flipping is needed.
- Hdr.Desc := 32;
- // Set alpha channel size in descriptor (mostly ignored by other software though)
- if Format = ifA8R8G8B8 then
- Hdr.Desc := Hdr.Desc or 8
- else if Format = ifA1R5G5B5 then
- Hdr.Desc := Hdr.Desc or 1;
- // Choose image type
- if FmtInfo.IsIndexed then
- Hdr.ImageType := Iff(FUseRLE, 9, 1)
- else
- if FmtInfo.HasGrayChannel then
- Hdr.ImageType := Iff(FUseRLE, 11, 3)
- else
- Hdr.ImageType := Iff(FUseRLE, 10, 2);
- Write(Handle, @Hdr, SizeOf(Hdr));
- // Write palette
- if FmtInfo.PaletteEntries > 0 then
- begin
- GetMem(Pal, FmtInfo.PaletteEntries * SizeOf(TColor24Rec));
- try
- for I := 0 to FmtInfo.PaletteEntries - 1 do
- with Pal[I] do
- begin
- R := Palette[I].R;
- G := Palette[I].G;
- B := Palette[I].B;
- end;
- Write(Handle, Pal, FmtInfo.PaletteEntries * SizeOf(TColor24Rec));
- finally
- FreeMemNil(Pal);
- end;
- end;
- if FUseRLE then
- // Save rle compressed mode images
- SaveRLE
- else
- // Save uncompressed mode images
- Write(Handle, Bits, Size);
- Result := True;
- finally
- if MustBeFreed then
- FreeImage(ImageToSave);
- end;
- end;
- procedure TTargaFileFormat.ConvertToSupported(var Image: TImageData;
- const Info: TImageFormatInfo);
- var
- ConvFormat: TImageFormat;
- begin
- if Info.HasGrayChannel then
- // Convert all grayscale images to Gray8 (preserve alpha of AxGrayx formats)
- ConvFormat := IffFormat(not Info.HasAlphaChannel, ifGray8, ifA8R8G8B8)
- else if Info.IsIndexed then
- // Convert all indexed images to Index8
- ConvFormat := ifIndex8
- else if Info.HasAlphaChannel then
- // Convert images with alpha channel to A8R8G8B8
- ConvFormat := ifA8R8G8B8
- else if Info.UsePixelFormat then
- // Convert 16bit images (without alpha channel) to A1R5G5B5
- ConvFormat := ifA1R5G5B5
- else
- // Convert all other formats to R8G8B8
- ConvFormat := ifR8G8B8;
- ConvertImage(Image, ConvFormat);
- end;
- function TTargaFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
- var
- Hdr: TTargaHeader;
- ReadCount: LongInt;
- begin
- Result := False;
- if Handle <> nil then
- begin
- ReadCount := GetIO.Read(Handle, @Hdr, SizeOf(Hdr));
- GetIO.Seek(Handle, -ReadCount, smFromCurrent);
- Result := (ReadCount >= SizeOf(Hdr)) and
- (Hdr.ImageType in [0, 1, 2, 3, 9, 10, 11]) and
- (Hdr.PixelSize in [1, 8, 15, 16, 24, 32]) and
- (Hdr.ColorEntrySize in [0, 16, 24, 32]);
- end;
- end;
- initialization
- RegisterImageFileFormat(TTargaFileFormat);
- {
- File Notes:
- -- TODOS ----------------------------------------------------
- - nothing now
- -- 0.21 Changes/Bug Fixes -----------------------------------
- - MakeCompatible method moved to base class, put ConvertToSupported here.
- GetSupportedFormats removed, it is now set in constructor.
- - Made public properties for options registered to SetOption/GetOption
- functions.
- - Changed extensions to filename masks.
- - Changed SaveData, LoadData, and MakeCompatible methods according
- to changes in base class in Imaging unit.
- -- 0.17 Changes/Bug Fixes -----------------------------------
- - 16 bit images are usually without alpha but some has alpha
- channel and there is no indication of it - so I have added
- a check: if all pixels of image are with alpha = 0 image is treated
- as X1R5G5B5 otherwise as A1R5G5B5
- - fixed problems with some nonstandard 15 bit images
- }
- end.
|