如何使TFrame(及其上的所有内容)部分透明?

10
我有一个对象,由一个 TFrame 组成,在它上面有一个 TPanel,在其上有一个 TImage。一个位图被分配给 TImage,其中包含钢琴卷轴。这个框架对象被放在一个 TImage 上,其中包含一个包含网格的图像。请参见示例图片。

enter image description here

问题:是否可以使框架部分透明,以便在主窗体上包含网格的背景图像模糊可见?理想情况下,透明度可以由用户设置。位图为32位深,但尝试使用alpha通道没有帮助。面板不是必需的。它用于快速在对象周围绘制边框。我可以在图像上绘制它。
更新1:添加了一个小的代码示例。主单元格绘制具有垂直线条的背景。第二个单元格包含一个TFrame和一个TImage,在其上绘制一条水平线。我想看到的是,垂直线条部分地透过TFrame图像闪耀出来。
更新2:我在原始问题中没有指定的是:TFrame是一个更大应用程序的一部分,并且行为独立。如果TFrame本身可以处理透明度问题,那将有所帮助。
///////////////// Main unit, on mouse click draw lines and plot TFrame
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls,
  Unit2;

type
  TForm1 = class(TForm)
    Image1: TImage;
    procedure Image1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var background: TBitmap;
    f: TFrame2;
    i, c: Int32;
begin
   background := TBitmap.Create;
   background.Height := Image1.Height;
   background.Width  := Image1.Width;
   background.Canvas.Pen.Color := clBlack;

   for i := 0 to 10 do
   begin
      c := i * background.Width div 10;
      background.Canvas.MoveTo (c, 0);
      background.Canvas.LineTo (c, background.Height);
   end;
   Image1.Picture.Assign (background);
   Application.ProcessMessages;

   f := TFrame2.Create (Self);
   f.Parent := Self;
   f.Top    := 10;
   f.Left   := 10;
   f.plot;
end;

end.

///////////////////Unit containing the TFrame
unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
  Dialogs, ExtCtrls;

type
  TFrame2 = class(TFrame)
    Image1: TImage;

    procedure plot;
  end;

implementation

{$R *.dfm}

procedure TFrame2.plot;
var bitmap: TBitmap;
begin
   bitmap := TBitmap.Create;
   bitmap.Height := Image1.Height;
   bitmap.Width  := Image1.Width;
   bitmap.PixelFormat := pf32Bit;
   bitmap.Canvas.MoveTo (0, bitmap.Height div 2);
   bitmap.Canvas.LineTo (bitmap.Width, bitmap.Height div 2);
   Image1.Picture.Assign (bitmap);
end;

end.
更新 3 我原本希望有一些消息或 API 调用可以使控件自己部分透明,就像 WMEraseBkGnd 消息对于完全透明度所做的那样。在他们的解决方案中,Sertac 和 NGLN 都指出使用 AlphaBlend 函数来模拟透明度。该函数合并两个位图,因此需要了解背景图像。现在我的 TFrame 有一个额外的属性:BackGround: TImage,由父控件分配。这就得到了期望的结果(看到它工作起来真是太专业了 :-)

RRUZ 指向 Graphics32 库。我看到它产生了极好的结果,但对我来说学习曲线太陡峭。

感谢大家的帮助!


1
你所寻找的通常是通过使用图层来解决的,尝试使用支持图层的Graphics32库。 - RRUZ
@RRUZ,我已经尝试多次理解Graphics32,但对我来说太难了。我希望有一个适合我理解的解决方案 :-) - Arnold
@Sertac,有两个TBitmaps和两个TImages。一个TImage/TBitmap位于帧上,所以我可以移动这个组合。 - Arnold
发布一些代码,这样可能更容易得到一个好的答案。 - Leonardo Herrera
3个回答

8
隐藏框架并使用Frame.PaintTo。例如,如下所示:
unit Unit1;

interface

uses
  Windows, Classes, Graphics, Controls, Forms, Unit2, JPEG, ExtCtrls;

