Board index » delphi » AV in Graphics Routine

AV in Graphics Routine


2007-04-26 09:14:37 PM
delphi240
Can someone who is more versed in graphics then I look at this and tell
me where I went wrong? I have been getting a large number of reports
on the last line shown
LongColor := PDWORD( PMask)^;
I am clearly not accounting for something in this routine.
Thanks,
Jim
exception class : EAccessViolation
exception message : Access violation at address 004F67CC in module
'UltraExplorer.exe'. Read of address 055E8E80.
**********************************************
procedure ConvertBitmapEx(Image32: TBitmap; var OutImage: TBitmap;
const BackGndColor: TColor);
var
I, N: Integer;
PImage, PTarget, PMask: PByte;
LongColor: DWORD;
SourceRed, SourceGreen, SourceBlue, BkGndRed, BkGndGreen, BkGndBlue,
RedTarget, GreenTarget, BlueTarget, Alpha: Byte;
Target, Mask: TBitmap;
PImageDelta, PBitmapDelta, PMaskDelta: Integer;
begin
// Algorithm only works for bitmaps with a height>1 pixel, should
not be a limitation
// as it would then be a line!
if (Image32.PixelFormat = pf32Bit) and (Image32.Height>1) then
begin
Target := TBitmap.Create;
Mask := TBitmap.Create;
try
Target.Width := Image32.Width;
Target.Height := Image32.Height;
Target.Assign(Image32);
Mask.Width := Image32.Width;
Mask.Height := Image32.Height;
Mask.Canvas.Brush.Color := BackGndColor;
Mask.Canvas.FillRect(Mask.Canvas.ClipRect);
// initialize the bitmaps
PImage := Image32.ScanLine[0];
PTarget := Target.ScanLine[0];
PMask := Mask.ScanLine[0];
// Calculate the number of bytes in a row to do the direct
calculation
// of the pixel index address, note for a "bottom up" storage
this will
// these will be negative numbers
PImageDelta := Integer( Image32.ScanLine[1]) - Integer(PImage);
PBitmapDelta := Integer( Target.ScanLine[1]) - Integer( PTarget);
PMaskDelta := Integer( Mask.ScanLine[1]) - Integer( PMask);
// If the Deltas are postive then it is a "top down" bitmap and
easier
// to calculate then next rows address, just add one
if PImageDelta>0 then
PImageDelta := 1;
if PBitmapDelta>0 then
PBitmapDelta := 1;
if PMaskDelta>0 then
PMaskDelta := 1;
for I := 0 to Image32.Height - 1 do
begin
for N := 0 to Image32.Width -1 do
begin
// Pixel encoded:
// Source GetColorValues ; Profiled = ~24-30% of time
LongColor := PDWORD( PImage)^;
SourceBlue := LongColor and $000000FF;
SourceGreen := (LongColor and $0000FF00) shr 8;
SourceRed := (LongColor and $00FF0000) shr 16;
Alpha := (LongColor and $FF000000) shr 24;
Inc(PImage, 4);
// Mask GetColorValues ; Profiled = ~24-30% of time
LongColor := PDWORD( PMask)^; <<<<<<< AV Right Here.
......
--
www.mustangpeak.net
 
 

Re:AV in Graphics Routine

Jim writes:
Quote

// Mask GetColorValues ; Profiled = ~24-30% of time
LongColor := PDWORD( PMask)^; <<<<<<< AV Right Here.
......


Target := TBitmap.Create;
Mask := TBitmap.Create;
These create bitmaps in device format Bitmap.pixelformat := pfdevice;
If so the bitmap is in the screen pixel depth, which may not be 32 bit
and your pointer's overrunning the bitmap, should use
Target.pixelformat := pf32bit;
Mask.pixelformat := pf32bit;
hth,
Dave
 

Re:AV in Graphics Routine

"Jim" <XXXX@XXXXX.COM>skrev i meddelandet
Quote
// Calculate the number of bytes in a row to do the direct
calculation
// of the pixel index address, note for a "bottom up" storage
this will
// these will be negative numbers
PImageDelta := Integer( Image32.ScanLine[1]) - Integer(PImage);
PBitmapDelta := Integer( Target.ScanLine[1]) - Integer( PTarget);
PMaskDelta := Integer( Mask.ScanLine[1]) - Integer( PMask);

