×

DirectX与VB.NET编程(十七)*图片结构与DirectDraw截图(下)

Kalet Kalet 发表于2009-03-20 12:00:14 浏览297 评论0

抢沙发发表评论

今天是DirectDraw的最后一章了,前几天因为ISAS考试,所以文章发布迟了这么久,向大家道个歉。
这次的内容很偏僻,我也是在网上查了很久才有头绪,经过千辛万苦的总结才出来,这也是更新迟的原因,下面废话不多说,马上开始。


===============华丽的分割线===============

DirectX与VB.NET编程(十七)*图片结构与DirectDraw截图(下)

学习内容:
·在DirectDraw中实现截图


===============华丽的分割线===============


这次的例子,我们使用的原来《全屏动画》的例子上的扩展,文章链接:http://hi.baidu.com/jadechoice/blog/item/910b7617eb1f5c054b90a7fe.html


DirectDraw实现截图的原理是实现将要截图的表面进行LOCK,此时会返回一个GraphicsStream,这个流是存在于DirectX的名称空间的,因此在你未引入DirectX名称空间前你是无法使用到它的。获得这个流后,我们再通过这个流的句柄来创建一个Bitmap对象,最后使用Bitmap对象的Save方法储存图片。由于上一章还没有预料到会如此简单,于是说了那么多相关于BMP的内容,不过了解下也无妨。
首先我们创建一个内存表面,它的目的就是将当前图像保存到内存中,虽然我们可以直接处理主表面,但是速度会慢很多,使用这种方法再保存到硬盘的时候可以大大提高保存速度,代码如下:
'声明内存表面
Dim vMemorySurfaceCaps As New SurfaceCaps
vMemorySurfaceCaps.SystemMemory = True
vMemorySurfaceCaps.OffScreenPlain = True
Dim vMemorySurfaceDesc As New SurfaceDescription(vMemorySurfaceCaps)
vMemorySurfaceDesc.Width = vPrimarySurface.SurfaceDescription.Width
vMemorySurfaceDesc.Height = vPrimarySurface.SurfaceDescription.Height
Dim vMemorySurface As New Surface(vMemorySurfaceDesc, vDev)


然后需要做的是将主表面的内容绘制到内存表面中:
'将主屏内容绘制到内存表面中
vMemorySurface.DrawFast(0, 0, vPrimarySurface, DrawFastFlags.Wait)


下面便是将表面锁定并取出其中的数据了,在我们LOCK表面时,会返回一个LockData类型的对象,我们需要的GraphicsStream便储存在其中,代码如下:
Dim vLockedData As LockedData = vMemorySurface.Lock(LockFlags.Wait)
Dim GS As New GraphicsStream(vLockedData.Data.InternalData, vLockedData.Width * vLockedData.Height * vLockedData.RGBBitCount, True, True)

需要注意的是GraphicsStream的长度需要自己指定,它的长度等于表面宽*表面高*色深。


GraphicsStream已经取到了,我们需要使用它来构造Bitmap的对象了,代码如下:
Dim Img As New Bitmap(vLockedData.Width, vLockedData.Height, vLockedData.Width * 4, Imaging.PixelFormat.Format32bppRgb, GS.InternalData)
这个构造函数的参数中,第一个、第二个分别是Bitmap的宽和高,第三个参数根据描述是像素数据流中的内存大小,根据我的实验,它等于Bitmap宽的4倍,第四个是环境色深,它应该等于你桌面的色深,最后一个就是流的句柄,通过流对象的InternalData方法来获得。


最后就是保存Bitmap内的图像信息,通过Save方法即可,保存路径可随意,格式选为JPEG:
Img.Save("C:\test.jpg", System.Drawing.Imaging.ImageFormat.Jpeg)


最后截图结束时,再将表面解锁就可以了,虽然这个表面即将被垃圾回收,但我们还是养成这个习惯比较好,代码如下:
vMemorySurface.Unlock()


