Scan Line Out of Range Error for Bitmap. TJanDrawImage Component for a Paint-like Program

343 views Asked by At

I am using the free JansDraw Components and when the executable runs, it throws AV error. I could locate the error to specifically the loop block of the code (not the initial block of assignment statements).

procedure TjanDrawImage.colorcircle(var bm:TBitmap;center:tpoint;radius,mode:integer);
var p,p0,p1:pbytearray;
    dx,x,y,w,h,i,j,sum,c:integer;
    cm,tm:tbitmap;
    Rs,Rd:trect;
begin
x:=center.x;
y:=center.y;
w:=bm.width;
h:=bm.height;
cm:=tbitmap.create;
cm.width:=2*radius;
cm.height:=2*radius;
cm.PixelFormat :=FPixelFormat;
tm:=tbitmap.create;
tm.width:=2*radius;
tm.height:=2*radius;
tm.PixelFormat :=FPixelFormat;
tm.canvas.brush.color:=clblack;
tm.canvas.Ellipse (0,0,tm.width-1,tm.height-1);
tm.transparent:=true;
tm.TransparentColor :=clblack;
Rd:=rect(0,0,cm.width,cm.height);
Rs:=rect(x-radius,y-radius,x+radius,y+radius);
cm.canvas.CopyRect (Rd,bm.canvas,RS);

    for j:=0 to cm.height-1 do begin
     p:=cm.scanline[j];
     if j>0 then p0:=cm.scanline[j-1];
     if j<(h-1) then p1:=cm.scanline[j+1];
     for i:=0 to cm.width-1 do begin
      case mode of
      0: //blue
       begin
         p[i*3+1]:=0;
         p[i*3+2]:=0;
       end;
      1: //green
       begin
         p[i*3]:=0;
         p[i*3+2]:=0;
       end;
      2: //red
       begin
         p[i*3]:=0;
         p[i*3+1]:=0;
       end;
      3: //not blue
       begin
         p[i*3]:=0;
       end;
      4: //not green
       begin
         p[i*3+1]:=0;
       end;
      5: //not red
       begin
         p[i*3+2]:=0;
       end;
      6: //half blue
       begin
         p[i*3]:=p[i*3]*9 div 10;
       end;
      7: //half green
       begin
         p[i*3+1]:=p[i*3+1]*9 div 10;
       end;
      8: //half red
       begin
         p[i*3+2]:=p[i*3+2]*9 div 10;
       end;
      9:// darker
       begin
         p[i*3]:=round(p[i*3]*10 /11);
         p[i*3+1]:=round(p[i*3+1]*10 / 11);
         p[i*3+2]:=round(p[i*3+2]*10 /11);
       end;
      10:// lighter
       begin
         p[i*3]:=round(p[i*3]*11 / 10);
         p[i*3+1]:=round(p[i*3+1]*11 / 10);
         p[i*3+2]:=round(p[i*3+2]*11 / 10);
       end;
      11:// gray
       begin
         sum:=round((p[i*3]+p[i*3+1]+p[i*3+2])/ 3);
         p[i*3]:=sum;
         p[i*3+1]:=sum;
         p[i*3+2]:=sum;
       end;
      12:// mix
       begin
         c:=p[i*3];
         p[i*3]:=p[i*3+1];
         p[i*3+1]:=p[i*3+2];
         p[i*3+2]:=c;
       end;
      13://smooth
       begin
       if ((j>0) and (j<(h-1))and (i>0)and (i<(w-1))) then begin
         p[i*3]:=round((p[(i-1)*3]+p[(i+1)*3]+p0[i*3]+p1[i*3]) /4);
         p[i*3+1]:=round((p[(i-1)*3+1]+p[(i+1)*3+1]+p0[i*3+1]+p1[i*3+1]) /4);
         p[i*3+2]:=round((p[(i-1)*3+2]+p[(i+1)*3+2]+p0[i*3+2]+p1[i*3+2]) / 4);
         end;
       end;
      end;
      end;
     end;

cm.canvas.Draw (0,0,tm);
cm.transparent:=true;
cm.transparentcolor:=clwhite;
bm.Canvas.draw(x-radius,y-radius,cm);
cm.free;
tm.free;
end;

