unit PcxClass;
{*=====================================================================
  Classes:   PCX_Reader

  File:      PCXClass.pas

  Summary:
       PCXClass.pas contain a moreless useful PCX-viewer class, and some
			 others to make it working. BitWise also needed (BitWiseClass.pas).
			 It will work with the most widely distributed 256 or 24bit color
			 pictures and the older 16 bit ones.

			 Known issues are the possibly problems with true CGA pictures
			 and the packed 16 bit ones (with 4 planes).
			 Some coding solution surely will be like a stone-age way
			 for professionals, but probably not for starter ones in the C#-world.

			 This .NET-class was implemented by Endre I Simay according to

---------------------------------------------------------------------
  This file is submitted by:

	 endresy@axelero.hu
	 Endre I. Simay,
	 Hungary

THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
=====================================================================*}

interface
uses
  System.IO,
  bitwiseclass;

type
  VideoModes = class //  video modes
  public
    CGA4,
      CGA2,
      EGA,
      VGA,
      MCGA,
      MCGA2: byte;
    constructor Create;
  end;

  dword = UInt32;

  BITMAPFILEHEADER = record
    bfType: word;
    bfSize: dword;
    bfReserved1,
      bfReserved2: word;
    bfOffBits: dword;
  end; // Size=14

  BITMAPINFOHEADER = record
    biSize: dword;
    biWidth,
      biHeight: Int32;
    biPlanes,
      biBitCount: word;
    biCompression,
      biSizeImage: dword;
    biXPelsPerMeter,
      biYPelsPerMeter: Int32;
    biClrUsed,
      biClrImportant: dword;
  end; // Size=40

  PCX_Header = class
  public
    Manufacturer: byte; // Always 10 for PCX file
    Version: byte;
      {* 2 - old PCX - no palette (NOT used anymore),
         3 - no palette,
         4 - Microsoft Windows - no palette (only in
         old files, New Windows version USES 3),
         5 - WITH palette *}
    Encoding: byte;
      {* 1 is PCX, it is possible that we may add
         additional encoding methods IN the future *}
    Bits_per_pixel: byte;
      {* Number of bits to represent a pixel
         (per plane) - 1, 2, 4, or 8 *}
    Xmin: Int16; // Image window dimensions (inclusive)
    Ymin: Int16; // Xmin, Ymin are usually zero (not always)
    Xmax: Int16;
    Ymax: Int16;
    Hdpi: Int16; // Resolution of image (dots per inch)
    Vdpi: Int16; // Set to scanner resolution - 300 is default
    ColorMap: array[0..15, 0..2] of byte;
      {* RGB palette data (16 colors or less)
         256 color palette is appended to END OF FILE *}
    Reserved: byte;
      {* (used to contain video mode)
         now it is ignored - just set to zero *}
    Nplanes: byte; // Number of planes
    Bytes_per_line_per_plane,
      {* Number of bytes to allocate
         for a scanline plane.
         MUST be an an EVEN number!
         DO NOT calculate from Xmax-Xmin! *}
    PaletteInfo: word;
      {* 1 = black & white or color image,
         2 = grayscale image - ignored IN PB4, PB4+
         palette must also be set to shades of gray! *}
    HscreenSize: Int16; // added for PC Paintbrush IV Plus ver 1.0,
    VscreenSize: Int16; // PC Paintbrush IV ver 1.02 (and later)
    Filler: array[0..53] of byte; // Set to zeros but mainly indifferent paddings to 128 byte

    constructor Create;
  end;

  PCX_Reader = class
  private
    PictureMode: Int16;
    RealWidth,
      RealHeight,
      Error,
      Index: integer;
    data: byte;
    bytes_per_line: word;
    video: VideoModes;
    PCXheader: PCX_Header;
    PCXStream: System.IO.StreamReader;
    breader: System.IO.BinaryReader;
    bwrite: System.IO.BinaryWriter;
    ms: MemoryStream;
    Fbitmapfileheader: BITMAPFILEHEADER;
    Fbitmapinfoheader: BITMAPINFOHEADER;
    errors: array[0..6] of string;
    procedure PCX_Reader_Init;
    procedure FromFileToStream(FilePath: string; IStream: Stream);
    procedure FromStreamToStream(IStream: Stream);
    procedure FillBitmapStructs;
    procedure ReadPalettes(IStream: Stream);
    procedure Read256palette(IStream: Stream); // Read in a 256 color palette at end of PCX file
    procedure ReadMCGA2palette(IStream: Stream); // Read in a 24b color PCX file
    procedure ReadVGA16palette(IStream: Stream); // Read in a 16 color PCX file

  strict protected
      procedure Dispose(Disposing: Boolean);

  public
    constructor Create;
    function PCX_ErrorString: string;
    function PCX_ErrorNumber: integer;
    function FromStream(Source: Stream): Stream;
    function FromFile(FilePath: string): Stream;
  end;