type
  TForm1 = class(TForm)
    Image1: TImage; //Align = alClient, Visible = False
    Frame21: TFrame2; //Visible = False
    procedure FormPaint(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormResize(Sender: TObject);
  private
    FBlendFunc: TBlendFunction;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TForm1 }

procedure TForm1.FormPaint(Sender: TObject);
var
  Bmp: TBitmap;
begin
  Bmp := TBitmap.Create;
  try
    Bmp.Width := Frame21.Width;
    Bmp.Height := Frame21.Height;
    Frame21.PaintTo(Bmp.Canvas, 0, 0);
    Canvas.StretchDraw(ClientRect, Image1.Picture.Graphic);
    with Frame21 do
      Windows.AlphaBlend(Canvas.Handle, Left, Top, Left + Width, Top + Height,
        Bmp.Canvas.Handle, 0, 0, Width, Height, FBlendFunc);
  finally
    Bmp.Free;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FBlendFunc.BlendOp := AC_SRC_OVER;
  FBlendFunc.BlendFlags := 0;
  FBlendFunc.SourceConstantAlpha := 255 div 2;
  FBlendFunc.AlphaFormat := 0;
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  Invalidate;
end;

end.

框架单元:
unit Unit2;

interface

uses
  Windows, Classes, Controls, Forms, JPEG, ExtCtrls;

type
  TFrame2 = class(TFrame)
    Image1: TImage;  //Align = alClient
    Panel1: TPanel;  //Align = alClient, BevelWidth = 5
  end;

implementation

{$R *.dfm}

end.

结果:

部分透明框架

根据您的具体情况重写上述内容,最好在TPaintBox上绘制,摆脱主窗体上的图像组件。但是当框架的唯一重要元素是图像时,我也会停止使用它,并开始自己绘制所有内容。


很不错,我晚上会试一下。我看到一个缺点是这个解决方案需要TFrame“知道”底层位图。没有办法规避这个问题吗? - Arnold
@Arnold 不,框架不知道表单上的图像。请注意,我的框架组件根本没有代码,绘制工作是在主窗体组件中完成的。 - NGLN

8

这里有另一个解决方案,将背景图像复制到顶部图像中,并在保留黑点不透明度的情况下使用 AlphaBlend 来覆盖位图:

unit1:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Unit2, ExtCtrls, ComCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    Clip_View1: TClip_View;
    TrackBar1: TTrackBar;
    Label1: TLabel;
    procedure TrackBar1Change(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  TrackBar1.Min := 0;
  TrackBar1.Max := 255;
  TrackBar1.Position := 255;
end;

procedure TForm1.TrackBar1Change(Sender: TObject);
begin
  Label1.Caption := IntToStr(TrackBar1.Position);
  Clip_View1.Transparency := TrackBar1.Position;
end;

end.

第二单元:

unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TClip_View = class(TFrame)
    Image1: TImage;
    Panel1: TPanel;
    Image2: TImage;
  protected
    procedure SetTransparency(Value: Byte);
  private
    FTopBmp: TBitmap;
    FTransparency: Byte;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property Transparency: Byte read FTransparency write SetTransparency;
  end;

implementation

{$R *.dfm}

{ TClip_View }

constructor TClip_View.Create(AOwner: TComponent);
begin
  inherited;
  Image1.Left := 0;
  Image1.Top := 0;
  Image1.Picture.LoadFromFile(ExtractFilePath(Application.ExeName) + '..\..\back.bmp');
  Image1.Picture.Bitmap.PixelFormat := pf32bit;
  Image1.Width := Image1.Picture.Bitmap.Width;
  Image1.Height := Image1.Picture.Bitmap.Height;

  FTopBmp := TBitmap.Create;
  FTopBmp.LoadFromFile(ExtractFilePath(Application.ExeName) + '..\..\top.bmp');
  FTopBmp.PixelFormat := pf32bit;
  Image2.SetBounds(1, 1, FTopBmp.Width, FTopBmp.Height);
  Panel1.SetBounds(20, 20, Image2.Width + 2, Image2.Height + 2);
  Image2.Picture.Bitmap.SetSize(Image2.Width, Image2.Height);
  Image2.Picture.Bitmap.Canvas.Draw(0, 0, FTopBmp);
end;

destructor TClip_View.Destroy;
begin
  FTopBmp.Free;
  inherited;
end;

procedure TClip_View.SetTransparency(Value: Byte);
var
  Bmp: TBitmap;
  R: TRect;
  X, Y: Integer;
  Pixel: PRGBQuad;
  BlendFunction: TBlendFunction;
begin
  if Value <> FTransparency then begin
    FTransparency := Value;
    R := Image2.BoundsRect;
    OffsetRect(R, Panel1.Left, + Panel1.Top);
    Image2.Picture.Bitmap.Canvas.CopyRect(Image2.ClientRect,
                                          Image1.Picture.Bitmap.Canvas, R);

    Bmp := TBitmap.Create;
    Bmp.SetSize(FTopBmp.Width, FTopBmp.Height);
    Bmp.PixelFormat := pf32bit;
    Bmp.Assign(FTopBmp);
    try
      for Y := 0 to Bmp.Height - 1 do begin
        Pixel := Bmp.ScanLine[Y];
        for X := 0 to Bmp.Width - 1 do begin
          if (Pixel.rgbBlue <> 0) and (Pixel.rgbGreen <> 0) and
              (Pixel.rgbRed <> 0) then begin
            Pixel.rgbBlue := MulDiv(Pixel.rgbBlue, Value, $FF);
            Pixel.rgbGreen := MulDiv(Pixel.rgbGreen, Value, $FF);
            Pixel.rgbRed := MulDiv(Pixel.rgbRed, Value, $FF);
            Pixel.rgbReserved := Value;
          end else                      // don't touch black pixels
            Pixel.rgbReserved := $FF;
          Inc(Pixel);
        end;
      end;

      BlendFunction.BlendOp := AC_SRC_OVER;
      BlendFunction.BlendFlags := 0;
      BlendFunction.SourceConstantAlpha := 255;
      BlendFunction.AlphaFormat := AC_SRC_ALPHA;
      AlphaBlend(Image2.Picture.Bitmap.Canvas.Handle,
          0, 0, Image2.Picture.Bitmap.Width, Image2.Picture.Bitmap.Height,
          Bmp.Canvas.Handle, 0, 0, Bmp.Width, Bmp.Height,
          BlendFunction);
    finally
      Bmp.Free;
    end;
  end;
end;

end.


启动时:
enter image description here
应用透明度:
enter image description here


这正是我想要的!但是在这里,与NGLN的解决方案一样:AlphaBlend函数似乎必须同时了解两个位图,而在我的应用程序中,TFrame不知道它的周围环境。 - Arnold
1
@Arnold - 上述代码并未使用背景位图,而是从背景TImage中复制。但顶部位图不是这样的,您需要有一个原始副本才能将不同程度的透明度应用于AlphaBlend。我想不出其他实现您要求的方式,除了使用AlphaBlend。 - Sertac Akyuz
如果源位图是32位的,TCanvas.Draw(该位图)将使用AlphaBlend,因此您可能只需通过设置位图的属性而无需使用BlendFunc或API调用等来使用Draw。 - Rudy Velthuis
@Rudy - 我需要设置哪些位图属性? - Arnold
1
@Arnold - 首先,我不知道你是否正确使用了 OffsetRect,这将取决于你是否去掉了面板,背景图片是否在表单上或框架上,这我不清楚。其次,你不需要 Frame_Image.Picture.Bitmap.Canvas.Draw(0, 0, FTopBmp);,它是 AlphaBlend 将绘制前景图像。第三,在你的更新中并不明显你是否有一个正确大小的位图用于前景图像,你可能需要在 CopyRect 之前使用 Frame_Image.Picture.Bitmap.SetSize(FTopBmp.Width, FTopBmp.Height); - Sertac Akyuz
显示剩余2条评论

2

我建议使用 TPaintBox。在它的 OnPaint 事件中,先绘制网格,然后在上面透明混合你的滚动图片即可。不需要使用任何 TImageTPanelTFrame 组件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接