将多个图像层渲染为透明PNG图像

时间:2014-07-21 17:42:36

标签: vb.net winforms transparency system.drawing alpha-transparency

不完全确定如何说出这个问题,所以我将继续解释细节,并尽力提出问题。

我有一个由以下组件组成的项目

Canvas - 继承PictureBox控件

图层 - "图层"的集合 图层 - 可以包含图形集合作为包含信息的图像。

每个图层都可以移动,图层的选择框被约束到包含最大边界图形的图层部分。

以上所有都在工作!!

什么不起作用,就是当我想保存合并结果(包括透明度和alpha等)时,Canvas控件为空。我知道图像是在框中绘制的,因为在我执行Canvas1.Invalidate()之前它不会显示任何内容。

我对这些类的代码如下:

帆布

    Imports System.Drawing
    Imports System.Drawing.Graphics

    Public Class Canvas
        Inherits PictureBox

        Private _MoveStart As Point
        Private _Layers As List(Of Layer)

        Public Sub New()
            Me.DoubleBuffered = True

            _Layers = New List(Of Layer)
        End Sub

        Public ReadOnly Property Layers() As List(Of Layer)
            Get
                Return _Layers
            End Get
        End Property

        Public Property SelectedLayer As Layer
            Get
                'Loop through all layers and return the one that is selected
                For Each l As Layer In Me.Layers
                    If l.Selected Then Return l
                Next
                Return Nothing
            End Get
            Set(ByVal value As Layer)
                'Loop through all layers and set their Selected property to True if it is the assigned layer ("value") or False if it isn't.
                For Each l As Layer In Me.Layers
                    l.Selected = (l Is value)
                Next
            End Set
        End Property

        Private Function GetLayerFromPoint(ByVal p As Point) As Layer
            ' Finds the layer that contains the point p
            For Each l As Layer In Me.Layers
                If l.Bounds.Contains(p) Then Return l
            Next
            Return Nothing
        End Function

        Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
            MyBase.OnMouseDown(e)

            If e.Button = Windows.Forms.MouseButtons.Left Then
                ' Store the previous selected layer to refresh the image there
                Dim oldSelection = Me.SelectedLayer

                ' Get the new selected layer
                Me.SelectedLayer = Me.GetLayerFromPoint(e.Location)

                'Update the picturebox
                If oldSelection IsNot Nothing Then Me.InvalidateLayer(oldSelection)
                Me.InvalidateLayer(Me.SelectedLayer)
                Me.Update()

                _MoveStart = e.Location
            End If
        End Sub

        Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs)
            MyBase.OnMouseMove(e)

            If Control.MouseButtons = Windows.Forms.MouseButtons.Left Then
                If Me.SelectedLayer IsNot Nothing Then

                    'Store the old bounds for refreshing
                    Dim oldBounds As Rectangle = Me.SelectedLayer.Bounds

                    'Move the selected layer
                    Me.SelectedLayer.Move(e.Location.X - _MoveStart.X, e.Location.Y - _MoveStart.Y)
                    _MoveStart = e.Location

                    'Update the picturebox
                    Me.InvalidateRectangle(oldBounds)
                    Me.InvalidateLayer(Me.SelectedLayer)
                    Me.Update()
                End If
            End If
        End Sub

        Private Sub InvalidateLayer(ByVal l As Layer)
            If l IsNot Nothing Then
                Me.InvalidateRectangle(l.Bounds)
            End If
        End Sub

        Private Sub InvalidateRectangle(ByVal r As Rectangle)
            'Inflate by 1 pixel otherwise the border isnt visible 
            r.Inflate(1, 1)
            Me.Invalidate(r)
        End Sub

        Protected Overrides Sub OnPaint(ByVal pe As System.Windows.Forms.PaintEventArgs)
            MyBase.OnPaint(pe)
            For Each l As Layer In Me.Layers
                l.Draw(pe.Graphics)
            Next
        End Sub

    End Class

    Imports System.Drawing
    Imports System.Drawing.Graphics

    Public Class Layer

        Private _Graphics As List(Of Graphic)
        Private _Name As String
        Private _Selected As Boolean

        Public Sub New(ByVal name As String)
            Me.Name = name
            Me.Selected = False
            Me.Graphics = New List(Of Graphic)
        End Sub

        Public Property Name() As String
            Get
                Return _Name
            End Get
            Set(ByVal value As String)
                _Name = value
            End Set
        End Property

        Public Property Selected() As Boolean
            Get
                Return _Selected
            End Get
            Set(ByVal value As Boolean)
                _Selected = value
            End Set
        End Property

        Public ReadOnly Property Bounds As Rectangle
            Get
                'Combine the bounds of all items
                If Me.Graphics.Count > 0 Then
                    Dim b = Me.Graphics(0).Bounds
                    For i As Integer = 1 To Me.Graphics.Count - 1
                        b = Rectangle.Union(b, Me.Graphics(i).Bounds)
                    Next
                    Return b
                End If
                Return Rectangle.Empty
            End Get
        End Property

        Public Property Graphics() As List(Of Graphic)
            Get
                Return _Graphics
            End Get
            Set(ByVal value As List(Of Graphic))
                _Graphics = value
            End Set
        End Property

        Public Sub Move(ByVal dx As Integer, ByVal dy As Integer)
            'Simply move each item 
            For Each item As Graphic In Me.Graphics
                item.Move(dx, dy)
            Next
        End Sub

        Public Sub Draw(ByVal g As System.Drawing.Graphics)
            'Draw each item
            For Each item As Graphic In Me.Graphics
                item.Draw(g)
            Next

            'Draw a selection border if selected
            If Me.Selected Then
                g.DrawRectangle(Pens.Red, Me.Bounds)
            End If
        End Sub

    End Class