implementation
const
  PCX_errors: array[0..6] of string = (
    'No problems.',
    'Problem with opening the sourcefile.',
    'Problem to read the PCX-header.',
    'Problem with initializing the BinaryReader.',
    'Not a valid PCX-file for this decoder.',
    'Problem with reading the palette.',
    'Any problem with reading from or writing to a stream of the image.')
  ;

constructor VideoModes.Create; //  video modes
begin
  inherited;
  CGA4 := $04;
  CGA2 := $06;
  EGA := $10;
  VGA := $12;
  MCGA := $13;
  MCGA2 := $15;
end;

constructor PCX_Header.Create;
begin
  inherited;
end;

constructor PCX_Reader.Create;
begin
  inherited;
  PCX_Reader_Init;
end;

procedure PCX_Reader.Dispose(Disposing: Boolean);
begin
  if (disposing) then
  begin
    if (bwrite <> nil) then
    begin
      bwrite.Close();
    end;
  end;
end;

procedure PCX_Reader.PCX_Reader_Init;
begin
  video := VideoModes.Create;
  PCXheader := PCX_Header.Create;
  PictureMode := 0;
  Error := 0;
  Index := 0;
  data := 0;
  bytes_per_line := 0;
  RealWidth := 0;
  RealHeight := 0;
  ms := MemoryStream.Create;
  bwrite := BinaryWriter.Create(ms);
  errors[0] := 'No problems.';
  errors[1] := 'Problem with opening the sourcefile.';
  errors[2] := 'Problem to read the PCX-header.';
  errors[3] := 'Problem with initializing the BinaryReader.';
  errors[4] := 'Not a valid PCX-file for this decoder.';
  errors[5] := 'Problem with reading the palette.';
  errors[6] := 'Any problem with reading from or writing to a stream of the image.';
end;

function PCX_Reader.PCX_ErrorString: string;
begin
  Result := errors[Error];
end;

function PCX_Reader.PCX_ErrorNumber: integer;
begin
  Result := Error;
end;


function PCX_Reader.FromStream(Source: Stream): Stream;
var
  IStream: MemoryStream;
begin
  IStream := MemoryStream.Create;
  PCXStream := StreamReader.Create(Source);
  FromStreamToStream(IStream);
  if (Error = 0) then
  begin
    Result := IStream;
  end
  else
  begin
    Result := nil;
  end;
end;

function PCX_Reader.FromFile(FilePath: string): Stream;
var
  IStream: MemoryStream;
begin
  IStream := MemoryStream.Create;
  PCXStream := StreamReader.Create(FilePath);
  FromStreamToStream(IStream);
  if (Error = 0) then
  begin
    Result := IStream;
  end
  else
  begin
    Result := nil;
  end;
end;

procedure PCX_Reader.FromFileToStream(FilePath: string; IStream: Stream);

