TPNGImage help

TPNGImage

Direct access to pixels data


Accessing the pixels data directly is very useful when you need speed while manipulating the png data. For this purpose, the new version provides two properties to do so: Scanline and AlphaScanline.

Before directly accessing the data (using typecasts) you must first verify which pixel format the current png is using. This information is stored in the TChunkIHDR chunk. This is returned using header property from the main TPNGObject.

TChunkIHDR provides the properties BitDepth and ColorType. Using the table typecasting table (described in this page) you may directly access the data.

There are two typecasts you should use, pByteArray (windows.pas) when pixels occupy 8 or less bits and pRGBLine otherwise.

type
  TRGBLine = array[word] of TRGBTriple;
  pRGBLine = ^TRGBLine;

The method bellow returns whenever you should use pRGBLine or pByteArray.

type
  TScanlineTypeReturn = (stByteArray, stRGBLine)
function GetScanlineType(const png: TPngObject):
  TScanlineTypeReturn
begin
  with png.Header do
  begin
    if ColorType in [COLOR_GRAYSCALE,
      COLOR_PALETTE] then
      Result := stByteArray
    else
      Result := stRGBLine
  end
end;

The method bellow returns an arbituary pixel from the png object. Note: It does not performs any bounds checking; Valid values for X are (0, png.Width - 1) and for y (0, png.Height - 1)

function GetRGBLinePixel(const png: TPngObject;
  const
X, Y: Integer): TColor;
begin
  with pRGBLine(png.Scanline[Y])^[X] do
    Result := RGB(rgbtRed, rgbtGreen, rgbtBlue)
end;

And the following sets a pixel value:

procedure SetRGBLinePixel(const png: TPngObject;
 const X, Y: Integer; Value: TColor);
begin
  with pRGBLine(png.Scanline[Y])^[X] do
  begin
    rgbtRed := GetRValue(Value);
    rgbtGreen := GetGValue(Value);
    rgbtBlue := GetBValue(Value)
  end
end;

Adapt the code above so it may fit to your needs.

This method is much more complicated than pRGBLine because, except for bit depths 8, allows more than one pixel for each byte (pixels never cross byte boundaries) requering bit manipulation to access the data. Also, for COLOR_PALETTE it must map the value to a palette entry because it is actually an index rather than the actual value (different from pRGBLine).

{Returns pixel for png using palette and grayscale}
function GetByteArrayPixel(const png: TPngObject;
  const X, Y: Integer): TColor;
var
  ByteData: Byte;
  DataDepth: Byte;
begin
  with png, Header do
  begin
    {Make sure the bitdepth is not greater than 8}
    DataDepth := BitDepth;
    if DataDepth > 8 then DataDepth := 8;
    {Obtains the byte containing this pixel}
    ByteData := pByteArray(png.Scanline[Y])^[X div
      (8 div DataDepth)];
    {Moves the bits we need to the right}
    ByteData := (ByteData shr ((8 - DataDepth) -
      (X mod (8 div DataDepth)) * DataDepth));
    {Discard the unwanted pixels}
    ByteData:= ByteData and ($FF shr (8 - DataDepth));

    {For palette mode map the palette entry and for
      grayscale convert and returns the intensity}
    case ColorType of
      COLOR_PALETTE:
        with TChunkPLTE(png.Chunks.ItemFromClass(TChunkPLTE)).Item[ByteData] do
          Result := rgb(GammaTable[rgbRed], GammaTable[rgbGreen],
            GammaTable[rgbBlue]);
      COLOR_GRAYSCALE:
      begin
        ByteData := GammaTable[ByteData * ((1 shl DataDepth) + 1)];
        Result := rgb(ByteData, ByteData, ByteData);
      end;
    end {case};
  end {with}
end;

And the following sets a pixel value:

{Sets a pixel for grayscale and palette pngs}
procedure SetByteArrayPixel(const png: TPngObject;
  const X, Y: Integer; const Value: TColor);
const
  ClearFlag: Array[1..8] of Integer = (1, 3, 0, 15, 0, 0, 0, $FF);
var
  ByteData: pByte;
  DataDepth: Byte;
  ValEntry: Byte;
begin
  with png.Header do
  begin
    {Map into a palette entry}
    ValEntry := GetNearestPaletteIndex(Png.Palette,
      ColorToRGB(Value));

    {16 bits grayscale extra bits are discarted}
   
DataDepth := BitDepth;
    if DataDepth > 8 then DataDepth := 8;
    {Gets a pointer to the byte we intend to change}
   
ByteData := @pByteArray(png.Scanline[Y])^[X div
      (8 div DataDepth)];
    {Clears the old pixel data}
   
ByteData^ := ByteData^ and not (ClearFlag[DataDepth] shl
      ((8 - DataDepth) - (X mod (8 div DataDepth)) * DataDepth));

    {Setting the new pixel}
   
ByteData^ := ByteData^ or (ValEntry shl ((8 - DataDepth) -
      (X mod (8 div DataDepth)) * DataDepth));
  end {with png.Header}
end;

The methods above are extracted from the Pixels[] property which might be used to set and get pixels. Although this property do all the dirty work, it is probably fast for time critical algorithms. It is recomended to change and adapt the code in order to fast change and access the data.