数学用于慢图像缩放

时间:2012-06-09 22:00:59

标签: delphi delphi-xe

我有一个带漫画书布局的.bmp图片。目前我的代码是这样的。如果我右键单击并按住鼠标按钮,我可以在漫画书页面上的其中一个框架周围绘制一个选框类型框。当我释放按钮时,它将放大到该帧。但它的瞬间。我希望它有动画效果。

因此,而不是将PicRect的值设置为“结束值”

PicRect.Left
PicRect.right
PicRect.top
PicRect.bottom

如下面的代码中所示,我需要一种方法来慢慢到达那里,某种类型的while循环一次设置这些值,直到它达到“最终值”但我不是100%肯定这个数学是如何工作的。也没有任何我的while循环尝试做任何事情,但放大太远。这是程序。

procedure TZImage.MouseUp(Button: TMouseButton; Shift: TShiftState;
                      X, Y: Integer);
    var coef:Double;
    t:integer;
begin
   if FMouse=mNone then Exit;
   if x>ShowRect.Right then x:=ShowRect.Right;
   if y>ShowRect.Bottom then y:=ShowRect.Bottom;
   if FMouse=mZoom then begin  //calculate new PicRect
     t:=startx;
     startx:=Min(startx,x);
     x:=Max(t,x);
     t:=starty;
     starty:=Min(starty,y);
     y:=Max(t,y);
     FMouse:=mNone;
     MouseCapture:=False;
//enable the following if you want to zoom-out by dragging in the opposite direction}
    {     if Startx>x then begin
            DblClick;
            Exit;
         end;}
         if Abs(x-startx)<5 then Exit;
         if (x - startx < y - starty) then
         begin
           while (x - startx < y - starty) do
           begin
              x := x + 100;
              startx := startx - 100;
           end;
         end
         else if (x - startx > y - starty) then
         begin
            while (x - startx > y - starty) do
            begin
                y := y + 100;
                starty := starty - 100;
            end;
         end;


    //This is were it sets the zoom info. This is were
    //I have to change to slowly get the PICRECT.Left/right/top/bottom
         if (PicRect.Right=PicRect.Left)
         then
            coef := 100000
         else
            coef:=ShowRect.Right/(PicRect.Right-PicRect.Left);
         PicRect.Left:=Round(PicRect.Left+startx/coef);
         PicRect.Right:=PicRect.Left+Round((x-startx)/coef);
         if (PicRect.Bottom=PicRect.Top)
         then
            coef := 100000
         else
            coef:=ShowRect.Bottom/(PicRect.Bottom-PicRect.Top);
         PicRect.Top:=Round(PicRect.Top+starty/coef);
         PicRect.Bottom:=PicRect.Top+Round((y-starty)/coef);
       end;
       if FMouse=mDrag then begin
         FMouse:=mNone;
         Canvas.Pen.Mode:=pmCopy;
         Screen.Cursor:=crDefault;
       end;
       Invalidate;
    end;

我相信这可以在上面的代码中完成。但也希望补充一下这有用。

type
    TZImage = class(TGraphicControl)
  private
    FBitmap        : TBitmap;
    PicRect        : TRect;
    ShowRect       : TRect;
    FShowBorder    : boolean;
    FBorderWidth   : integer;
    FForceRepaint  : boolean;
    FMouse         : (mNone, mDrag, mZoom);
    FProportional  : boolean;
    FDblClkEnable  : boolean;
    startx, starty,
    oldx, oldy     : integer;

感谢您帮助我们开展此工作。

4 个答案:

答案 0 :(得分:6)

我有一些建议;我不确定它们是否足以解决您的问题,但我希望它可以帮助您实现目标。

首先,你的while循环正在进行大量有趣的小动作:

if (x - startx < y - starty) then
     begin
       while (x - startx < y - starty) do
       begin
          x := x + 100;
          startx := startx - 100;
       end;
     end
else if (x - startx > y - starty) then
     /* similar code */

请注意x - start == y - starty案例被完全忽略了。我不知道这是否重要。