begin
  try
    PCXStream := StreamReader.Create(FilePath);
    FromStreamToStream(IStream);
  except
    Error := 1; //Problem with opening the sourcefile
  end;
end;

procedure PCX_Reader.FromStreamToStream(IStream: Stream);
var
  i, j: integer;
begin
  PCX_Reader_Init();
  try
    breader := BinaryReader.Create(PCXStream.BaseStream);
    try
      PCXheader.Manufacturer := breader.ReadByte();
      PCXheader.Version := breader.ReadByte();
      PCXheader.Encoding := breader.ReadByte();
      PCXheader.Bits_per_pixel := breader.ReadByte();
      PCXheader.Xmin := breader.ReadInt16();
      PCXheader.Ymin := breader.ReadInt16();
      PCXheader.Xmax := breader.ReadInt16();
      PCXheader.Ymax := breader.ReadInt16();
      PCXheader.Hdpi := breader.ReadInt16();
      PCXheader.Vdpi := breader.ReadInt16();
      for j := 0 to 15 do
      begin
        for i := 0 to 2 do
        begin
          PCXheader.ColorMap[j, i] := breader.ReadByte;
        end;
      end;
      PCXheader.Reserved := breader.ReadByte();
      PCXheader.Nplanes := breader.ReadByte();
      PCXheader.Bytes_per_line_per_plane := breader.ReadUInt16();
      PCXheader.PaletteInfo := breader.ReadUInt16();
      PCXheader.HscreenSize := breader.ReadInt16();
      PCXheader.VscreenSize := breader.ReadInt16();
      PCXheader.Filler := breader.ReadBytes(55);
    except
      Error := 2; //Problem to read the PCX-header
    end;
  except
    Error := 3; //Problem with initializing the BinaryReader
  end;
  if (Error = 0) then
  begin
    if ((PCXheader.Manufacturer <> 10) or (PCXheader.Encoding <> 1)) then
    begin
      Error := 4; //Not a valid PCX-file
    end;
    if ((PCXheader.Nplanes = 3) and (PCXheader.Bits_per_pixel = 8)) then
    begin
      PictureMode := video.MCGA2;
    end;
    if ((PCXheader.Nplanes = 4) and (PCXheader.Bits_per_pixel = 1)) then
    begin
      PictureMode := video.VGA;
    end;

    if ((PCXheader.Nplanes = 1) and (PCXheader.Bits_per_pixel = 4)) then
    begin
      Error := 4; //Not a valid PCX-file for this class
                 {* Not implemented yet *}
    end;

    if (PCXheader.Nplanes = 1) then
    begin
      if (PCXheader.Bits_per_pixel = 1) then
      begin
        PictureMode := video.VGA;
                    {* b/w PCX saved on Windows (e.g. from Paint Shop Pro)
                       working with VGA-decoding, while true CGA2
                       images may cause problem
                    *}
                    //   Error := 4; //Not a valid PCX-file for this class
      end
      else
      begin
        if (PCXheader.Bits_per_pixel = 2) then
        begin
          PictureMode := video.CGA4;
          Error := 4; //Not a valid PCX-file for this class
        end
        else begin
          if (PCXheader.Bits_per_pixel = 8) then
          begin
            PictureMode := video.MCGA;
            if (PCXheader.Version <> 5) then
            begin
              Error := 4; //Not a valid PCX-file
            end;
          end;
        end;
      end;
    end;
  end;
  if (Error = 0) then
  begin
    bytes_per_line := word(PCXheader.Bytes_per_line_per_plane * PCXheader.Nplanes);
    RealWidth := PCXheader.Xmax - PCXheader.Xmin + 1;
    RealHeight := PCXheader.Ymax - PCXheader.Ymin + 1;
    FillBitmapStructs();
    ReadPalettes(PCXStream.BaseStream);
  end;
  if (breader <> nil) then
  begin
    breader.Close();
  end;
  if (PCXStream <> nil) then
  begin
    PCXStream.Close();
  end;
  if (Error = 0) then
  begin
    (MemoryStream(bwrite.BaseStream)).WriteTo(IStream);
  end
  else
  begin
    IStream.Close;
  end;
