unit KOLPcx;
{* PCX - PC Paintbrush format (ZSoft) support for KOL.
 (C) by Kladov Vladimir 30-Sep-2002
 ( bonanzas@xcl.cjb.net, http://xcl.cjb.net )
 v1.0 - reading PCX only (it is converted to DIB bitmap when loaded)
}

interface

{$RANGECHECKS OFF}

uses
 Windows, KOL;


type
  TRGBPixel = packed record
    R, G, B: Byte;
  end;

  PPCXHeader = ^TPCXHeader;
  TPCXHeader = packed record
    Manufacturer      : Byte; //Постоянный флаг 10 = ZSoft .PCX
    Version           : Byte; //0 = Версия 2.5
                              //2 = Версия 2.8 с информацией о палитре
                              //3 = Версия 2.8 без информации о палитре
                              //5 = Версия 3.0
    Encoding          : Byte; //1 = .PCX кодирование длинными сериями
    BitsPerPixel      : Byte; //Число бит на пиксел в слое
    Xmin              : Word; //Размеры изображения (Xmin, Ymin) - (Xmax, Ymax) в пикселах включительно
    Ymin              : Word;
    Xmax              : Word;
    Ymax              : Word;
    Hres              : Word; //Горизонтальное разрешение создающего устройства
    Vres              : Word; //Вертикальное разрешение создающего устройства
    ColorMap          : array[ 0..15 ] of TRGBPixel;
    Reserved          : Byte;
    NPlanes           : Byte; //Число цветовых слоев
    BytesPerLine      : Word; //Число байт на строку в цветовом слое
                              //(для PCX-файлов всегда должно быть четным)
    PaletteInfo       : Byte; //Как интерпретировать палитру:
                              //1 = цветная/черно-белая,
                              //2 = градации серого
    Filler            : array[ 0..58 ] of Byte; // нули
  end;

  PPCX = ^TPCX;
  TPCX = object( TObj )
  {* PCX implementation object}
  protected
    //FError: TPCXError;
    FBitmap: PBitmap;
  protected
    {Returns image width and height}
    function GetWidth: Integer;
    function GetHeight: Integer;
    {Returns if the image is empty}
    function GetEmpty: Boolean;
  public
    procedure Clear;
    {Draws the image into a canvas}
    procedure Draw(DC: HDC; X, Y: Integer);
    procedure StretchDraw( DC: HDC; const Rect: TRect );
    {Width and height properties}
    property Width: Integer read GetWidth;
    property Height: Integer read GetHeight;
    {Property to return if the image is empty or not}
    property Empty: Boolean read GetEmpty;
    {Object being created and destroyed}
    destructor Destroy; virtual;
    function LoadFromFile(const Filename: String): Boolean;
    //procedure SaveToFile(const Filename: String);
    function LoadFromStream(Stream: PStream): Boolean;
    //procedure SaveToStream(Stream: PStream);
    {Loading the image from resources}
    function LoadFromResourceName(Instance: HInst; const Name: String): Boolean;
    function LoadFromResourceID(Instance: HInst; ResID: Integer): Boolean;
    {}
    property Bitmap: PBitmap read FBitmap;
  end;

function NewPCX: PPCX;

implementation

function NewPCX: PPCX;
begin
  new( Result, Create );
end;

{ TPCX }

procedure TPCX.Clear;
begin
  Free_And_Nil( FBitmap );
end;

destructor TPCX.Destroy;
begin
  FBitmap.Free;
  inherited;
end;

procedure TPCX.Draw(DC: HDC; X, Y: Integer);
begin
  if Empty then Exit;
  FBitmap.Draw( DC, X, Y );
end;

function TPCX.GetEmpty: Boolean;
begin
  Result := (FBitmap=nil) or FBitmap.Empty;
end;

function TPCX.GetHeight: Integer;
begin
  if Empty then
    Result := 0
  else
    Result := FBitmap.Height;
end;

function TPCX.GetWidth: Integer;
begin
  if Empty then
    Result := 0
  else
    Result := FBitmap.Width;
end;

function TPCX.LoadFromFile(const Filename: String): Boolean;
var Strm: PStream;
begin
  Strm := NewReadFileStream( Filename );
  Result := LoadFromStream( Strm );
  Strm.Free;
end;

function TPCX.LoadFromResourceID(Instance: HInst; ResID: Integer): Boolean;
var Strm: PStream;
begin
  Strm := NewMemoryStream;
  Resource2Stream( Strm, Instance, PChar( ResID ), RT_RCDATA );
  Strm.Position := 0;
  Result := LoadFromStream( Strm );
  Strm.Free;
end;

function TPCX.LoadFromResourceName(Instance: HInst; const Name: String): Boolean;
var Strm: PStream;
begin
  Strm := NewMemoryStream;
  Resource2Stream( Strm, Instance, PChar( Name ), RT_RCDATA );
  Strm.Position := 0;
  Result := LoadFromStream( Strm );
  Strm.Free;
end;

function TPCX.LoadFromStream(Stream: PStream): Boolean;
type
  TRGBPixelExt = packed record
    Pixel: TRGBPixel;
    Dummy: Byte;
  end;
var
  StartPos: DWORD;

    procedure Decode;
    var Header: TPCXHeader;
        Format: TPixelFormat;
        Buffer, Dest, Src, SrcBuf: PByte;
        W, H, ImgSize, I, BitIdx, X, SrcSize: Integer;
        B, B1, B2, B3, B4: Byte;
        RGBPixelExt: TRGBPixelExt;
    begin
      Result := FALSE;
      Clear;
      if Stream.Read( Header, Sizeof( Header ) ) < Sizeof( Header ) then Exit;
      if Header.Manufacturer <> 10 then Exit;
      if (Header.BitsPerPixel = 1) and (Header.NPlanes = 1) then
        Format := pf1bit
      else
      if (Header.BitsPerPixel = 1) and (Header.NPlanes = 4) or
         (Header.BitsPerPixel = 4) and (Header.NPlanes = 1) then
        Format := pf4bit
      else
      if (Header.BitsPerPixel = 8) and (Header.NPlanes = 1) then
        Format := pf8bit
      else
      if (Header.BitsPerPixel = 8) and (Header.NPlanes = 3) then
        Format := pf24bit
      else
        Exit;

      W := Header.Xmax - Header.Xmin + 1;
      H := Header.Ymax - Header.Ymin + 1;
      ImgSize := Header.NPlanes * Header.BytesPerLine * H;
      GetMem( Buffer, ImgSize );
      if Buffer = nil then Exit;

      //-------------------- декодирование ----------------------
      SrcSize := Stream.Size - Stream.Position;
      if SrcSize > ImgSize * 2 then
        SrcSize := ImgSize * 2;
      GetMem( SrcBuf, SrcSize );
      SrcSize := Stream.Read( SrcBuf^, SrcSize );
      Src := SrcBuf;
      Dest := Buffer;
      while (DWORD( Dest ) < DWORD( Buffer ) + DWORD( ImgSize ) ) and
            (DWORD( Src ) < DWORD( SrcBuf ) + DWORD( SrcSize ) ) do
      begin
        //Stream.Read( B, 1 );
        B := Src^; Inc( Src );
        if B >= $C0 then
        begin
          I := B and $3F;
          //Stream.Read( B, 1 );
          B := Src^; Inc( Src );
          for I := I-1 downto 0 do
          begin
            Dest^ := B; Inc( Dest );
          end;
        end
          else
        begin
          Dest^ := B; Inc( Dest );
        end;
      end;
      //Stream.Position := StartPos + Sizeof( Header ) + DWORD( Src ) - DWORD( SrcBuf );
      FreeMem( SrcBuf );

      FBitmap := NewDIBBitmap( W, H, Format );
      //-------------------- формирование изображения ------------------------
      if (Format = pf4bit) and (Header.NPlanes = 4) then
      begin
        for I := 0 to H-1 do
        begin
          Dest := FBitmap.ScanLine[ I ];
          BitIdx := 8;
          B1 := 0; B2 := 0; B3 := 0; B4 := 0;
          Src := Pointer( Integer( Buffer ) + Header.BytesPerLine * 4 * I );
          for X := 0 to W div 2 - 1 do
          begin
            if BitIdx >= 8 then
            begin
              BitIdx := 0;
              B1 := Src^;
              B2 := PByte( Integer( Src ) + Header.BytesPerLine )^;
              B3 := PByte( Integer( Src ) + Header.BytesPerLine * 2 )^;
              B4 := PByte( Integer( Src ) + Header.BytesPerLine * 3 )^;
              Inc( Src );
            end;
            B := ((B1 and $80) shr 3) or ((B2 and $80) shr 2) or ((B3 and $80) shr 1) or (B4 and $80)
              or ((B1 and $40) shr 6) or ((B2 and $40) shr 5) or ((B3 and $40) shr 4) or ((B4 and $40) shr 3);
            B1 := B1 shl 2;
            B2 := B2 shl 2;
            B3 := B3 shl 2;
            B4 := B4 shl 2;
            Dest^ := B;
            Inc( Dest );
            Inc( BitIdx, 2 );
          end;
        end; // конец загрузки 16-цветного изображения по слоям
      end
        else
      if Format = pf24bit then
      begin
        for I := 0 to H-1 do
        begin
          Dest := FBitmap.ScanLine[ I ];
          Src := PByte( Integer( Buffer ) + Header.BytesPerLine * 3 * I );
          for X := 0 to W-1 do
          begin
            B1 := Src^;
            B2 := PByte( Integer( Src ) + Header.BytesPerLine )^;
            B3 := PByte( Integer( Src ) + Header.BytesPerLine*2 )^;
            Dest^ := B3; Inc( Dest );
            Dest^ := B2; Inc( Dest );
            Dest^ := B1; Inc( Dest );
            Inc( Src );
          end;
        end; // конец загрузки монохромного, 256-цветного изображения
      end
        else
      //if (Format in [pf8bit,pf1bit]) or ((Format = pf4bit) and (Header.NPlanes = 1)) then
      begin
        Src := Buffer;
        for I := 0 to H-1 do
        begin
          Dest := FBitmap.ScanLine[ I ];
          Move( Src^, Dest^, Header.BytesPerLine );
          if Format = pf4bit then
          begin
            for X := 0 to W div 2 - 1 do
            begin
              B := Dest^;
              B := ((B and $11) shl 2) or ((B and $44) shr 2) or
                   (B and $AA);
              Dest^ := B;
              Inc( Dest );
            end;
          end;
          Inc( Src, Header.BytesPerLine );
        end; // конец загрузки монохромного, 256-цветного изображения или 16-цветного в одном слое
      end;
      //----------- загрузка палитры ------------------
      if Format = pf8bit then
      begin
        B := 0;
        if Stream.Size > 768 then
        begin
          Stream.Position := Stream.Size - 769;
          Stream.Read( B, 1 );
        end;
        if (Header.Version in [2,5]) and (B in [10,12]) then
        begin // есть своя палитра, прочитаем
          RGBPixelExt.Dummy := 0;
          GetMem( SrcBuf, 768 );
          Stream.Read( SrcBuf^, 768 );
          Src := SrcBuf;
          for I := 0 to 255 do
          begin
            RGBPixelExt.Pixel.B := Src^; Inc( Src );
            RGBPixelExt.Pixel.G := Src^; Inc( Src );
            RGBPixelExt.Pixel.R := Src^; Inc( Src );
            if B = 10 then
            begin
              RGBPixelExt.Pixel.R := RGBPixelExt.Pixel.R shl 2;
              RGBPixelExt.Pixel.G := RGBPixelExt.Pixel.G shl 2;
              RGBPixelExt.Pixel.B := RGBPixelExt.Pixel.B shl 2;
            end;
            FBitmap.DIBPalEntries[ I ] := Integer( RGBPixelExt );
          end;
          FreeMem( SrcBuf );
        end;
      end
        else
      if Format in [pf1bit, pf4bit] then
      begin  // загрузка палитры для 16-цветных или монохромных изображений
        RGBPixelExt.Dummy := 0;
        for I := 0 to FBitmap.DIBPalEntryCount-1 do
        begin
          RGBPixelExt.Pixel := Header.ColorMap[ I ];
          B := RGBPixelExt.Pixel.R;
          RGBPixelExt.Pixel.R := RGBPixelExt.Pixel.B;
          RGBPixelExt.Pixel.B := B;
          FBitmap.DIBPalEntries[ I ] := Integer( RGBPixelExt );
        end;
      end;
      FreeMem( Buffer );
      Result := TRUE;
    end;
begin
  StartPos:= Stream.Position;
  Decode;
  If Result = False Then Stream.Position:= StartPos;
end;

procedure TPCX.StretchDraw(DC: HDC; const Rect: TRect);
begin
  If Empty = False Then FBitmap.StretchDraw( DC, Rect );
end;

end.