其次,这可能是在没有循环的情况下重写的。我在这里猜测,它需要一些测试,看看这是否正确,但这感觉就像正确的道路:

foo := (x - startx) - (y - starty)
if (foo > 200 || foo < -200)
    bar = foo / 200  # I assume integer truncation
    x += bar * 100
    startx += bar * 100

我不完全确定你为什么试图让(x-startx) - (y-starty)达到200之内;可能还有更好的东西。

这部分代码有点令人困惑:

if (PicRect.Right=PicRect.Left)
     then
        coef := 100000
     else
        coef:=ShowRect.Right/(PicRect.Right-PicRect.Left);
     PicRect.Left:=Round(PicRect.Left+startx/coef);
     PicRect.Right:=PicRect.Left+Round((x-startx)/coef);
     if (PicRect.Bottom=PicRect.Top)
     then
        coef := 100000
     else
        coef:=ShowRect.Bottom/(PicRect.Bottom-PicRect.Top);
     PicRect.Top:=Round(PicRect.Top+starty/coef);
     PicRect.Bottom:=PicRect.Top+Round((y-starty)/coef);
   end;

coef是否应该从之前被覆盖?或者,您是应该计算coefxcoefy,然后选择(更大?更小?接近100000?)值来为.Left.Right投放, .Top.Bottom计算?我不得不认为,这样的代码,更有可能导致内容的笨拙延伸,从而可能会惹恼用户和作者。

现在,为了解决真正的原因,为什么你在这里,动画缩放 - 你可能需要彻底改变某些东西。我觉得你的while循环可能是为了进行缩放,但是在 coef计算后它们来了,所以我认为它们是用于其他东西的。但是,一旦你弄清楚确切地放置循环的位置来计算从“无缩放”到“最终缩放”范围内的不同coef值,你还需要添加对重绘<的调用/ em>显示 - 或者,根据您的环境,可能需要每50毫秒添加一些定时器触发的回调代码,或者使用更新的coef值进行重新绘制。

答案 1 :(得分:5)

由于两个问题,请勿使用while循环更新缩放级别:

  1. 不同的动画持续时间,因为缩放操作的速度取决于代码的速度,当前的CPU使用率,CPU型号等......
  2. 阻止te GUI,因为即使你使用延迟(例如使用Sleep),代码也会在主线程中运行,而程序却没有响应。
  3. sarnoldElling已经说过:使用计时设备(例如TTimer)在每个间隔执行一次总缩放操作。现在,有两种方法可以计算

    1. 将桥接的总距离除以固定数量的小距离,将计时器的间隔设置为总持续时间除以该数量,并处理每个间隔上所有处理距离的总和。这种方法的缺点是双重的:
      • 计时器的设置间隔是近似值,由于各种原因而不准确,其中一个原因是依赖于Windows消息传递系统,
      • 因此而可能出现粗糙或不平滑的动画。
    2. 每隔一段时间重新计算桥接距离的一部分。这样,无论下一个间隔是否需要两倍,动画总会显得平滑。
    3. 我在this answer中使用了related question中的第二个解决方案,其中包含以下相关摘要:

      procedure TZImage.Animate(Sender: TObject); 
      var 
        Done: Single; 
      begin 
        Done := (GetTickCount - FAnimStartTick) / FAnimDuration; 
        if Done >= 1.0 then 
        begin 
          FAnimTimer.Enabled := False; 
          FAnimRect := FCropRect; 
        end 
        else 
          with FPrevCropRect do 
            FAnimRect := Rect( 
              Left + Round(Done * (FCropRect.Left - Left)), 
              Top + Round(Done * (FCropRect.Top - Top)), 
              Right + Round(Done * (FCropRect.Right - Right)), 
              Bottom + Round(Done * (FCropRect.Bottom - Bottom))); 
        Invalidate; 
      end; 
      
      procedure TZImage.Zoom(const ACropRect: TRect); 
      begin 
        FPrevCropRect := FCropRect; 
        FAnimRect := FPrevCropRect; 
        FCropRect := ACropRect; 
        FAnimStartTick := GetTickCount; 
        FAnimTimer.Enabled := True; 
      end; 
      

      说明:

      • FCropRect是新的缩放矩形,FPrevCropRect是前一个矩形,
      • FAnimRect是两者之间的矩形,具体取决于动画的进度,
      • FAnimStartTick是通过调用Zoom开始缩放操作的时间,
      • 在每个定时器间隔(设置为15毫秒,~67Hz刷新率),Animate被调用,
      • Done是动画进度的百分比,
      • Invalidate触发重绘,将图形绘制到FAnimRect

