{
  $Id: ImagingPcx.pas 100 2007-06-28 21:09:52Z galfar $
  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 for ZSoft Paintbrush images known as PCX.}
unit ImagingPcx;

{$I ImagingOptions.inc}

interface

uses
  ImagingTypes, Imaging, ImagingFormats, ImagingUtility, ImagingIO;

type
  { Class for loading  ZSoft Paintbrush images known as PCX. It is old
    format which can store 1bit, 2bit, 4bit, 8bit, and 24bit (and 32bit but is
    probably non-standard) images. Only loading is supported (you can still come
    accross some PCX files) but saving is not (I don't wont this venerable format
    to spread).}
  TPCXFileFormat = class(TImageFileFormat)
  protected
    function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
      OnlyFirstLevel: Boolean): Boolean; override;
  public
    constructor Create; override;
    function TestFormat(Handle: TImagingHandle): Boolean; override;
  end;

implementation

const
  SPCXFormatName = 'ZSoft Paintbrush Image';
  SPCXMasks      = '*.pcx';

type
  TPCXHeader = packed record
    Id: Byte;            // Always $0A
    Version: Byte;       // 0, 2, 3, 4, 5
    Encoding: Byte;      // 0, 1
    BitsPerPixel: Byte;  // 1, 2, 4, 8
    X0, Y0: Word;        // Image window top-left
    X1, Y1: Word;        // Image window bottom-right
    DpiX: Word;
    DpiY: Word;
    Palette16: array [0..15] of TColor24Rec;
    Reserved1: Byte;
    Planes: Byte;        // 1, 3, 4
    BytesPerLine: Word;
    PaletteType: Word;   // 1: color or s/w   2: grayscale
    Reserved2: array [0..57] of Byte;
  end;

{ TPCXFileFormat }

constructor TPCXFileFormat.Create;
begin
  inherited Create;
  FName := SPCXFormatName;
  FCanLoad := True;
  FCanSave := False;
  FIsMultiImageFormat := False;

  AddMasks(SPCXMasks);
end;