end;

procedure PCX_Reader.FillBitmapStructs;
begin
  Fbitmapfileheader.bfType := word($4D42);
  Fbitmapfileheader.bfSize := UInt32((3 * 255) + 14 {*Sizeof(BitmapFileHeader)*}
    + 40 {* Sizeof(TBitmapInfoHeader) *}
    + ((RealHeight) * (RealWidth)));
  Fbitmapfileheader.bfReserved1 := 0;
  Fbitmapfileheader.bfReserved2 := 0;
  Fbitmapfileheader.bfOffBits := (4 * 256) + 14 {*Sizeof(BitmapFileHeader)*}
    + 40 {* Sizeof(TBitmapInfoHeader) *};
  Fbitmapinfoheader.biSize := UInt32(40);
  Fbitmapinfoheader.biWidth := Int32(RealWidth);
  Fbitmapinfoheader.biHeight := Int32(RealHeight);
  Fbitmapinfoheader.biPlanes := word(1); // biPlanes  :=  1; Arcane and rarely used
  Fbitmapinfoheader.biBitCount := word(8); //biBitCount  :=  8; Most widely occurring for PCX format
  Fbitmapinfoheader.biCompression := UInt32(0); // biCompression  :=  BI_RGB; Not needed compressing for the laters
  Fbitmapinfoheader.biSizeImage := UInt32(0); //biSizeImage  :=  0; Valid since we are not compressing the image
  Fbitmapinfoheader.biXPelsPerMeter := Int32(143); //biXPelsPerMeter  :=  143; Rarely used (Windows not use) very arcane field
  Fbitmapinfoheader.biYPelsPerMeter := Int32(143); //biYPelsPerMeter  :=  143; Ditto
  Fbitmapinfoheader.biClrUsed := UInt32(0); //biClrUsed  :=  0; all colors are used
  Fbitmapinfoheader.biClrImportant := UInt32(0); //biClrImportant  :=  0; all colors are important
end;

procedure PCX_Reader.ReadPalettes(IStream: Stream);
begin
  Error := 0;
  if ((PictureMode = video.MCGA) and (PCXheader.Version = 5)) then
  begin
    Read256palette(IStream);
  end;
  if (PictureMode = video.MCGA2) then
  begin
    ReadMCGA2palette(IStream);
  end;
  if (PictureMode = video.VGA) then
  begin
    ReadVGA16palette(IStream);
  end;
end;

procedure PCX_Reader.Read256palette(IStream: Stream); // Read in a 256 color palette at end of PCX file
var
  bytes_in_line, dY, i, j, k: integer;
  count: word;
  Palette256: array[0..255, 0..2] of byte;
  lines: array of array of byte;