图形

    Public Class Graphic
        Private _Image As Image
        Private _Location As Point
        Private _Size As Size

        Public Sub New(ByVal img As Image)
            Me.Bounds = Rectangle.Empty
            Me.Image = img
        End Sub

        Public Sub New(ByVal img As Image, ByVal location As Point)
            Me.New(img)
            Me.Location = location
            Me.Size = img.Size
        End Sub

        Public Sub New(ByVal img As Image, ByVal location As Point, ByVal size As Size)
            Me.New(img)
            Me.Location = location
            Me.Size = size
        End Sub

        Public Property Location() As Point
            Get
                Return _Location
            End Get
            Set(ByVal value As Point)
                _Location = value
            End Set
        End Property

        Public Property Size() As Size
            Get
                Return _Size
            End Get
            Set(ByVal value As Size)
                _Size = value
            End Set
        End Property

        Public Property Bounds() As Rectangle
            Get
                Return New Rectangle(Me.Location, Me.Size)
            End Get
            Set(ByVal value As Rectangle)
                Me.Location = value.Location
                Me.Size = value.Size
            End Set
        End Property

        Public Property Image() As Image
            Get
                Return _Image
            End Get
            Set(ByVal value As Image)
                _Image = value
            End Set
        End Property

        Public Sub Move(ByVal dx As Integer, ByVal dy As Integer)
            ' We need to store a copy of the Location, change that, and save it back,
            ' because a Point is a structure and thus a value-type!!
            Dim l = Me.Location
            l.Offset(dx, dy)
            Me.Location = l
        End Sub

        Public Sub Draw(ByVal g As Graphics)
            If Me.Image IsNot Nothing Then
                g.DrawImage(Me.Image, Me.Bounds)
            End If
        End Sub

    End Class

使用示例

  • 创建一个新的WinForms项目(Form1)
  • 将Canvas对象添加到表单(将命名为Canvas1)
  • 将Button对象添加到表单(将命名为Button1)
  • 将以下代码粘贴到Form1源视图

Form1中

    Public Class Form1
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            Me.DoubleBuffered = True
            Me.Show()

            Dim l As Layer

            l = New Layer("Layer 1")
            l.Graphics.Add(New Graphic(My.Resources.ActualSizeHS, New Point(10, 10)))
            Canvas1.Layers.Add(l)

            l = New Layer("Layer 2")
            l.Graphics.Add(New Graphic(My.Resources.AlignObjectsRightHS, New Point(320, 240)))
            l.Graphics.Add(New Graphic(My.Resources.AlignToGridHS, New Point(290, 140)))
            l.Graphics.Add(New Graphic(My.Resources.AlignObjectsBottomHS, New Point(320, 130)))
            Canvas1.Layers.Add(l)

            l = New Layer("Layer 3")
            l.Graphics.Add(New Graphic(My.Resources.AlignObjectsTopHS, New Point(520, 240)))
            l.Graphics.Add(New Graphic(My.Resources.AlignTableCellMiddleRightHS, New Point(390, 240)))
            l.Graphics.Add(New Graphic(My.Resources.AlignTableCellMiddleCenterHS, New Point(520, 130)))
            Canvas1.Layers.Add(l)

            Canvas1.Invalidate()
        End Sub

        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Canvas1.Image.Save("MyRenderedPicture.png", System.Drawing.Imaging.ImageFormat.Png)
        End Sub
    End Class