// If the Deltas are postive then it is a "top down" bitmap and
easier
// to calculate then next rows address, just add one
if PImageDelta>0 then
PImageDelta := 1;
if PBitmapDelta>0 then
PBitmapDelta := 1;
if PMaskDelta>0 then
PMaskDelta := 1;
As you don't show the incrementing parts of your loops, this might not be a
problem, but the above code looks very suspicious to me! For a bottom-up
image, you calculate a Delta that is the number of bytes to add to get to
the next scan line, for a top-down image you set the Delta to 1 byte!?
As the Delta is a signed value, calculated as the difference ScanLine[1] -
ScanLine[0] it will work equally well for both kinds of storage.
--
Anders Isaksson, Sweden
BlockCAD: web.telia.com/~u16122508/proglego.htm
Gallery: web.telia.com/~u16122508/gallery/index.htm
 

Re:AV in Graphics Routine

Quote
As you don't show the incrementing parts of your loops, this might
not be a problem, but the above code looks very suspicious to me! For
a bottom-up image, you calculate a Delta that is the number of bytes
to add to get to the next scan line, for a top-down image you set the
Delta to 1 byte!?
Trust me I am not proud of these and to be honest I don't recall all
the details of why I did what. Here is the entire thing.
Jim
procedure ConvertBitmapEx(Image32: TBitmap; var OutImage: TBitmap;
const BackGndColor: TColor);
var
I, N: Integer;
PImage, PTarget, PMask: PByte;
LongColor: DWORD;
SourceRed, SourceGreen, SourceBlue, BkGndRed, BkGndGreen, BkGndBlue,
RedTarget, GreenTarget, BlueTarget, Alpha: Byte;
Target, Mask: TBitmap;
PImageDelta, PBitmapDelta, PMaskDelta: Integer;
begin
// Algorithm only works for bitmaps with a height>1 pixel, should
not be a limitation
// as it would then be a line!
if (Image32.PixelFormat = pf32Bit) and (Image32.Height>1) then
begin
Target := TBitmap.Create;
Mask := TBitmap.Create;
try
Target.Width := Image32.Width;
Target.Height := Image32.Height;
Target.Assign(Image32);
Mask.Width := Image32.Width;
Mask.Height := Image32.Height;
Mask.Canvas.Brush.Color := BackGndColor;
Mask.Canvas.FillRect(Mask.Canvas.ClipRect);
// initialize the bitmaps
PImage := Image32.ScanLine[0];
PTarget := Target.ScanLine[0];
PMask := Mask.ScanLine[0];
// Calculate the number of bytes in a row to do the direct
calculation
// of the pixel index address, note for a "bottom up" storage
this will
// these will be negative numbers
PImageDelta := Integer( Image32.ScanLine[1]) - Integer(PImage);
PBitmapDelta := Integer( Target.ScanLine[1]) - Integer( PTarget);
PMaskDelta := Integer( Mask.ScanLine[1]) - Integer( PMask);
// If the Deltas are postive then it is a "top down" bitmap and
easier
// to calculate then next rows address, just add one
if PImageDelta>0 then
PImageDelta := 1;
if PBitmapDelta>0 then
PBitmapDelta := 1;
if PMaskDelta>0 then
PMaskDelta := 1;
for I := 0 to Image32.Height - 1 do
begin
for N := 0 to Image32.Width -1 do
begin
// Pixel encoded:
// Source GetColorValues ; Profiled = ~24-30% of time
LongColor := PDWORD( PImage)^;
SourceBlue := LongColor and $000000FF;
SourceGreen := (LongColor and $0000FF00) shr 8;
SourceRed := (LongColor and $00FF0000) shr 16;
Alpha := (LongColor and $FF000000) shr 24;
Inc(PImage, 4);
// Mask GetColorValues ; Profiled = ~24-30% of time
LongColor := PDWORD( PMask)^;
BkGndBlue := LongColor and $000000FF;
BkGndGreen := (LongColor and $0000FF00) shr 8;
BkGndRed := (LongColor and $00FF0000) shr 16;
Inc(PMask, 4);
if Alpha < High(Byte) then
begin
// displayColor = sourceColor?alpha / 255 +
backgroundColor?(255 - alpha) / 255
// Profiled = ~15-24% of time
RedTarget := SourceRed*Alpha shr 8 + BkGndRed*(255-Alpha)
shr 8;
GreenTarget := SourceGreen*Alpha shr 8 +
BkGndGreen*(255-Alpha) shr 8;
BlueTarget := SourceBlue*Alpha shr 8 +
BkGndBlue*(255-Alpha) shr 8;
end else
begin
// skip non-blended pixels
RedTarget := SourceRed;
GreenTarget := SourceGreen;
BlueTarget := SourceBlue;
end;
// Create the RGB DWORD color ; Profiled = ~8%-9%% of time
// Mask out all but the alpha channel then build the
backwards stored RGB preserving the alpha channel bits
PDWORD(PTarget)^ := ((BlueTarget) or (GreenTarget shl 8)or
(RedTarget shl 16));
Inc(PTarget, 4);
end;
Inc(PImage, PImageDelta*2);
Inc(PMask, PMaskDelta*2);
Inc(PTarget, PBitmapDelta*2);
end;
OutImage.Assign(Target);
finally
FreeAndNil(Target);
FreeAndNil(Mask);
end;
end else
OutImage.Assign(Image32)
end;
--
www.mustangpeak.net
 