begin
  IStream.Seek((IStream.Length) - 769, SeekOrigin(0));
  try
    if (IStream.ReadByte() = 12) then // read indicator byte
    begin
      for j := 0 to 255 do // read palette if there is one
      begin
        for i := 0 to 2 do
        begin
          Palette256[j, i] := byte(IStream.ReadByte());
        end;
      end;
      IStream.Seek(128, SeekOrigin(0)); /// go back to start of PCX data
      Error := 0;
    end
    else
    begin
      Error := 5; // no palette here...
    end;
  except
    Error := 5; //Problem with reading the palette
  end;

  if (Error = 0) then
  begin
    try
      bwrite.Write(Fbitmapfileheader.bfType);
      bwrite.Write(Fbitmapfileheader.bfSize);
      bwrite.Write(Fbitmapfileheader.bfReserved1);
      bwrite.Write(Fbitmapfileheader.bfReserved2);
      bwrite.Write(Fbitmapfileheader.bfOffBits);
      bwrite.Write(Fbitmapinfoheader.biSize);
      bwrite.Write(Fbitmapinfoheader.biWidth);
      bwrite.Write(Fbitmapinfoheader.biHeight);
      bwrite.Write(Fbitmapinfoheader.biPlanes);
      bwrite.Write(Fbitmapinfoheader.biBitCount);
      bwrite.Write(Fbitmapinfoheader.biCompression);
      bwrite.Write(Fbitmapinfoheader.biSizeImage);
      bwrite.Write(Fbitmapinfoheader.biXPelsPerMeter);
      bwrite.Write(Fbitmapinfoheader.biYPelsPerMeter);
      bwrite.Write(Fbitmapinfoheader.biClrUsed);
      bwrite.Write(Fbitmapinfoheader.biClrImportant);
      for i := 0 to 255 do
      begin // R, G, and B must be 0..63
        bwrite.Write(byte(Palette256[i, 2]));
        bwrite.Write(byte(Palette256[i, 1]));
        bwrite.Write(byte(Palette256[i, 0]));
        bwrite.Write(byte(0));
      end;
      Index := 0;
      bytes_in_line := RealWidth;
      dY := 4 - (bytes_in_line - (bytes_in_line div 4) * 4);
      if (dY = 4) then
      begin
        dY := 0;
      end;
      bytes_in_line := bytes_in_line + dY;
      SetLength(lines, RealHeight, bytes_in_line);
      for i := 0 to RealHeight - 1 do
      begin
        if (Index <> 0) then
        begin
          for j := 0 to Index - 1 do
          begin
            lines[i, j] := data; // fills a contiguous block
          end;
        end;
        while (Index < bytes_per_line) do // read 1 line of data (all planes)
        begin
          data := byte(IStream.ReadByte);
          if (byte(data and byte($C0)) = byte($C0)) then
          begin
            count := word(byte(data and byte($3F)));
            data := byte(IStream.ReadByte);
            for j := 0 to count - 1 do
            begin
              lines[i, Index + j] := data; // fills a contiguous block
            end;
            Inc(Index, count);
          end
          else
          begin
            lines[i, Index] := data;
            Inc(Index);
          end;
        end;
        Index := Index - bytes_per_line;
      end;
      for k := RealHeight - 1 downto 0 do
      begin
        for i := 0 to bytes_in_line - 1 do
        begin
          bwrite.Write(byte(lines[k, i]));
        end;
      end;
    except
      Error := 6; // Any problem with reading from or writing to a stream of the image
    end;

  end;
end;

procedure PCX_Reader.ReadMCGA2palette(IStream: Stream); // Read in a 24b color PCX file
var
  bytes_in_line, dY, i, j, k, L, x: integer;
  count: word;
  lines: array of array of byte;
  line: array of byte;
