位图的扫描线超出范围错误。 Paint-like 程序的 TJanDrawImage 组件

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

我正在使用免费的 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,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;

一个有用的链接问题是 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,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;

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

因此,在最后一个示例中,您必须使用 max(min(..)) 将 j 限制在 [0,h-1] 并将 i 限制在 [0,w-1]。 另外,一开始就把src和dst的size和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, 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;