位图的扫描线超出范围错误 TJanDrawImage组件,用于类似画图的程序

问题描述

我正在使用免费的JansDraw组件,当可执行文件运行时,它将引发AV错误。我可以将错误定位到代码的特定循环块(而不是赋值语句的初始块)。

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,tm.width-1,tm.height-1);
tm.transparent:=true;
tm.TransparentColor :=clblack;
Rd:=rect(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,tm);
cm.transparent:=true;
cm.transparentcolor:=clwhite;
bm.Canvas.draw(x-radius,cm);
cm.free;
tm.free;
end;

一个有用的链接问题是this - implementing scan line of bitmap corectly。它建议将指针转换为NativeInt。 OP在回答后更改了他的代码,因此很难将旧代码与新代码相关联。我了解我的问题是由于对指针进行了一些硬编码的顺序访问,但是我真的是初学者扫描线或指针。如果您帮助我移植,这些组件将继续对所有人有用。

在@Renate Schaaf发表评论后更新: janDrawImage的所有画笔模式现在都可以使用,但以下一种除外。我原本期待更大的问题,但事实并非如此。因此修改了问题的标题。 @Renate Schaaf您能否也帮助解决以下问题。我尝试过但失败了。

procedure TjanDrawImage.rimple(src,dst:tbitmap;amount:extended);
var ca,sa,a,dx,dy,r,rx,ry,sr,fr:extended;
    w,cx,cy,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,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;

解决方法

进行扫描线操作时,始终需要确保像素位置在位图的边界内,尤其是在进行几何变换时。

因此,在最后一个示例中,必须使用max(min(..))将j钳位到[0,h-1],将i钳位到[0,w-1]。 另外,您应该在开始时将src和dst的大小和pixelformat设置为相同。

我并没有真正去寻找这种涟漪应该做的事情,但是当我运行一个示例时,它看起来并没有做好它所做的一切。对于几何变换,您需要向后工作,遍历目标像素并找出源中的哪些像素需要到达那里。否则,您最终将到达一个有孔的目的地,例如此处。

编辑: 由于我坚持自己的项目:我认为这是您真正想要使用的例程。请注意,我只是切换了src和dst的角色,并更正了一些错误。现在,它会在位图上添加水波纹效果。

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,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;