function TPCXFileFormat.LoadData(Handle: TImagingHandle;
  var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
const
  ifMono: TImageFormat = TImageFormat(250);
  ifIndex2: TImageFormat = TImageFormat(251);
  ifIndex4: TImageFormat = TImageFormat(252);
var
  Hdr: TPCXHeader;
  PalID, B: Byte;
  PalPCX: TPalette24Size256;
  FileDataFormat: TImageFormat;
  I, J, UncompSize, BytesPerLine, ByteNum, BitNum: LongInt;
  UncompData, RowPointer, PixelIdx: PByte;
  Pixel24: PColor24Rec;
  Pixel32: PColor32Rec;
  AlphaPlane, RedPlane, GreenPlane, BluePlane,
  Plane1, Plane2, Plane3, Plane4: PByteArray;

  procedure RleDecode(Target: PByte; UnpackedSize: LongInt);
  var
    Count: LongInt;
    Source: Byte;
  begin
    while UnpackedSize > 0 do
    with GetIO do
    begin
      GetIO.Read(Handle, @Source, SizeOf(Source));
      if (Source and $C0) = $C0 then
      begin
        // RLE data
        Count := Source and $3F;
        if UnpackedSize < Count then
          Count := UnpackedSize;
        Read(Handle, @Source, SizeOf(Source));
        FillChar(Target^, Count, Source);
        //Inc(Source);
        Inc(Target, Count);
        Dec(UnpackedSize, Count);
      end
      else
      begin
        // Uncompressed data
        Target^ := Source;
        Inc(Target);
        Dec(UnpackedSize);
      end;
    end;
  end;

begin
  Result := False;
  SetLength(Images, 1);
  with GetIO, Images[0] do
  begin
    // Read PCX header and store input position (start of image data)
    Read(Handle, @Hdr, SizeOf(Hdr));
    FileDataFormat := ifUnknown;

    // Determine image's data format and find its Imaging equivalent
    // (using some custom TImageFormat constants)
    case Hdr.BitsPerPixel of
      1:
        case Hdr.Planes of
          1: FileDataFormat := ifMono;
          4: FileDataFormat := ifIndex4;
        end;
      2: FileDataFormat := ifIndex2;
      4: FileDataFormat := ifIndex4;
      8:
        case Hdr.Planes of
          1: FileDataFormat := ifIndex8;
          3: FileDataFormat := ifR8G8B8;
          4: FileDataFormat := ifA8R8G8B8;
        end;
    end;

    // No compatible Imaging format found, exit
    if FileDataFormat = ifUnknown then
      Exit;

    // Get width, height, and output data format (unsupported formats
    // like ifMono are converted later to ifIndex8)
    Width := Hdr.X1 - Hdr.X0 + 1;
    Height := Hdr.Y1 - Hdr.Y0 + 1;
    if FileDataFormat in [ifIndex8, ifR8G8B8] then
      Format := FileDataFormat
    else
      Format := ifIndex8;

    NewImage(Width, Height, Format, Images[0]);

    if not (FileDataFormat in [ifIndex8, ifR8G8B8]) then
    begin
      // other formats use palette embedded to file header
      for I := Low(Hdr.Palette16) to High(Hdr.Palette16) do
      begin
        Palette[I].A := $FF;
        Palette[I].R := Hdr.Palette16[I].B;
        Palette[I].G := Hdr.Palette16[I].G;
        Palette[I].B := Hdr.Palette16[I].R;
      end;
    end;

    // Now we determine various data sizes
    BytesPerLine := Hdr.BytesPerLine * Hdr.Planes;
    UncompSize := BytesPerLine * Height;

    GetMem(UncompData, UncompSize);
    try
      if Hdr.Encoding = 1 then
      begin
        // Image data is compressed -> read and decompress
        RleDecode(UncompData, UncompSize);
      end
      else
      begin
        // Just read uncompressed data
        Read(Handle, UncompData, UncompSize);
      end;

      if FileDataFormat in [ifR8G8B8, ifA8R8G8B8] then
      begin
        // RGB and ARGB images are stored in layout different from
        // Imaging's (and most other file formats'). First there is
        // Width red values then there is Width green values and so on
        RowPointer := UncompData;

        if FileDataFormat = ifA8R8G8B8 then
        begin
          Pixel32 := Bits;
          for I := 0 to Height - 1 do
          begin
            AlphaPlane := PByteArray(RowPointer);
            RedPlane :=   @AlphaPlane[Hdr.BytesPerLine];
            GreenPlane := @AlphaPlane[Hdr.BytesPerLine * 2];
            BluePlane :=  @AlphaPlane[Hdr.BytesPerLine * 3];
            for J := 0 to Width - 1 do
            begin
              Pixel32.A := AlphaPlane[J];
              Pixel32.R := RedPlane[J];
              Pixel32.G := GreenPlane[J];
              Pixel32.B := BluePlane[J];
              Inc(Pixel32);
            end;
            Inc(RowPointer, BytesPerLine);
          end;
        end
        else
        begin
          Pixel24 := Bits;
          for I := 0 to Height - 1 do
          begin
            RedPlane :=   PByteArray(RowPointer);
            GreenPlane := @RedPlane[Hdr.BytesPerLine];
            BluePlane :=  @RedPlane[Hdr.BytesPerLine * 2];
            for J := 0 to Width - 1 do
            begin
              Pixel24.R := RedPlane[J];
              Pixel24.G := GreenPlane[J];
              Pixel24.B := BluePlane[J];
              Inc(Pixel24);
            end;
            Inc(RowPointer, BytesPerLine);
          end;
        end;
      end
      else if FileDataFormat = ifIndex8 then
      begin
        // Just copy 8bit lines
        for I := 0 to Height - 1 do
          Move(PByteArray(UncompData)[I * Hdr.BytesPerLine], PByteArray(Bits)[I * Width], Width);
      end
      else if FileDataFormat = ifMono then
      begin
        // Convert 1bit images to ifIndex8
        Convert1To8(UncompData, Bits, Width, Height, Hdr.BytesPerLine);
      end
      else if FileDataFormat = ifIndex2 then
      begin
        // Convert 2bit images to ifIndex8. Note that 2bit PCX images
        // usually use (from specs, I've never seen one myself) CGA palette
        // which is not array of RGB tripplets. So 2bit PCXs are loaded but
        // their colors would be wrong
        Convert2To8(UncompData, Bits, Width, Height, Hdr.BytesPerLine);
      end
      else if FileDataFormat = ifIndex4 then
      begin
        // 4bit images can be stored similar to RGB images (in four one bit planes)
        // or like array of nibbles (which is more common)
        if (Hdr.BitsPerPixel = 1) and (Hdr.Planes = 4) then
        begin
          RowPointer := UncompData;
          PixelIdx := Bits;
          for I := 0 to Height - 1 do
          begin
            Plane1 := PByteArray(RowPointer);
            Plane2 := @Plane1[Hdr.BytesPerLine];
            Plane3 := @Plane1[Hdr.BytesPerLine * 2];
            Plane4 := @Plane1[Hdr.BytesPerLine * 3];

            for J := 0 to Width - 1 do
            begin
              B := 0;
              ByteNum := J div 8;
              BitNum := 7 - (J mod 8);
              if (Plane1[ByteNum] shr BitNum) and $1 <> 0 then B := B or $01;
              if (Plane2[ByteNum] shr BitNum) and $1 <> 0 then B := B or $02;
              if (Plane3[ByteNum] shr BitNum) and $1 <> 0 then B := B or $04;
              if (Plane4[ByteNum] shr BitNum) and $1 <> 0 then B := B or $08;
              PixelIdx^ := B;
              Inc(PixelIdx);
            end;
            Inc(RowPointer, BytesPerLine);
          end;
        end
        else if (Hdr.BitsPerPixel = 4) and (Hdr.Planes = 1) then
        begin
          // Convert 4bit images to ifIndex8 
          Convert4To8(UncompData, Bits, Width, Height, Hdr.BytesPerLine);
        end
      end;

      if FileDataFormat = ifIndex8 then
      begin
        // 8bit palette is appended at the end of the file
        // with $0C identifier
        //Seek(Handle, -769, smFromEnd);
        Read(Handle, @PalID, SizeOf(PalID));
        if PalID = $0C then
        begin
          Read(Handle, @PalPCX, SizeOf(PalPCX));
          for I := Low(PalPCX) to High(PalPCX) do
          begin
            Palette[I].A := $FF;
            Palette[I].R := PalPCX[I].B;
            Palette[I].G := PalPCX[I].G;
            Palette[I].B := PalPCX[I].R;
          end;
        end
        else
          Seek(Handle, -SizeOf(PalID), smFromCurrent);
      end;

    finally
      FreeMem(UncompData);
    end;
    Result := True;
  end;
end;

function TPCXFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
var
  Hdr: TPCXHeader;
  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.Id = $0A) and
      (Hdr.Version in [0, 2, 3, 4, 5]) and
      (Hdr.Encoding in [0..1]) and
      (Hdr.BitsPerPixel in [1, 2, 4, 8]) and
      (Hdr.Planes in [1, 3, 4]) and
      (Hdr.PaletteType in [1..2]);
  end;

end;

initialization
  RegisterImageFileFormat(TPCXFileFormat);

{
  File Notes:

  -- TODOS ----------------------------------------------------
    - nothing now

  -- 0.21 Changes/Bug Fixes -----------------------------------
    - Made loader stream-safe - stream position is exactly at the end of the
      image after loading and file size doesn't need to be know during the process.
    - Initial TPCXFileFormat class implemented.

}

end.