begin
  IStream.Seek(128, SeekOrigin(0)); ///  guaranted go to start of PCX data
  bytes_in_line := 3 * (RealWidth);
  dY := 4 - (bytes_in_line - (bytes_in_line div 4) * 4);

  if (dY = 4) then
  begin
    dY := 0;
  end;
  bytes_in_line := bytes_in_line + dY;

  if (Error = 0) then
  begin
    try
      Fbitmapfileheader.bfSize := UInt32((3 * 15) + 14 {*Sizeof(BitmapFileHeader)*}
        + 40 {* Sizeof(TBitmapInfoHeader) *}
        + ((RealHeight) * (RealWidth) * 3));
      Fbitmapfileheader.bfOffBits := 14 {*Sizeof(BitmapFileHeader)*}
        + 40 {* Sizeof(TBitmapInfoHeader) *};
      Fbitmapinfoheader.biBitCount := word(24);
      bwrite.Write(Fbitmapfileheader.bfType);
      bwrite.Write(Fbitmapfileheader.bfSize);
      bwrite.Write(Fbitmapfileheader.bfReserved1);
      bwrite.Write(Fbitmapfileheader.bfReserved2);
      bwrite.Write(Fbitmapfileheader.bfOffBits);
      bwrite.Write(Fbitmapinfoheader.biSize);
      bwrite.Write(Fbitmapinfoheader.biWidth);
      bwrite.Write(Fbitmapinfoheader.biHeight);
      bwrite.Write(Fbitmapinfoheader.biPlanes);
      bwrite.Write(Fbitmapinfoheader.biBitCount);
      bwrite.Write(Fbitmapinfoheader.biCompression);
      bwrite.Write(Fbitmapinfoheader.biSizeImage);
      bwrite.Write(Fbitmapinfoheader.biXPelsPerMeter);
      bwrite.Write(Fbitmapinfoheader.biYPelsPerMeter);
      bwrite.Write(Fbitmapinfoheader.biClrUsed);
      bwrite.Write(Fbitmapinfoheader.biClrImportant);

      Index := 0;
      SetLength(lines, RealHeight, bytes_in_line * 3);
      SetLength(line, bytes_in_line * 3);
      for i := 0 to RealHeight - 1 do
      begin
        if (Index <> 0) then
        begin
          for j := 0 to Index - 1 do
          begin
            line[j] := data; // fills a contiguous block
          end;
        end;
        while (Index < bytes_per_line) do // read 1 line of data (all planes)
        begin
          data := byte(IStream.ReadByte);
          if (byte(data and byte($C0)) = byte($C0)) then
          begin
            count := word(byte(data and byte($3F)));
            data := byte(IStream.ReadByte);
            for j := 0 to count - 1 do
            begin
              line[Index + j] := data; // fills a contiguous block
            end;
            Inc(Index, count);
          end
          else
          begin
            line[Index] := data;
            Inc(Index);
          end;
        end;
        Index := Index - bytes_per_line;
        x := 0;
        for L := 0 to PCXheader.Bytes_per_line_per_plane - 1 do
        begin
          lines[i, x + 2] := line[L];
          lines[i, x + 1] := line[L + PCXheader.Bytes_per_line_per_plane];
          lines[i, x] := line[L + 2 * PCXheader.Bytes_per_line_per_plane];
          lines[i, x + 3] := 0;
          x := x + 3;
        end;
      end;
      for k := RealHeight - 1 downto 0 do
      begin
        for i := 0 to bytes_in_line - 1 do
        begin
          bwrite.Write(byte(lines[k, i]));
        end;
      end;
    except
      Error := 6; // Any problem with reading from or writing to a stream of the image
    end;

  end;

end;

procedure PCX_Reader.ReadVGA16palette(IStream: Stream); // Read in a 16 color PCX file
var
  bitwise: ByteBitWise;
  c: byte;
  bytes_in_line, dY, i, j, k, L, x, kmax: integer;
  count: word;
  lines: array of array of byte;
  line: array of byte;