我们设置,在程序运行中,按下F1键代表截图,而且为了防止这段代码出现错误,我们也应将其加入到Try块中,因此,在Form1.KeyDown事件方法中追加如下代码:
If e.KeyCode = Keys.F1 Then
        If e.KeyCode = Keys.F1 Then
                Try
                        '声明内存表面
                        Dim vMemorySurfaceCaps As New SurfaceCaps
                        vMemorySurfaceCaps.SystemMemory = True
                        vMemorySurfaceCaps.OffScreenPlain = True
                        Dim vMemorySurfaceDesc As New SurfaceDescription(vMemorySurfaceCaps)
                        vMemorySurfaceDesc.Width = vPrimarySurface.SurfaceDescription.Width
                        vMemorySurfaceDesc.Height = vPrimarySurface.SurfaceDescription.Height
                        Dim vMemorySurface As New Surface(vMemorySurfaceDesc, vDev)
                        '将主屏内容绘制到内存表面中
                        vMemorySurface.DrawFast(0, 0, vPrimarySurface, DrawFastFlags.Wait)
                        '将表面锁定到流中
                        Dim vLockedData As LockedData = vMemorySurface.Lock(LockFlags.Wait)
                        Dim GS As New GraphicsStream(vLockedData.Data.InternalData, vLockedData.Width * vLockedData.Height * vLockedData.RGBBitCount, True, True)
                        Dim Img As New Bitmap(vLockedData.Width, vLockedData.Height, vLockedData.Width * 4, Imaging.PixelFormat.Format32bppRgb, GS.InternalData)
                        Img.Save("C:\test.jpg", System.Drawing.Imaging.ImageFormat.Jpeg)
                        vMemorySurface.Unlock()
                Catch ex As Exception
                        MsgBox("程序出现异常,被迫退出。")
                        ExitProg(False)
                        Exit Sub
                End Try
        End If
End If


===============华丽的分割线===============


下面是程序全部的代码:
Imports Microsoft.DirectX
Imports Microsoft.DirectX.DirectDraw
Imports System.Threading


Public Class Form1
    Inherits System.Windows.Forms.Form


    Dim vDev As Device
    Dim vPrimarySurface As Surface
    Dim vBackSurface As Surface
    Dim vOffSurface As Surface


    Public Const BALL_COUNT As Integer = 50
    Dim X(BALL_COUNT) As Integer
    Dim Y(BALL_COUNT) As Integer
    Dim SpeedX(BALL_COUNT) As Integer
    Dim SpeedY(BALL_COUNT) As Integer


    Dim Running As Boolean
    Dim DrawThread As Thread


#Region " Windows 窗体设计器生成的代码 "


    Public Sub New()
        MyBase.New()


        '该调用是 Windows 窗体设计器所必需的。
        InitializeComponent()


        '在 InitializeComponent() 调用之后添加任何初始化


    End Sub


    '窗体重写 dispose 以清理组件列表。
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub


    'Windows 窗体设计器所必需的
    Private components As System.ComponentModel.IContainer


    '注意: 以下过程是 Windows 窗体设计器所必需的
    '可以使用 Windows 窗体设计器修改此过程。
    '不要使用代码编辑器修改它。
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        '
        'Form1
        '
        Me.AutoScaleBaseSize = New System.Drawing.Size(6, 14)
        Me.ClientSize = New System.Drawing.Size(216, 128)
        Me.Name = "Form1"
        Me.Text = "动画"


    End Sub


