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 not fast enough for time critical
algorithms. It is recomended to change and adapt the code in order to fast
change and access the pixels data.