Learn VB .NET Through Game Programming [Electronic resources]

Matthew Tagliaferri

نسخه متنی -صفحه : 106/ 78
نمايش فراداده

Using the BMPStitch Code

The BMPStitch utility contains a few code fragments worthy of discussion and study, especially for someone uninitiated with the .NET Framework. Some of the functionality may serve as a learning tool into this vast library.

Creating the ClipPictureBox Class

The most interesting part of the BMPStitch code is a class I developed for displaying the image and the clipping lines. This class is ClipPictureBox, and Listing C-1 shows the interface of the class.

Listing C.1: Public (and Protected) Interface of the ClipPictureBox Class

Public Class ClipPictureBox 
Inherits PictureBox 
Property ClipColor() As Color 
Property ClipDisabled() As Boolean 
Public Event ClippingRectChanged(ByVal r As Rectangle) 
ReadOnly Property ClippingRect() As Rectangle 
Shadows Property Image() As Image 
Protected Overrides Sub OnPaint(ByVal pe As _ 
System.Windows.Forms.PaintEventArgs) 
Protected Overrides Sub OnMouseDown(ByVal e As _ 
System.Windows.Forms.MouseEventArgs) 
Protected Overrides Sub OnMouseMove(ByVal e As _ 
System.Windows.Forms.MouseEventArgs) 
Protected Overrides Sub OnMouseUp(ByVal e As _ 
System.Windows.Forms.MouseEventArgs) 
End Class 

The ClipPictureBox class is an ancestor of the standard .NET Framework PictureBox class, meaning of course that it receives all of that ancestor’s functionality “for free.” This class extends the ancestor class by supporting the drawing and manipulation of the clipping lines. To that end, the OnPaint method and the three mouse-based methods OnMouseDown, OnMouseMove, and OnMouseUp are all overridden.

Another case of extended functionality occurs in the Image property, which is aproperty found in the base class. You can extend the functionality of the property so that you can initialize the location of the four clipping lines. You do this by using the Shadows keyword on the property, which indicates that this property replaces the functionality found in the base class. Listing C-2 shows the code for the Image property, as well as the private variables used to store the location of the four clipping lines.

Listing C.2: Shadowed Image Property on the ClipPictureBox

Private FClipInitialized As Boolean = False 
Private FClipTop As Integer 
Private FClipBottom As Integer 
Private FClipLeft As Integer 
Private FClipRight As Integer 
Shadows Property Image() As Image 
Get 
Return MyBase.Image 
End Get 
Set(ByVal Value As Image) 
MyBase.Image = Value 
If Not FClipInitialized Then 
FClipTop = Height \ 4 
FClipBottom = (Height \ 4) * 3 
FClipLeft = Width \ 4 
FClipRight = (Width \ 4) * 3 
FClipInitialized = True 
End If 
End Set 
End Property 

What’s interesting here is that you’ve specified to replace the Image property by using the Shadows keyword, but then you go ahead and use the base Image property to store the image. In effect, you’re declaring that you want to replace the base property, but then you go ahead and use the property for storage anyway. You then add some functionality to the Set portion of the property. This new functionality initialized the four clipping line variables (only if they haven’t already been set; the variable FClipInitialized acts as a flag to make sure the initialization happens only once).

This technique of shadowing a property and then using it anyway is a great way to “cheat” on a member that isn’t declared Overrideable. The standard way of replacing or extended functionality on a class member is to override it, but you can’t do this unless the base class explicitly declares the class Overrideable. You can shadow any member, though.

File Handling

The code within the utility’s form has some base file handling functionality that’s important. The first example of this functionality, shown in Listing C-3, occurs when the user clicks the Open button.

Listing C.3: Getting a File from the User, and Setting Up the ClipPictureBox Control

Public Class fBMPStitch 
Inherits System.Windows.Forms.Form 
Private FBaseFile As String 
Dim FFrames As Integer 
Dim pb As ClipPictureBox 
Private Sub cbFile_Click(ByVal sender As System.Object, _ 
ByVal e As System.EventArgs) Handles cbFile.Click 
If oDialog.ShowDialog Then 
FBaseFile = oDialog.FileName 
If pb Is Nothing Then 
pb = New ClipPictureBox 
pb.SizeMode = PictureBoxSizeMode.AutoSize 
pb.Location = New Point(10, 10) 
pb.Visible = True 
AddHandler pb.ClippingRectChanged, _ 
AddressOf pb_ClippingRectChanged 
Me.Controls.Add(pb) 
End If 
pb.Image = Image.FromFile(FBaseFile) 
Call CountFiles() 
End If 
End Sub 
Private Sub CountFiles() 
Dim f As New FileInfo(FBaseFile) 
Dim d As New DirectoryInfo(f.DirectoryName) 
Dim cExt As String = f.Extension 
FFrames = 0 
For Each f In d.GetFiles("*" & cExt) 
FFrames += 1 
Next 
lbFrames.Text = FFrames & " frames" 
nudAcross.Value = Math.Sqrt(FFrames) 
nudDown.Value = nudAcross.Value 
End Sub 