Re:AV in Graphics Routine

Jim wrote [slightly edited]:
Quote
PImageDelta := Integer( Image32.ScanLine[1]) - Integer(PImage);
PBitmapDelta := Integer( Target.ScanLine[1]) - Integer( PTarget);
PMaskDelta := Integer( Mask.ScanLine[1]) - Integer( PMask);
if PImageDelta>0 then
PImageDelta := 1;
if PBitmapDelta>0 then
PBitmapDelta := 1;
if PMaskDelta>0 then
PMaskDelta := 1;
for I := 0 to Image32.Height - 1 do
begin
for N := 0 to Image32.Width -1 do
begin
< work>
Inc(PImage, 4);
Inc(PMask, 4);
Inc(PTarget, 4);
end;
Inc(PImage, PImageDelta*2);
Inc(PMask, PMaskDelta*2);
Inc(PTarget, PBitmapDelta*2);
end;
So, for bottom-up, you do one line pixelwise forward, and then a double line
leap backwards, seems OK.
But for top-down you do one line pixelwise forward, then skip half a pixel
and think you are at the beginning of the next line... I would assume the Deltas
should be zero for top-down, as you have Inc'ed your way through the whole
line and are already standing at the beginning of the next.
For both approaches, you have also made the assumption that the memory
layout doesn't contain any non-pixel filler bytes. I am not sure that is a
safe assumption.
I would have coded it something like (newsreader code, no promises):
LineDelta := ScanLine[1] - ScanLine[0];
if (LineDelta>0) then PixelDelta := SizeOf(TRGBQuad) else PixelDelta
:= -SizeOf(TRGBQuad);
PLine := ScanLine[0];
for I := 0 to Height - 1 do
begin
PPixel := PLine;
for N := 0 to Width - 1 do
begin
< work with PPixel^>
Inc(PPixel, PixelDelta);
end;
Inc(PLine, LineDelta);
end;
Also note that if the pointers are instead declared as pointers to the
actual data type, you don't have to fiddle with SizeOf(TRGBQuad), the value
would have been +-1 instead. But the pointer arithmetic lines would need
some more typecasts.
There's also (I believe) a problem zone where the image begins at an address
< PositiveMaxInt and extends into the negative range of an Integer (or vice
versa). Unsigned integers, or longint, would probably be better.
Another beware for the future: Integer will not be able to contain a Pointer
when Delphi for Win64 finally arrives, AFAIK.
--
Anders Isaksson, Sweden
BlockCAD: web.telia.com/~u16122508/proglego.htm
Gallery: web.telia.com/~u16122508/gallery/index.htm
 

Re:AV in Graphics Routine

Quote
For both approaches, you have also made the assumption that the
memory layout doesn't contain any non-pixel filler bytes. I am not
sure that is a safe assumption.

I would have coded it something like (newsreader code, no promises):

