【问题标题】:Math for slow image zoom慢速图像缩放的数学
【发布时间】:2012-06-13 11:03:28
【问题描述】:

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

因此,不要将 PicRect 的值设置为“END VALUE”

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;

感谢您为使其正常工作提供的任何帮助。

【问题讨论】:

    标签: delphi delphi-xe


    【解决方案1】:

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

    首先,你的 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 值,您还需要添加对 repaint的调用> 显示 - 或者,根据您的环境,可能需要添加一些由计时器每 50 毫秒触发一次的回调代码,或者使用更新的 coef 值重新绘制。

    【讨论】:

    • 问题是它会拉伸,现在添加的循环会停止拉伸。如果注释掉,x - start == y - starty 的工作方式相同。我会在没有while循环的情况下尝试这个,看看它是如何工作的,谢谢。我不确定 COEF 甚至应该做什么 :( 我认为 while 循环会替换你称之为有点混乱的代码部分。..因为它上面的 while 循环是为了消除拉伸。
    • 接受,因为这帮助我让一切正常工作!
    • 如果您使用 fixed 代码添加答案,那会是 awesome - 其他人可能会尝试制作一些图像缩放动画非常想知道具体需要改变什么。
    • 确实,这里确实需要用到计时器。使用任何类型的循环都会导致 GUI 在执行动画时基本冻结一小段时间,而计时器将确保 GUI 在动画发生时仍会响应。
    【解决方案2】:

    不要使用while循环来更新缩放级别,因为有两个问题:

    1. 不同的动画持续时间,因为缩放操作的速度取决于代码的速度、当前的 CPU 使用率、CPU 型号等...
    2. 阻止 te GUI,因为即使您使用延迟(例如使用 Sleep),代码也会在主线程中运行并且程序会无响应。

    就像sarnoldElling 已经说过的那样:使用计时设备(例如TTimer)在每个间隔上执行总缩放操作的一部分。现在,有两种方法可以计算这些

    1. 将要桥接的总距离划分为固定数量的小距离,将计时器的时间间隔设置为总持续时间除以该数字,并处理每个时间间隔上所有已处理距离的总和。这种方法的缺点是双重的:
      • 计时器的设置间隔是一个近似值,由于各种原因并不准确,其中一个原因是依赖于 Windows 消息传递系统,
      • 因此可能导致动画粗糙或不流畅。
    2. 在每个间隔重新计算要桥接的部分距离。这样一来,无论下一个间隔是否需要两倍以上,动画都会始终显得流畅。

    我将this answer中的第二种解决方案用于您的related question,从中提取了以下相关的sn-ps:

    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

    【讨论】:

    • 这是一个极好的答案。优秀的细节,关于重新计算每个时钟滴答的动画的好建议,以及非常好的示例代码。 (优雅示例代码。)
    • @sarnold 哇,赏金!非常非常慷慨,在此先感谢您。
    • 我时不时会遇到一些如此优雅的东西,它只是需要一些特别的认可。 :)
    【解决方案3】:

    我为您之前的问题写了一个答案(该问题已被某些(可能是好意的)stackoverflow 版主永久删除)。

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

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

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

    即使你不使用 GLScene 库,我想你也可以通过查看代码示例对动画部分有所启发。

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

    首先将开始缩放级别和结束缩放级别之间的距离划分为离散的步长。然后使用计时器循环执行这些步骤并重绘每个步骤。

    【讨论】:

    • +1 用于提及计时器解决方案。但是,我不同意将距离划分为固定数量的计时器间隔,因为这些间隔不会准确。
    • @NGLN 是的,我同意。预先划分距离是一种粗略的方法,在某些情况下可能会导致不准确。我想总体上更好的方法是使用确切的经过时间来计算中间帧的位置。
    【解决方案4】:

    我实际上正在与 Glen 合作开展这个项目。我写了一些有问题的代码,所以我想澄清一下它的作用。我把 cmets 排除在外是因为我很快就把它扔到了一起。这里的大部分代码都是通过我们找到的开放许可证使用的。代码原本没有循环:

    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 填充视图区域,但 100pix 会被剪裁。侧面从观看区域脱落。因此,此处添加的代码是通过使两者中较小的部分成为两者中的较大部分来解决问题。通过这样做,它实际上将适合原来的两者中较大的视图,现在是两者中的较小者。这是一个令人难以置信的草率修复,但它确实有效。您的方法也可以为我们解决这个问题,并且可能会以更好的方式解决。这一切的最大问题是,如果该区域是一个非常大的区域,那么 200pix 实际上可能不足以解决差异。出于我们的目的,它可能适用于 85% 以上,但不是全部,所以这段代码仍然需要工作。

    您质疑的其他代码实际上是之前让我们发疯的原因。整个文档中完全没有 cmets,我们仍在努力拼凑出这一切的确切含义。 coef实际上是让我发疯的原因。我什至不完全确定它首先意味着什么。我确实尝试了一个单独的 coefx 和 coefy ,这实际上打破了它。缩放框与想象的大不相同。据我所知,目前的方法没有增加奇怪的拉伸,至于我不确定的原因。

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

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

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

    【讨论】:

      猜你喜欢
      • 2019-01-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-10
      • 2013-09-16
      相关资源
      最近更新 更多