A linked question which is helpful is this - implementing scan line of bitmap corectly. It suggests to cast the pointers to NativeInt. The OP changed his code after answers, making it difficult to correlate old code with new code. I understand that my problem is due to some hard coded sequential access of pointers but I am really beginner to make sense of scan line or pointers. If you help me port this, these components will continue to be useful to everyone.

update after comment from @Renate Schaaf: all the brush modes of the janDrawImage are working now, except for the below one. I was expecting a bigger problem but that didn't turn out to be the case. So modified the title of the question. @Renate Schaaf Can you please help fix the below one too. I tried but failed.

procedure TjanDrawImage.rimple(src,dst:tbitmap;amount:extended);
var ca,sa,a,dx,dy,r,rx,ry,sr,fr:extended;
    w,h,x,y,cx,cy,i,j,c,ci:NativeInt;
    p1,p2:pbytearray;
begin
w:=src.width;
h:=src.height;
cx:=w div 2;
cy:=h div 2;
if amount<1 then amount:=1;
fr:=cx/amount;
   for y:=0 to h-1 do begin
     p1:=src.ScanLine[y];
     for x:=0 to w-1 do begin
       dx:=x-cx;dy:=-(y-cx);
       r:=sqrt(sqr(dx)+sqr(dy));
       sr:=fr*sin(r/cx*amount*2*pi);
       if (r+sr<cx) and (r+sr>0) then begin
       a:=arctan2(dy,dx);
       sincos(a,sa,ca);
       i:=cx+round((r+sr)*ca);
       j:=cy+round((r+sr)*sa);
       p2:=dst.scanline[j];
       c:=x*3;ci:=i*3;
         p2[ci]:=p1[c];
         p2[ci+1]:=p1[c+1];
         p2[ci+2]:=p1[c+2];
       end;
      end;
     end;
end;
1

There are 1 answers

1
Renate Schaaf On BEST ANSWER

When you do scanline operations, you always need to make sure that your pixel location is within the boundaries of your bitmap, particularly if you make geometric transformations.

So, in the last example you must clamp j to [0,h-1] and i to [0,w-1] using max(min(..)). Also, you should set the size and pixelformat of src and dst to the same at the beginning.

I didn't really bother to find out what this ripple is supposed to do, but when I run an example it doesn't look like it's doing whatever it does right. For geometric transformations you need to work backwards, running through the pixels of the destination and figure out which pixel of the source needs to go there. Otherwise you end up with a destination that has holes, like here.

Edit: Since I'm stuck with my project: I think this is the routine you really want to use. Note that I just switched the roles of src and dst and corrected some errors. It now adds a water ripple effect to the bitmap.

procedure rimple(src, dst: TBitmap; amount: extended);
var
  ca, sa, a, dx, dy, r, sr, fr: extended;
  w, h, x, y, cx, cy, i, j, c, ci: NativeInt;
  p1, p2: pbytearray;
  bits: integer;
begin
  Assert(src.PixelFormat in [pf24bit, pf32bit],
    'Device independent bitmap needed');
  dst.PixelFormat := src.PixelFormat;
  bits := 3;
  if src.PixelFormat = pf32bit then
    bits := 4;
  w := src.width;
  h := src.height;
  dst.SetSize(w, h);
  cx := w div 2;
  cy := h div 2;
  // in case somebody enters a negative amount
  if abs(amount) < 1 then
    amount := 1;
  fr := cx / amount;
  for y := 0 to h - 1 do
  begin
    // switched src and dst
    p1 := dst.scanline[y]; // src.scanline[y];
    for x := 0 to w - 1 do
    begin
      dx := x - cx;
      // Corrected from dy:=-(y-cx)
      dy := (y - cy);
      r := sqrt(sqr(dx) + sqr(dy));
      sr := fr * sin(r / cx * amount * 2 * pi);
      // Omitted the following check
      // if (r + sr < cx) and (r + sr > 0) then
      begin
        a := arctan2(dy, dx);
        sincos(a, sa, ca);
        i := max(min(cx + round((r + sr) * ca), w - 1), 0);
        j := max(min(cy + round((r + sr) * sa), h - 1), 0);
        // switched src and dst
        p2 := src.scanline[j];
        c := x * bits;
        ci := i * bits;
        p1[c] := p2[ci];
        p1[c + 1] := p2[ci + 1];
        p1[c + 2] := p2[ci + 2];
      end;
    end;
  end;
end;