Thanks, much clearer but....
I uploaded a demo to the attachements. If I set the first loop to what
is should be
for I := 0 to Image32.Height - 1 do
it AV when access the last (top row). If I do this:
for I := 0 to Image32.Height - 2 do
Then it works... but it does not run the top line. You can not see the
effect in the demo but in my real app there is a black line across the
top where the algrothim does not touch those pixels.
If you could take a quick look I'd appreciate it.
Jim
--
www.mustangpeak.net
 

Re:AV in Graphics Routine

Jim writes:
Quote
If you could take a quick look I'd appreciate it.
I haven't taken a look yet, but have you given thought to the other thing I
mentioned in the previous post:
Quote
>>
But for top-down you do one line pixelwise forward, then skip half a pixel
and think you are at the beginning of the next line... I would assume the Deltas
should be zero for top-down, as you have Inc'ed your way through the whole
line and are already standing at the beginning of the next.
<<<
If I am understanding your code right, it will Inc the pointers a bit too far
for each line, meaning that it will AV if you try to go through them all (as
it seems you go a bit farther), while it would still 'work' if you skip
enough lines.
Why not test it on a bitmap that is only 1 pixel wide, and with very
distinct color variations for each line?
--
Anders Isaksson, Sweden
BlockCAD: web.telia.com/~u16122508/proglego.htm
Gallery: web.telia.com/~u16122508/gallery/index.htm
 

Re:AV in Graphics Routine

Jim writes:
Quote
If you could take a quick look I'd appreciate it.
OK, now I have taken a look. Jim, we are both a little stupid :-)
The [newsreader] code I wrote places the line pointer at the *beginning* of
each line, irrespective of the direction of Delta.
Of course, if PixelDelta is negative, we should start at the *end* of the
line , or (the easier way) we should always go *forward* inside the line -
PixelDelta is always = SizeOf(TRGBQuad). Save a bit on the if:s too :-)
After changing this, it works as expected (at least in D5).
As a side note, I tried to compile with OVERFLOW and RANGE on, but then I
get exceptions on the pointer arthmetic lines. Sometimes I feel like having
these tests on, as they *can* show me problems with the code. To make that
possible here, you'd have to go through some more type casting, probably up
to a longer integer type which would take more time, so I am not sure which
way I would go...
--
Anders Isaksson, Sweden
BlockCAD: web.telia.com/~u16122508/proglego.htm
Gallery: web.telia.com/~u16122508/gallery/index.htm
 

Re:AV in Graphics Routine

BTW, did you know that by setting
GroupBoxDest.DoubleBuffered := True;
in the FormCreate() event, you will get very smooth, flicker-free drawing of
the blended image? Probably takes a bit more time, but the user experience
is so much better :-)
--
Anders Isaksson, Sweden
BlockCAD: web.telia.com/~u16122508/proglego.htm
Gallery: web.telia.com/~u16122508/gallery/index.htm
 

Re:AV in Graphics Routine

Anders Isaksson writes:
Quote
GroupBoxDest.DoubleBuffered := True;
GroupBoxDest.ControlStyle := GroupBoxDest.ControlStyle + [csOpaque];
seems to give as good results, but takes less time. That assumes that the
control is redrawing the full canvas though (as this example project is
doing).
--
Anders Isaksson, Sweden
BlockCAD: web.telia.com/~u16122508/proglego.htm
Gallery: web.telia.com/~u16122508/gallery/index.htm
 

Re:AV in Graphics Routine

Quote
OK, now I have taken a look. Jim, we are both a little stupid :-)

The [newsreader] code I wrote places the line pointer at the
beginning of each line, irrespective of the direction of Delta.
Uh..... stepped through that at least 50 times and did not even think
about that...... Thanks.
Jim
--
www.mustangpeak.net
 

Re:AV in Graphics Routine

Quote
GroupBoxDest.ControlStyle := GroupBoxDest.ControlStyle + [csOpaque];

seems to give as good results, but takes less time. That assumes that
the control is redrawing the full canvas though (as this example
project is doing).
This example is not being used for anything. Someone wanted me to do
that type of blending for the images in my EasyListview component so
all that is was a test bed they developed. I did a bit of profiling
and got it about 300% faster with that code. All I use out of it is
the graphics routines in my common library.
Thanks for all your help.
Jim
--
www.mustangpeak.net