The variable oDialog is an instance of the .NET Framework class named OpenFileDialog. As shown in Listing C-3, the method ShowDialog opens the dialog box that requests a file from the user and returns True if the user does indeed select a file (the method returns False if the end user selects Cancel).

If the user selects a file, the code initializes a ClipPictureBox variable and adds it to the form. You could’ve done this by putting the ClipPictureBox into its own project and adding it to the Visual Studio toolbox, but this method does it at runtime instead. Once the ClipPictureBox is added to the form, it’s loaded with the image from the selected file.

The routine CountFiles is called next. This routine uses some .NET Framework file and directory handling classes to count the number of files in the folder of the user-selected file. The first class, named FileInfo, encapsulates the functionality of any on-disk file. In this code, the class is initialized with the file selected by the user. The next line initializes a class named DirectoryInfo using the directory of the user-selected file as the constructor’s parameter. What this gives you is a representation of the folder in which the user-selected file lies. One of the things the DirectoryInfo class lets you do is loop through all the files in the represented directory, and this is exactly what the remainder of the code does. The purpose of this loop is to count the number of files in the folder that have the same extension as the originally selected file. Once the number of files is known, it’s stored in the variable FFrames, and the across/down controls are initialized to numbers that are the square root of this value. This works great when there are 36 frames because there would be six frames across and six down, but it doesn’t quite work with, say, 24 frames. It does provide a reasonable starting value for the across/down controls, though.

Creating the Bitmap Building Code

Listing C-4 shows the routine that takes the individual bitmap files, smashes them into a large single bitmap, and then saves the bitmap to disk.

Listing C.4: The “Meat” of the Bitmap Stitcher: The Stitching Code

Private Sub cbBuild_Click(ByVal sender As System.Object, _ 
ByVal e As System.EventArgs) Handles cbBuild.Click 
Dim b As Bitmap 
Dim f As New FileInfo(FBaseFile) 
Dim d As New DirectoryInfo(f.DirectoryName) 
Dim x, y As Integer 
Dim h, w As Integer 
Dim iA, iD As Integer 
Dim cExt As String 
iA = nudAcross.Value 
iD = nudDown.Value 
If iA * iD <> FFrames Then 
MsgBox("across * down must = frames", _ 
MsgBoxStyle.Critical + MsgBoxStyle.OKOnly, "Error") 
Else 
w = pb.ClippingRect.Width 
h = pb.ClippingRect.Height 
b = New Bitmap(w * iA, h * iD, Graphics.FromImage(pb.Image)) 
Dim g As Graphics 
g = Graphics.FromImage(b) 
Try 
x = 0 
y = 0 
cExt = f.Extension 
For Each f In d.GetFiles("*" & cExt) 
pb.Image = Image.FromFile(f.FullName) 
pb.Refresh() 
Thread.CurrentThread.Sleep(100) 
g.DrawImageUnscaled(pb.Image, x, y) 
x += w 
If x >= w * iA Then 
x = 0 
y += h 
End If 
Next 
Finally 
g.Dispose() 
End Try 
b.Save("c:\BMPStitch.bmp", System.Drawing.Imaging.ImageFormat.Bmp) 
lbClip.Text = "c:\BMPStitch.bmp saved" 
End If 
End Sub 

The first thing this routine does is check that the across/down control values, when multiplied together, actually add up to the number of frames found in the CountFrames routine discussed previously. If not, then the routine displays an error to the end user and exits.

If the math all works out, though, the bitmap stitching can begin. First, abitmap simply named b is created that has the width and height needed to store the frame bitmaps. The program then loops through all the image files in the folder again, just as in the routine CountFrames. This time, though, each file is loaded into the ClipPictureBox variable and then copied into the appropriate spot on the final bitmap. Finally, the bitmap is saved to the hard-coded filename c:\BMPStitch.bmp.