| AWARE [SYSTEMS] | Imaging expertise for the Delphi developer | |||||||
![]() |
TechTalks / Unused color | |||||||
|
AWare Systems on-line
Home Languages
Contact
Information: info@awaresystems.be |
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 take it that this TBitmap is pf24bit or pf32bit? Any bitdepth smaller than that cannot cause a significant problem, since you can simply
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. |
|||||||