【问题标题】:How could I fade in/out a TImage?如何淡入/淡出 TImage?
【发布时间】:2014-02-14 22:08:15
【问题描述】:

我有一个简单的TForm,名为Form1; Image1 是一个 TImage 加载了一个 PNGImage 和一个 Button1 TButton 来测试东西。它成功地实现了AlphaBlend Image1 的图片的方法。代码如下:

procedure SetPNGOpacity(Image : TImage; Alpha: Byte);
var
    Bmp: TBitmap;
    BlendFn: TBlendFunction;
    PNG: TPNGImage;
begin
    Png := TPngImage.Create;
    Png.Assign(TPNGImage(Image.Picture.Graphic));
    Bmp := TBitmap.Create;
    Bmp.Assign(Png);
    Image.Picture.Bitmap.PixelFormat := pf32bit;
    Image.Picture.Bitmap.AlphaFormat := afPremultiplied;
    Image.Picture.Bitmap.Canvas.Brush.Color := clBlack;
    Image.Picture.Bitmap.SetSize(Png.Width, Png.Height);
    BlendFn.BlendOp := AC_SRC_OVER;
    BlendFn.BlendFlags := 0;
    BlendFn.SourceConstantAlpha := Alpha;
    BlendFn.AlphaFormat := AC_SRC_ALPHA;
    winapi.windows.AlphaBlend(
        Image.Picture.Bitmap.Canvas.Handle,
        0, 0, Image.Picture.Bitmap.Width,
        Image.Picture.Bitmap.Height,
        Bmp.Canvas.Handle,
        0, 0, Bmp.Width,
        Bmp.Height,
        BlendFn
    );
    Bmp.FreeImage;
    Bmp.Free;
    Png.Free;
end;

如果我简单地在 Button1 onClick 上调用它,图像就会被混合。 无论如何,我的目标是淡入/淡出 Image1;或者换句话说,转到不透明度 0 到 255 和相反的方式。我能看到的是上面的SetPNGOpacity 停止在循环内工作。 我自然地尝试使用以下代码设置应用程序繁忙:

procedure TForm1.Button1Click(Sender: TObject);
var 
    I : integer;
begin
    I := 255;
    while I > 0 do
    begin
        I := I - 1;
        sleep(125);
        SetPNGOpacity(Image2, I);
   //     MessageBeep(0);
    end;
end;

我只是希望在一个非活动窗口等待几秒钟,然后 Image1 应该会完全消失。什么没有发生。所以我尝试了一个简单的线程来淡出,如下所述:

TBar = class(TThread)
private
    I : integer;
public
    procedure execute; override;
    procedure Test;
    constructor Create;
end;

implementation

constructor TBar.Create;
begin
    inherited Create(false);
    I := 255;
end;

procedure TBar.execute;
begin
    while I > 0 do
    begin
        I := I - 1;
        sleep(250);
        synchronize(Test);
     //   MessageBeep(0);
    end;
end;

procedure TBar.Test;
begin
    SetPNGOpacity(Form1.Image2, I);
end;

然后这样称呼它:

procedure TForm1.Button1Click(Sender: TObject);
var 
    Foo : TBar;
begin
    Foo := TBar.Create;
end;

同样,什么也没有发生。所以我再次需要你们。有人对此有想法吗?难道我做错了什么?有谁知道一些有用的阅读;甚至是一段有用的代码?注意:我真的希望它使用TImage 甚至是TBitmap,我可以在TImage 中“提取/存储”它。

提前致谢。

【问题讨论】:

  • 你可以把后面的讨论抛在脑后……你是这里唯一抱怨“正确方式”的人。只是为了澄清,我正在做同一个项目,有同样的限制。我会尽量保持灵活。拜托,别再把我当叛逆者了。但无论如何,你所说的“混合”是什么意思?
  • @David 很少有人称其为“正确”,但我们不要介意这一点。您已经知道为什么很难使用Canvas.Draw 不是吗?我不会丢弃它。我只是问你。关于我所知道的差异。但我试图将它“抽象”(不知道它是否是更好的词)给我的用户。他不需要知道,特别是,这些事情。 @LU 这个例程不适用于PNGImage,是吗?
  • 只加载一次图像。你为什么要一遍又一遍地加载它?当您需要绘制新的不透明度时,请执行此操作。保留原图。在其他地方绘制混合图像。我已经告诉你一个星期了。
  • @Guill,也许不是,但用于淡化效果的技术也可以在这里应用。你有一个图像、一个颜料盒和一个计时器。绘画在paintbox OnPaint 事件中完成,计时器改变状态并调用paintbox.invalidate。

标签: delphi delphi-xe3


【解决方案1】:

为什么您的方法不起作用存在三个主要问题(我没有查看线程部分)。

  1. 您没有给应用程序处理反映图像更改的消息的机会。现在删除的答案中提到了这一点。出于测试目的,您可以在每次迭代中插入一个Application.ProcessMessages 调用。最终,您希望将计时器用于动画目的。根据您的需要,它可能需要比TTimer 具有更高的分辨率。

  2. 您不是每次都从同一个图像进行渲染。 cmets 中提到了这一点,因为它不保留要渲染的原始图像。在第一次迭代之后,您的图像已被更改,当您从中获取图像以连续用作源时,它看起来与之前的源完全不同。

  3. 您不是每次都在同一个目标上混合。第一次你在一个空白的黑色位图上渲染图像。在每次迭代中,您要混合的目标都会更改为其他内容。

以下不是我的建议,而是为了让您的方法发挥作用而需要修改的内容。 IMO 你应该做的最重要的事情是,在任何你喜欢的地方渲染它,但保持你的原始图像不被修改,不是在 TImage 中,而是在它自己的 f.i. 的 TPngImage 中。

procedure SetPNGOpacity(Master: TBitmap; Image : TImage; Alpha: Byte);
begin
    Image.Picture.Bitmap.PixelFormat := pf32bit;
    Image.Picture.Bitmap.AlphaFormat := afPremultiplied;
    Image.Picture.Bitmap.Canvas.Brush.Color := clBlack;
    Image.Picture.Bitmap.SetSize(Master.Width, Master.Height);
    Image.Picture.Bitmap.Canvas.FillRect(Rect(0, 0, Master.Width, Master.Height));
    Image.Picture.Bitmap.Canvas.Draw(0, 0, Master, Alpha); // thanks to TLama for telling that Canvas.Draw has an optional opacity parameter in later Delphi versions
end;

procedure TForm1.Button1Click(Sender: TObject);
var
    Bmp: TBitmap;
    I : integer;
begin
    Bmp := TBitmap.Create;
    Bmp.Assign(TPNGImage(Image2.Picture.Graphic));
    I := 255;
    while I > 0 do
    begin
        I := I - 1;
        SetPNGOpacity(Bmp, Image2, I);
        Application.ProcessMessages;
        Sleep(10);
   //     MessageBeep(0);
    end;
    Bmp.Free;
end;

【讨论】:

  • 要使用 TPNGImage,我需要在我当前的所有代码中替换 TImage。如果我发现有可能我可以摆脱这么多的创建。我会研究一下 TPNGImage 并回来。
  • 你建议我在哪里绘制我所有的 PGNImages?
  • @Guill - 一个颜料盒就可以了。
  • @Sertac, Image.Picture.Bitmap.Canvas.Draw(0, 0, Master, Alpha) 而不是 AlphaBlend ?它更适合那里的其余代码:-) [自毁评论...]
  • @Guill - 我很惊讶答案中的信息/建议和 cmets 不足以解决这个问题。
【解决方案2】:

冒着听起来像是一张破唱片的风险,你这样做是错误的。 TImage 对于静态图像很有用——用它来显示动态图像是错误的。你需要做的是:

  1. 将您的图像加载到TBitmapTPNGImage 或类似TGraphic 的后代中。
  2. 在您的表单中添加TPaintBox
  3. 运行一个以所需刷新率计时的计时器。
  4. 来自计时器调用 Invalidate 或者可能是油漆盒上的 Refresh
  5. 为绘制动态图像的绘制框添加 OnPaint 处理程序。

代码如下所示:

type
  TForm1 = class(TForm)
    PaintBox1: TPaintBox;
    Timer1: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure PaintBox1Paint(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    FBitmap: TBitmap;
    FOpacity: Integer;
  end;

procedure TForm1.FormCreate(Sender: TObject);
var
  Png: TPngImage;
begin
  Png := TPngImage.Create;
  Try
    Png.LoadFromFile('C:\desktop\YoshiMarioParty9.png');
    FBitmap := TBitmap.Create;
    FBitmap.Assign(Png);
  Finally
    Png.Free;
  End;

  BorderIcons := [biSystemMenu, biMinimize];
  BorderStyle := bsSingle;
  PaintBox1.Align := alClient;
  ClientWidth := FBitmap.Width;
  ClientHeight := FBitmap.Height;

  Timer1.Interval := 1000 div 25; // 25Hz refresh rate
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Timer1.Enabled := False;
  FBitmap.Free;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  inc(FOpacity, 5);
  PaintBox1.Invalidate;
end;

procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
  PaintBox1.Canvas.Brush.Color := clWhite;
  PaintBox1.Canvas.Brush.Style := bsSolid;
  PaintBox1.Canvas.FillRect(PaintBox1.ClientRect);
  PaintBox1.Canvas.Draw(0, 0, FBitmap, FOpacity);
end;

这会产生一个合理的结果,但有闪烁。这可以通过将表单的DoubleBuffered 属性设置为True 来消除,但我希望有一个更好的解决方案。

这种解决闪烁的方法是使绘画框成为一个窗口控件。 VCL TPaintBox 是一个非窗口控件,因此在其父窗口上绘制。这确实会导致闪烁。所以,这里有一个从TCustomControl 派生的简单画框控件的版本。这个变体在运行时设置所有内容,因为我没有费心将画框控件注册为设计时控件,尽管这样做非常简单。

program PaintBoxDemo;

uses
  Classes, Graphics, Controls, Forms, ExtCtrls, Diagnostics, pngimage;

type
  TWindowedPaintBox = class(TCustomControl)
  private
    FOnPaint: TNotifyEvent;
  protected
    procedure Paint; override;
  public
    constructor Create(AOwner: TComponent); override;
    property Canvas;
  published
    property Align;
    property Anchors;
    property Color;
    property Constraints;
    property DragCursor;
    property DragKind;
    property DragMode;
    property Enabled;
    property Font;
    property ParentColor;
    property ParentFont;
    property ParentShowHint;
    property PopupMenu;
    property ShowHint;
    property Touch;
    property Visible;
    property OnClick;
    property OnContextPopup;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDock;
    property OnEndDrag;
    property OnGesture;
    property OnMouseActivate;
    property OnMouseDown;
    property OnMouseEnter;
    property OnMouseLeave;
    property OnMouseMove;
    property OnMouseUp;
    property OnPaint: TNotifyEvent read FOnPaint write FOnPaint;
    property OnStartDock;
    property OnStartDrag;
  end;

constructor TWindowedPaintBox.Create(AOwner: TComponent);
begin
  inherited;
  ControlStyle := ControlStyle + [csReplicatable];
  Width := 105;
  Height := 105;
end;

procedure TWindowedPaintBox.Paint;
begin
  Canvas.Font := Font;
  Canvas.Brush.Color := Color;
  if csDesigning in ComponentState then
  begin
    Canvas.Pen.Style := psDash;
    Canvas.Brush.Style := bsClear;
    Canvas.Rectangle(0, 0, Width, Height);
  end;
  if Assigned(FOnPaint) then
    FOnPaint(Self);
end;

var
  Form: TForm;
  PaintBox: TWindowedPaintBox;
  Timer: TTimer;
  Bitmap: TBitmap;
  Stopwatch: TStopwatch;

type
  TEventHandlers = class
    class procedure TimerHandler(Sender: TObject);
    class procedure PaintHandler(Sender: TObject);
  end;

class procedure TEventHandlers.TimerHandler(Sender: TObject);
begin
  PaintBox.Invalidate;
end;

class procedure TEventHandlers.PaintHandler(Sender: TObject);
var
  t: Double;
  Opacity: Integer;
begin
  t := Stopwatch.ElapsedMilliseconds;
  Opacity := Trunc(128.0*(1.0+Sin(t/300.0)));
  PaintBox.Canvas.Brush.Color := clWhite;
  PaintBox.Canvas.Brush.Style := bsSolid;
  PaintBox.Canvas.FillRect(PaintBox.ClientRect);
  PaintBox.Canvas.Draw(0, 0, Bitmap, Opacity);
end;

procedure BuildForm;
var
  Png: TPngImage;
begin
  Png := TPngImage.Create;
  Try
    Png.LoadFromFile('C:\desktop\YoshiMarioParty9.png');
    Bitmap := TBitmap.Create;
    Bitmap.Assign(Png);
  Finally
    Png.Free;
  End;

  PaintBox := TWindowedPaintBox.Create(nil);
  PaintBox.Parent := Form;
  PaintBox.Align := alClient;
  PaintBox.DoubleBuffered := True;
  PaintBox.OnPaint := TEventHandlers.PaintHandler;

  Timer := TTimer.Create(nil);
  Timer.Interval := 1000 div 25; // 25Hz refresh rate
  Timer.Enabled := True;
  Timer.OnTimer := TEventHandlers.TimerHandler;

  Form.Caption := 'PaintBox Demo';
  Form.BorderIcons := [biSystemMenu, biMinimize];
  Form.BorderStyle := bsSingle;
  Form.ClientWidth := Bitmap.Width;
  Form.ClientHeight := Bitmap.Height;
  Form.Position := poScreenCenter;

  Stopwatch := TStopwatch.StartNew;
end;

procedure TidyUp;
begin
  Timer.Free;
  PaintBox.Free;
  Bitmap.Free;
end;

begin
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm, Form);
  BuildForm;
  Application.Run;
  TidyUp;
end.

这是一个包含在单个文件中的 GUI 程序,这显然不是编写生产代码的方式。我在这里这样做只是为了让您可以将代码逐字粘贴到 .dpr 文件中,从而向自己证明这种方法有效。

【讨论】:

  • 您没有实现 OnPaint 事件处理程序。您应该使用与我为油漆盒所做的完全相同的代码。或者使用标准的,以后担心闪烁。
  • 我只想展示图片。即使没有褪色的东西也需要 OnPaint?
  • 你需要在这里重新阅读我的答案stackoverflow.com/questions/21630970。你似乎没有完全理解绘画的事件驱动本质。
  • @Guill - 您将在构造函数中创建和销毁的内容(例如“PNG”),不要将其设为字段。使其成为本地变量。
  • @David 在Opacity := Trunc(128.0*(1.0+Sin(t/300.0))); 行中替换哪个值更好,以使淡入淡出更慢/更快?
猜你喜欢
  • 1970-01-01
  • 2011-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多