AWARE [SYSTEMS] Imaging expertise for the Delphi developer
AWare Systems, Imaging expertise for the Delphi developer, Home TechTalks / Unused color
AWare Systems on-line

Home
Custom development
Imaging
TechTalks
    Format from data
    Tiling versus banding
    VCL TGraphic basics
    Unused color
Company
Links

Languages

  English version
  Nederlandse versie

Contact

Information: info@awaresystems.be
Helpdesk: support@awaresystems.be
Sales: sales@awaresystems.be



Valid HTML 4.01!



How to calculate an unused color from a Delphi VCL TBitmap

> I'm after a way of finding a unused colour that is available in a TBitmap.
> I want to use this unused colour to encode transparency.

I take it that this TBitmap is pf24bit or pf32bit? Any bitdepth smaller than that cannot cause a significant problem, since you can simply

  • setup a bit array, one bit for each possible color value
  • clear the bit array
  • scan through the bitmap
    • for each pixel color value, set the appropriate bit in the bit array
  • scan through the bit array, until you find an unset bit
  • return the index of this unset bit as unused color value.

For example, in a pf16bit TBitmap, this would look like...

function Bitmap16UnusedIndex(const a: TBitmap): Integer;

var

BitArray: Pointer;

ma: PByte;

mb: Integer;

mc: Integer;

md: PWord;

na: Integer;

nb: Integer;

nba: PByte;

nbb: Integer;

oaa: PByte;

oab: Integer;

ob, oc: Integer;

begin

{$IFDEF DEBUG}

if a.PixelFormat <> pf16bit then

raise Exception.Create('Intented only for 16bit bitmaps');

{$ENDIF}

GetMem(BitArray, 8192);

ZeroMemory(BitArray, 8192);

{m is used to scan through the bitmap scanlines}

ma := a.Scanline[0];

mb := Integer(a.Scanline[1])-Integer(a.Scanline[0]);

for mc := 0 to a.Height-1 do

begin

md := PWord(ma);

for na := 0 to a.Width-1 do

begin

nb := md^;

Inc(md);

{splitting up the 16bit value into a bit array index consisting of byte pointer and bit mask}

nba := PByte(Cardinal(BitArray)+Cardinal(nb shr 3));

nbb := (128 shr (nb and 7));

{and making sure appropriate bit is set}

nba^ := (nba^ or nbb);

end;

Inc(ma, mb);

end;

{oa is used to scan through the bit array}

oaa := BitArray;

for oab := 0 to 8191 do

begin

if oaa^ <> 255 then

begin

ob := 0;

oc := 128;

while True do

begin

if (oaa^ and oc) = 0 then break;

Inc(ob);

oc := (oc shr 1);

end;

{making complete 16bit value}

Result := ((oab shl 3) or ob);

FreeMem(BitArray, 8192);

exit;

end;

Inc(oaa);

end;

FreeMem(BitArray, 8192);

Result := -1;

end;

This function returns either an unused 16bit color value, or -1 if all colors are used.

Note that it's still up to you to interpret the 16bit color value, whatever way you need to interpret it, the function simply returns it as it is stored in a pf16bit bitmap. Also note that we don't abuse the stack by declaring an 8Kb bit array in the var section, but separately allocate this, since it is not good practice to use the stack for blocks of this magnitude. Note also that we don't use nonsense like TBitArray and such, since we do not want our users to grow a beard waiting for this function to complete...

The problem grows more complex when dealing with pf24bit or pf32bit TBitmap's. Using exactly the same algorithm design to get at an unused color would involve a bit array of 2^24 bits, being 2 meg. 8 kilobyte fits into every user's processor cache, nowadays, but 2 meg does not. Randomly accessing 2 meg for each pixel in a TBitmap is extremely bad practice, at best.

The easiest solution is to go hunting for an unused combination of R and G values, given some fixed B value. If the algorithm finds one, then these R and G values, together with the fixed B value, is an unused color, for sure. If it does not, then the same algorithm can be repeated, searching for an unused combination of R and G values, given the next B value. Thus, this solution still needs a bit array of only 2^16 bits, at the expense of potentially scanning through the TBitmap multiple times. This multiple scanning is not half as bad as maintaining a 2^24 bits array, and is furthermore not very likely to occur. Most of the time, the first pass will already yield an unused color.

Here's some code implementing the proposed solution, for pf24bit bitmaps.

function Bitmap24UnusedColor(const a: TBitmap): TColor;

var

BitArray: Pointer;

p: Integer;

ma: PByte;

mb: Integer;

mc: Integer;

md: PByte;

na: Integer;

nb: Integer;

nba: PByte;

nbb: Integer;

oaa: PByte;

oab: Integer;

ob, oc: Integer;

o: Integer;

begin

{$IFDEF DEBUG}

if a.PixelFormat <> pf24bit then

raise Exception.Create('Intented only for 24bit bitmaps');

{$ENDIF}

GetMem(BitArray, 8192);

{p is the 'given' B value}

for p := 0 to 255 do

begin

ZeroMemory(BitArray, 8192);

{m is used to scan through the bitmap scanlines}

ma := a.Scanline[0];

mb := Integer(a.Scanline[1])-Integer(a.Scanline[0]);

for mc := 0 to a.Height-1 do

begin

md := ma;

{n is used to scan through the scanline's pixels}

for na := 0 to a.Width-1 do

begin

if md^ = p then

begin

Inc(md);

nb := (md^ shl 8);

Inc(md);

nb := (nb or md^);

Inc(md);

{splitting up the 16bit value into a bit array index consisting of byte pointer and bit mask}

nba := PByte(Cardinal(BitArray)+Cardinal(nb shr 3));

nbb := (128 shr (nb and 7));

{and making sure appropriate bit is set}

nba^ := (nba^ or nbb);

end

else

Inc(md, 3);

end;

Inc(ma, mb);

end;

oaa := BitArray;

for oab := 0 to 8191 do

begin

if oaa^ <> 255 then

begin

ob := 0;

oc := 128;

while True do

begin

if (oaa^ and oc) = 0 then break;

Inc(ob);

oc := (oc shr 1);

end;

{making complete 16bit value}

o := ((oab shl 3) or ob);

{that's it}

FreeMem(BitArray, 8192);

Result := RGB((o and 255), (o shr 8), p);

exit;

end;

Inc(oaa);

end;

end;

FreeMem(BitArray, 8192);

Result := TColor(-1);

end;

This function returns either an unused TColor, or TColor(-1) if all colors are used.

Please drop us a line drop us a line if you wish to be kept informed about changes and additions to this page.