#End Region


    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        '初始化设备
        vDev = New Device(CreateFlags.Default)
        vDev.SetCooperativeLevel(Me, CooperativeLevelFlags.FullscreenExclusive)
        vDev.SetDisplayMode(640, 480, 2 ^ 24, 60, True)


        '初始化主表面
        Dim vPrimarySurfaceCaps As New SurfaceCaps
        vPrimarySurfaceCaps.PrimarySurface = True
        vPrimarySurfaceCaps.VideoMemory = True
        vPrimarySurfaceCaps.Flip = True
        vPrimarySurfaceCaps.Complex = True
        Dim vPrimarySurfaceDesc As New SurfaceDescription(vPrimarySurfaceCaps)
        vPrimarySurfaceDesc.BackBufferCount = 1
        vPrimarySurface = New Surface(vPrimarySurfaceDesc, vDev)


        '初始化后备表面
        Dim vBackSurfaceCaps As New SurfaceCaps
        vBackSurfaceCaps.BackBuffer = True
        vBackSurfaceCaps.VideoMemory = True
        vBackSurface = vPrimarySurface.GetAttachedSurface(vBackSurfaceCaps)


        '初始化离屏表面
        Dim vOffSurfaceCaps As New SurfaceCaps
        vOffSurfaceCaps.OffScreenPlain = True
        vOffSurfaceCaps.VideoMemory = True
        Dim vOffSurfaceDesc As New SurfaceDescription(vOffSurfaceCaps)DirectX与VB.NET编程(十七)*图片结构与DirectDraw截图(下)
        vOffSurface = New Surface("C:\ball.bmp", vOffSurfaceDesc, vDev)
        '设置关键色以去除背景
        Dim vColorKey As New ColorKey
        vColorKey.ColorSpaceHighValue = RGB(255, 0, 255)
        vColorKey.ColorSpaceLowValue = RGB(255, 0, 255)
        vOffSurface.SetColorKey(ColorKeyFlags.SourceDraw, vColorKey)


        '随机初始化各小球位置
        Randomize()
        For i As Integer = 0 To BALL_COUNT
            X(i) = Int(Rnd() * 620)
            Y(i) = Int(Rnd() * 460)
            SpeedX(i) = -5 + Int(Rnd() * 10)
            SpeedY(i) = -5 + Int(Rnd() * 10)
        Next


        '初始化设置线程
        Running = True
        DrawThread = New Thread(AddressOf Draw)
        DrawThread.Start()
    End Sub


    Private Sub Draw()
        While Running
            Try
                '将各小球重复地从离屏表面中绘制到后备表面上
                For i As Integer = 0 To BALL_COUNT
                    vBackSurface.DrawFast(X(i), Y(i), vOffSurface, New Rectangle(0, 0, 20, 20), DrawFastFlags.SourceColorKey)
                Next
                '让主表面实现翻转,从而将后备表面中的图像绘制到主表面上
                vPrimarySurface.Flip(Nothing, FlipFlags.NoVSync)
                '清楚后备表面中的图片
                vBackSurface.ColorFill(Color.Black)
            Catch ex As Exception
                MsgBox("程序出现异常,被迫退出。")
                ExitProg(False)
                Exit Sub
            End Try
            '小球处理位置
            For i As Integer = 0 To BALL_COUNT
                X(i) += SpeedX(i)
                Y(i) += SpeedY(i)
                If X(i) <= 0 Then
                    X(i) = 0
                    SpeedX(i) = -SpeedX(i)
                End If
                If X(i) >= 620 Then
                    X(i) = 620
                    SpeedX(i) = -SpeedX(i)
                End If
                If Y(i) <= 0 Then
                    Y(i) = 0
                    SpeedY(i) = -SpeedY(i)
                End If
                If Y(i) >= 460 Then
                    Y(i) = 460
                    SpeedY(i) = -SpeedY(i)
                End If
            Next
            '线程延迟休息
            Thread.Sleep(50)
        End While
    End Sub


    Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyDown
        '按ESC退出程序
        If e.KeyCode = Keys.Escape Then
            ExitProg(True)
        End If


        If e.KeyCode = Keys.F1 Then
            If e.KeyCode = Keys.F1 Then
                Try
                    '声明内存表面
                    Dim vMemorySurfaceCaps As New SurfaceCaps
                    vMemorySurfaceCaps.SystemMemory = True
                    vMemorySurfaceCaps.OffScreenPlain = True
                    Dim vMemorySurfaceDesc As New SurfaceDescription(vMemorySurfaceCaps)
                    vMemorySurfaceDesc.Width = vPrimarySurface.SurfaceDescription.Width
                    vMemorySurfaceDesc.Height = vPrimarySurface.SurfaceDescription.Height
                    Dim vMemorySurface As New Surface(vMemorySurfaceDesc, vDev)
                    '将主屏内容绘制到内存表面中
                    vMemorySurface.DrawFast(0, 0, vPrimarySurface, DrawFastFlags.Wait)
                    '将表面锁定到流中
                    Dim vLockedData As LockedData = vMemorySurface.Lock(LockFlags.Wait)
                    Dim GS As New GraphicsStream(vLockedData.Data.InternalData, vLockedData.Width * vLockedData.Height * vLockedData.RGBBitCount, True, True)
                    Dim Img As New Bitmap(vLockedData.Width, vLockedData.Height, vLockedData.Width * 4, Imaging.PixelFormat.Format32bppRgb, GS.InternalData)
                    Img.Save("C:\test.jpg", System.Drawing.Imaging.ImageFormat.Jpeg)
                    vMemorySurface.Unlock()
                Catch ex As Exception
                    MsgBox("程序出现异常,被迫退出。")
                    ExitProg(False)
                    Exit Sub
                End Try
            End If
        End If
    End Sub


    Private Sub Form1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
        ExitProg(True)
    End Sub


    Private Sub ExitProg(ByVal Wait As Boolean)
        '结束程序
        Running = False
        '等待线程结束
        If Wait Then
            While DrawThread.ThreadState <> ThreadState.Stopped
            End While
        End If
        '释放内存
        vOffSurface.Dispose()
        vOffSurface = Nothing
        vBackSurface.Dispose()
        vBackSurface = Nothing
        vPrimarySurface.Dispose()
        vPrimarySurface = Nothing
        vDev.Dispose()
        vDev = Nothing
        X = Nothing
        Y = Nothing
        SpeedX = Nothing
        SpeedY = Nothing
        Running = Nothing
        DrawThread = Nothing
        '退出程序
        End
    End Sub
End Class



===============华丽的分割线===============


其实根据BMP图片的构造原理,你一样也可以通过GraphicsStream的内容直接写入到文件,只是需要对颜色进行复杂的处理。截图的效果如下:



DirectX与VB.NET编程(十七)*图片结构与DirectDraw截图(下)
===============华丽的分割线===============


DirectDraw的内容已经全部结束了,我将在以后更新关于DirectPlay的内容~谢谢各位的支持。顺便说明下,我将安装Visual Studio 2005,因此以后的所有文章将会基于Visual Studio 2005。


群贤毕至

访客