在上面的Form1示例中,将My.Resources。*替换为图形所在的位置。此参数只是一个System.Drawing.Image对象。

我遇到的问题是,当我单击Button1保存图像时,输出不包含添加到控件的任何图形。请注意,我使用的所有图形都是具有完全透明背景的PNG,并且在容器内拖动它们并不具有使用图片框分层图像的块效果。每张图片都是真实透明的。我希望保持这种透明度(以及alpha混合,如果有的话),当我保存文件时 - 但首先......我需要能够保存除了空白图片框以外的其他东西,其中包含图像。

提前致谢。

(保存的图像示例"阴影"未正确渲染其不透明度级别)

enter image description here

现在,如果我执行以下操作:

    Dim x As Integer = 0
    Using bmp As Bitmap = New Bitmap(Me.Width, Me.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb)
        'Me.DrawToBitmap(bmp, New Rectangle(0, 0, bmp.Width, bmp.Height))
        For Each l As Layer In Me.Layers
            For Each g As Graphic In l.Graphics
                g.Image.Save("layer" & x & ".png")
                x = x + 1
            Next
        Next

        bmp.MakeTransparent(Me.BackColor)
        bmp.Save(FileName, Format)
        bmp.Dispose()
    End Using

每个图层都可以单独保存。因此,图形控件正在按原样运行,当我将它们组合起来(并且需要保持位置和透明度)时,我认为这是我正在寻找的例程---

如何合并System.Drawing.Graphics对象我将尝试创建一个新的Graphics对象并尝试"绘制"使用其他图形对象及其位置到它上面。到目前为止,每个例子都使用了剪裁矩形,这些剪辑矩形不会占用图形背后的图片,然后需要清除等等。

2 个答案:

答案 0 :(得分:1)

您没有将图像分配到picbox / canvas,因此Image是Nothing。毕竟,你只是将它用作画布而不是图像持有者。由于帮助者已经知道它们在哪里,你只需要创建一个位图并从下往上绘制图像/图层:

Public Function GetBitmap(format As System.Drawing.Imaging.ImageFormat) As Bitmap

    ' ToDo: add graphics settings
    Dim bmp As New Bitmap(Me.Width, Me.Height)

    Using g As Graphics = Graphics.FromImage(bmp)
        ' ToDo: draw Canvas BG / COlor to bmp to start
        ' for BMP, JPG / non Transparents
        For n As Integer = 0 To Layers.Count - 1
            Layers(n).Draw(g)
        Next

    End Using

    Return bmp

End Function

然后在表格上:

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    ' to do - add a literal SAve
    Using bmp As Bitmap = Canvas1.GetBitmap(Imaging.ImageFormat.Png)

        bmp.Save("C:\Temp\myImage.png", System.Drawing.Imaging.ImageFormat.Png)

    End Using

End Sub

答案 1 :(得分:0)

PNG是否被添加到画布的图像中?这不完全一样,所以我道歉,但我最近做了一个快速测试应用程序,我试图堆叠PNG,所以我想分享我做的。

这循环通过lstImages(实际上是一个字符串列表),将图像加载到bmpTemp中,并将图像绘制到bmpBmp上。然后,该图像将用于PictureBox的Image属性,并添加到窗体的Controls集合中。

我刚刚添加了另一个按钮来测试保存,它可以正常使用下面的内容(在为PictureBox添加名称后)。

Private Sub StackImages()

    Dim bmpBmp As New Bitmap(picStack.Width, picStack.Height)
    Dim graGraphic As Graphics = Graphics.FromImage(bmpBmp)

    For Each i As String In Me.lstImages
        Dim bmpTemp As New Bitmap(i)
        graGraphic.DrawImage(bmpTemp, 0, 0)
    Next

    Dim picTemp As New PictureBox
    picTemp.Top = picStack.Top
    picTemp.Left = picStack.Left
    picTemp.Width = picStack.Width
    picTemp.Height = picStack.Height
    picTemp.Image = bmpBmp
    picTemp.Name = "NewPictureBox"

    Me.Controls.Add(picTemp)
    picTemp.BringToFront()

End Sub

Private Sub btnSave_Click(sender As Object, e As EventArgs) Handles btnSave.Click

    Dim picNew As PictureBox = CType(Me.Controls.Item("NewPictureBox"), PictureBox)
    picNew.Image.Save("c:\temp\picTest.png")

End Sub