答案 2 :(得分:3)

我写了一个你上一个问题的答案(一些人,可能是有意义的,stackoverflow主持人永久删除了)。

我之前的回答中的建议是你看看glflow代码示例:

http://code.google.com/p/glflow/

该示例使用GLScene库,并说明了图像加载,图像缩放和动画。

即使您不使用GLScene库,我认为您可以通过查看代码示例获得动画部分的一些灵感。

它的本质是你需要使用计时器来重绘。

首先将开始缩放级别和结束缩放级别之间的距离分成不连续的步骤。然后使用计时器循环执行这些步骤并对每一步重绘。

答案 3 :(得分:0)

我实际上是在这个项目上与Glen合作。我写了一些有问题的代码,所以我想澄清它的作用。我发表了评论,因为我很快把它扔了。这里的大部分代码都是通过我们发现的开放许可证来使用的。代码最初没有循环:

if (x - startx < y - starty) then
       begin
         while (x - startx < y - starty) do
         begin
            x := x + 100;
            startx := startx - 100;
         end;
       end
       else if (x - startx > y - starty) then
       begin
          while (x - startx > y - starty) do
          begin
              y := y + 100;
              starty := starty - 100;
          end;
       end;

这是我添加的内容,因为原始代码没有像我们认为的那样工作,所以它被添加了。基本上你通过拖放选择区域。所选区域会被放大,但不是显示所选的整个区域,而是将x-startx或y-starty中的较小区域调整到视图区域。所以,试着澄清一下,如果你选择了50pix高和100pix宽的区域,它将缩放并适合50pix从上到下填充视图区域,但100像素被剪裁。两侧从观景区掉下来。因此,这里添加的代码是通过使两者中较小的两者中的较小者来解决问题。通过这样做,它实际上将视图适合两者中原来较大的那个,现在两者中较小的一个。这是一个令人难以置信的草率修复,但它的工作原理。您的方法也会为我们解决此问题,并且可能会以更好的方式执行此操作。这一切的最大问题是,如果该区域是一个非常大的区域选择,那么200pix可能实际上不应该修复差异。对于我们的目的,它可能适用于85%+,但不是全部,所以这段代码仍然需要工作。

你质疑的其他代码实际上是让我们疯狂的原因。在整个文档中完全没有评论,我们仍然试图将这一切完全拼凑起来。 coef实际上是让我发疯的原因。我甚至不确定它首先应该做什么。我确实试过了一个单独的coefx和coefy,这实际上打破了它。缩放框与预期的大不相同。据我所知,目前的方法不会增加奇怪的拉伸,为什么我不确定。

如果你想全面看一下,这里是我们正在使用的代码的链接。 http://www.torry.net/authorsmore.php?id=986该页面上的Zimage。

至于手头的实际问题,我们一开始并不清楚coef究竟做了什么,因此对它进行更改会导致我们破坏事物而不是以反复试验的方式工作。如果您不介意看看它,我们就可以确切地知道它能做什么,这样我们就可以将它更改为正确的值,并在此过程中摆脱我的slop代码。然后,这将允许我们前进到缩放动画。

添加有关动画的其他问题。这样做,这也允许我们在从图像上的一个缩放点移动到另一个缩放点时添加动画。对于我们的应用程序,它将从一个漫画小组到另一个,在下方或侧面,并且在大多数情况下也是不同的大小。加载左,右,上,下之间的值是否是显示该类动画的最佳方式?如果是这样,我认为这也适用于从完整图像移动到第一个缩放面板。