begin
  bitwise := ByteBitWise.Create;

  IStream.Seek(128, SeekOrigin(0)); /// guaranted go to start of PCX data
  if (Error = 0) then
  begin
    try
      Fbitmapfileheader.bfSize := UInt32((3 * 15) + 14 {*Sizeof(BitmapFileHeader)*}
        + 40 {* Sizeof(TBitmapInfoHeader) *}
        + ((RealHeight) * (RealWidth)));
      Fbitmapfileheader.bfOffBits := (4 * 16) + 14 {*Sizeof(BitmapFileHeader)*}
        + 40 {* Sizeof(TBitmapInfoHeader) *};
      Fbitmapinfoheader.biBitCount := word(4); //biBitCount  :=  24;
      bwrite.Write(Fbitmapfileheader.bfType);
      bwrite.Write(Fbitmapfileheader.bfSize);
      bwrite.Write(Fbitmapfileheader.bfReserved1);
      bwrite.Write(Fbitmapfileheader.bfReserved2);
      bwrite.Write(Fbitmapfileheader.bfOffBits);
      bwrite.Write(Fbitmapinfoheader.biSize);
      bwrite.Write(Fbitmapinfoheader.biWidth);
      bwrite.Write(Fbitmapinfoheader.biHeight);
      bwrite.Write(Fbitmapinfoheader.biPlanes);
      bwrite.Write(Fbitmapinfoheader.biBitCount);
      bwrite.Write(Fbitmapinfoheader.biCompression);
      bwrite.Write(Fbitmapinfoheader.biSizeImage);
      bwrite.Write(Fbitmapinfoheader.biXPelsPerMeter);
      bwrite.Write(Fbitmapinfoheader.biYPelsPerMeter);
      bwrite.Write(Fbitmapinfoheader.biClrUsed);
      bwrite.Write(Fbitmapinfoheader.biClrImportant);
      for L := 0 to 15 do // R, G, and B must be 0..63
      begin
        bwrite.Write(byte(PCXheader.ColorMap[L, 2]));
        bwrite.Write(byte(PCXheader.ColorMap[L, 1]));
        bwrite.Write(byte(PCXheader.ColorMap[L, 0]));
        bwrite.Write(byte(0));
      end;
      kmax := PCXheader.Ymin + PCXheader.Ymax;
      x := 0;
      Index := 0;
      bytes_in_line := RealWidth div 2;
      dY := 4 - (bytes_in_line - (bytes_in_line div 4) * 4);
      if (dY = 4) then
      begin
        dY := 0;
      end;
      bytes_in_line := bytes_in_line + dY;
      SetLength(lines, RealHeight, bytes_in_line * 2);
      SetLength(line, bytes_in_line * 2);
      for i := 0 to RealHeight - 1 do
      begin
        if (Index = 0) then
        begin
          for j := 0 to Index - 1 do
          begin
            line[j] := data; // fills a contiguous block
          end;
        end;
        while (Index < bytes_per_line) do // read 1 line of data (all planes)
        begin
          data := byte(IStream.ReadByte);
          if (byte(data and byte($C0)) = byte($C0)) then
          begin
            count := word(byte(data and byte($3F)));
            data := byte(IStream.ReadByte);
            for j := 0 to count - 1 do
            begin
              line[Index + j] := data; // fills a contiguous block
            end;
            Inc(Index, count);
          end
          else
          begin
            line[Index] := data;
            Inc(Index);
          end;
        end;
        Index := Index - bytes_per_line;
        for dY := 0 to (bytes_in_line * 2) - 1 do
        begin
          lines[i, dY] := 0;
        end;
        x := 0;
        for dY := 0 to PCXheader.Nplanes - 1 do
        begin
          for j := 0 to PCXheader.Bytes_per_line_per_plane - 1 do
          begin
            c := line[x];
            Inc(x);
            for k := 0 to 7 do
            begin
              if ((byte(c) and bitwise.byteshr(128, byte(k))) > 0) then
              begin
                lines[i, (j * 8) + k] := byte(lines[i, (j * 8) + k] or bitwise.byteshl(1, byte(dY)));
              end;
            end;
          end;
        end;
        for dY := 0 to (bytes_in_line * 2) - 1 do
        begin
          line[dY] := lines[i, dY];
        end;
        dY := (-1);
        x := (-1);
        while (x < (PCXheader.Xmax - PCXheader.Xmin)) do
        begin
          Inc(dY);
          Inc(x);
          lines[i, dY] := byte(bitwise.byteshl(line[x], 4) xor line[x + 1] and $0F);
          Inc(x);
        end;
      end;
      for k := RealHeight - 1 downto 0 do
      begin
        for i := 0 to bytes_in_line - 1 do
        begin
          bwrite.Write(byte(lines[k, i]));
        end;
      end;
    except
      Error := 6; // Any problem with reading from or writing to a stream of the image
    end;
  end;
end;

end.
