C# Programming - GDI+
In a windows program, the output to an I/O device such as the display or printer requires going through an intermediate object known as the device context (DC). The purpose of DC is to make the windows program device independent so that the program will work with any type of display or printer without requiring a recompilation. The device context has two portions to it as shown below:
Windows Program
Output Device (e.g., display or printer)
The context portion of the DC maintains information about the drawing context such as line width, style, color, fill color, font style, font size etc.. The device drive portion of DC contains the actual device driver for a particular output device.
In MFC, there is a detailed set of classes that deal with the DC. The general framework that deals with the DC is referred to as the GDI (Graphical Device Interface). In .Net, the GDI interface has been greatly refined and is referred to as GDI+. The .Net GDI+ classes encapsulate some of the important Windows API to create graphical outputs. The set of GDI+ classes is quite comprehensive such that entire books can be written on it. We will try to cover some of the important concepts and the popular GDI+ classes needed in creating windows applications.
The System.Drawing namespace contains most of the classes, structs, enums, and delegates that provide the basic drawing functionality. The System.Darwing contains other namespaces for specialized applications such as System.Drawing.Imaging for image processing, System.Drawing.Design for design time controls such as dialog boxes, property sheets etc.., System.Design.Text for conrolling fonts, System.Drawing.Printing for controlling printing and print preview.
When dealing with drawings, there are three important .Net structs that are commonly used. These are Point, Size and Rectangle. The Point has two properties X and Y that are of type integer. The Point also has another similar struct called PointF where the X and Y properties are in float data type. Similarly Size and Rectangle have equivalent structs called SizeF and RectangleF. The Size struct has Width and Height as the two properties. The Rectangle can be specified in terms of Top, Left, Bottom, Right or Top, Left, Width and Height, or Location (Point) and Size. There is also a Region class that can be used to define arbitrarily complex shapes.
The Color class has some standard colors defined in it as static members e.g., Color.Red. When a precise control over a color is needed, there is a FromArgb function that can be used to specify the combination of red, green and blue pixels, e.g.,
Color.FromArgb(255,255,0) ; will generate a yellow color.
In GDI+, the DC is wrapped in the class System.Drawing.Graphics. All drawing of shapes such as rectangles, lines, text requires you to create the DC first. The typical code that creates a Dc for you is:
Graphics dc = this.CreateGraphics(); // code written inside a form class
Windows generates a PAINT message whenever a window needs to be repainted. .Net framework invokes an Paint( ) handler whenever a window needs to be repainted.
Example: Create a windows application. Name the project “gditest”. Right click on Form1.cs in the project explorer and choose “view code”. Type the following code in the constructor for form1 class.
public Form1()
{
//
InitializeComponent();
this.BackColor = Color.Aqua;
}
Add a Paint event handler by clicking on the lightning symbol above the properties window and double clicking on the Paint event. Type the following code in it. Note that the Paint event handler gets a DC through the PaintEventArgs parameter, so you can simply use, Graphics dc = e.Graphics; to obtain the DC.
private void Form1_Paint(object
sender, System.Windows.Forms.PaintEventArgs e)
{
//---first create
a device context
// Graphics dc =
this.CreateGraphics(); normally but Paint
// is given a DC
through the PaintEventArgs parameter
Graphics dc = e.Graphics;
Pen bluePen = new
Pen(Color.Blue, 3); // 3 pixels wide
dc.DrawEllipse(bluePen, 30,30,40,50); //30,30 is
top,left, 40=width, 50=height
Pen redPen = new
Pen(Color.Red, 2);
dc.DrawLine(redPen, 30,30,100,100);
Pen greenPen = new
Pen(Color.Green);
dc.DrawRectangle(greenPen,60,60,50,50);
}
If you run the program by choosing “debug->start without debugging”, you will see the following results.
Clipping Region: The PaintEventArgs parameter in the Paint handler also contains information about the clipping rectangle. The clipping rectangle is the rectangular area that needs to be repainted (this is similar to the invalidated region in MFC). The clipping rectangle becomes important when there are a large number of objects to be redrawn in the Paint handler. Using the clipping region information, we can choose to redraw only a few of these objects which are being affected.
Example: Modify the Paint handler code to look as,
private void Form1_Paint(object
sender, System.Windows.Forms.PaintEventArgs e)
{
//---first
create a device context
// Graphics dc =
this.CreateGraphics(); normally but Paint
// is given a DC
through the PaintEventArgs parameter
Graphics dc = e.Graphics;
Pen bluePen = new
Pen(Color.Blue, 3); // 3 pixels wide
dc.DrawEllipse(bluePen, 30,30,40,50); //30,30 is
top,left, 40=width, 50=height
Pen redPen = new
Pen(Color.Red, 2);
dc.DrawLine(redPen, 30,30,100,100);
if
((e.ClipRectangle.Top < 60) && (e.ClipRectangle.Left < 60)
&& (e.ClipRectangle.Bottom > 110) &&
(e.ClipRectangle.Right > 110))
{
Pen greenPen = new
Pen(Color.Green);
dc.DrawRectangle(greenPen,60,60,50,50);
}
}
Now the green rectangle will appear when you run the program, but if you overlap the above form window such that the top and left of the window covering the above form has greater than 60,60 coordinates, then if you minimize the other window that was covering the above form, the green rectangle will not be redrawn because of the clipping region code.
The clipping region is particularly useful when there are different shapes, and one of the shapes is selected to be moved or deleted. Then as a result of movement or deletion, you should redraw only the moved shape. The clipping region to be redrawn is controlled by the Invalidate call.
Brushes are used to fill a particular region with a color or a pattern. For example, the DC has a FillRectangle method and a FillEllipse method which you can pass the brush and the bounding rectangle to fill the shape with the brush.
Example: Modify the Paint handler to fill the rectangle and the ellipse with brushes as shown below.
private void Form1_Paint(object
sender, System.Windows.Forms.PaintEventArgs e)
{
//---first
create a device context
// Graphics dc =
this.CreateGraphics(); normally but Paint
// is given a DC
through the PaintEventArgs parameter
Graphics dc = e.Graphics;
Rectangle r1 = new Rectangle(30,30,40,40);
Pen bluePen = new
Pen(Color.Blue, 3); // 3 pixels wide
dc.DrawEllipse(bluePen,
r1); //30,30
is top,left, 50=height
Brush yellowBrush = new SolidBrush(Color.Yellow);
dc.FillEllipse(yellowBrush,r1);
//
alternatively you can use Brushes type
// which has many Brushes defined as static objects
// e.g., dc.FillEllipse(Brushes.Yellow,r1)
Pen redPen = new
Pen(Color.Red, 2);
dc.DrawLine(redPen, 30,30,100,100);
if ((e.ClipRectangle.Top < 60) &&
(e.ClipRectangle.Left < 60)
&&
(e.ClipRectangle.Bottom > 110) && (e.ClipRectangle.Right > 110))
{
Rectangle r2 = new Rectangle(60,60,50,50);
Pen greenPen = new
Pen(Color.Green);
dc.DrawRectangle(greenPen,r2);
Brush coralBrush = new SolidBrush(Color.Coral);
dc.FillRectangle(coralBrush,r2);
}
}
There are three popular coordinate systems in .Net windows programming. These are World coordinates, Page coordinates and Device coordinates. The World coordinates measure the position relative to the (top, left) of the document in pixels (it can be thought of as the logical coordinates under the old GDI concepts). The Page coordinates measure the position relative to the (top,left) of the client area in pixels. The device units are used to specify measurements in inches and millimeters.
Scrolling:
Scrolling in Windows forms is made easy by the AutoScrollPosition property of the form. If no clipping region is involved, then you can simply adjust the DC properly by the following code:
dc.TranslateTransform(this.AutoScrollPosition.X, this.AutoScrollPosition.Y);
When the clipping region is involved, then you have to adjust the offsets of rectangles by the size of the scroll position obtained from the AutoScrollPosition property.
Example:
Modify the constructor and the Paint code to properly support scrolling as shown below.
public Form1()
{
//
// Required for
Windows Form Designer support
//
InitializeComponent();
this.BackColor
= Color.Aqua;
this.AutoScrollMinSize
= new Size(250,350);
}
private void Form1_Paint(object
sender, System.Windows.Forms.PaintEventArgs e)
{
//---first
create a device context
// Graphics dc =
this.CreateGraphics(); normally but Paint
// is given a DC
through the PaintEventArgs parameter
Graphics dc = e.Graphics;
dc.TranslateTransform(this.AutoScrollPosition.X,
this.AutoScrollPosition.Y); // without this
yellow
// circle will
not scroll properly
Rectangle r1 = new
Rectangle(30,30,140,140);
Pen bluePen = new
Pen(Color.Blue, 3); // 3 pixels wide
dc.DrawEllipse(bluePen, r1); //30,30 is
top,left,
Brush yellowBrush = new SolidBrush(Color.Yellow);
dc.FillEllipse(yellowBrush,r1);
Pen redPen = new
Pen(Color.Red, 2);
dc.DrawLine(redPen, 30,30,100,100);
Size scrollOffset = new Size(this.AutoScrollPosition);
if
((e.ClipRectangle.Top +scrollOffset.Width < 160) ||
(e.ClipRectangle.Left +
scrollOffset.Height < 160))
// && (e.ClipRectangle.Bottom +
scrollOffset.Width > 310)
// && (e.ClipRectangle.Right +
scrollOffset.Height > 310))
{
Rectangle r2 = new Rectangle(160+scrollOffset.Width,
160+scrollOffset.Height,150,150);
Pen greenPen = new
Pen(Color.Green);
dc.DrawRectangle(greenPen,r2);
Brush coralBrush = new SolidBrush(Color.Coral);
dc.FillRectangle(coralBrush,r2);
}
}
Zooming, Scrolling and Hit
Testing:
Zooming is easily supported in .Net Windows applications by the scale factor supported as a property in DC. The typical zooming code looks as:
m_Scalef = 2.0; // set in a menu handler
for zooming
dc.PageUnit = GraphicsUnit.Pixel; //
drawing code
dc.PageScale = m_Scalef; // default value of m_Scalef = 1.0
dc.DrawEllipse(…
The zooming does create some complications with respect to scrolling and hit testing to see if the mouse was clicked inside a region. The TranslateTransform and the scroll offsets will be needed just as described in the previous example.
dc.TranslateTransform(this.AutoScrollPosition.X/m_Scalef,
this.AutoScrollPosition.Y/m_Scalef);
For hit testing, the mouse position can be obtained from the MouseEventArgs parameter in the MouseDown event handler. This mouse position will need to be adjusted for the scrolling position as,
// e is the parameter containing the mouse
position
Size scrollOffset = new Size(this.AutoScrollPosition);
mousep[0] = new
Point(e.X-scrollOffset.Width, e.Y-scrollOffset.Height);
dc.TransformPoints(CoordinateSpace.Page, CoordinateSpace.Device,mousep);
Pen
pen = new Pen(Color.Green,1);
dc.DrawRectangle(pen,m_r1);
if
(m_r1.Contains(new Rectangle(mousep[0].X,
mousep[0].Y,1,1)))
MessageBox.Show("mouse clicked inside rectangle");
Example: Create a new windows application project called testgrfx. Add a menu control to the form. Add a menu item called “zoom” with “zoom in” and “zoom out” as the menu options underneath zoom.
The code for the important parts of the project is shown below.
using System;
using System.Drawing;
using System.Collections;
using
System.ComponentModel;
using
System.Windows.Forms;
using System.Data;
using
System.Drawing.Drawing2D;
namespace testgrfx
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
float m_Scalef;
Rectangle
m_r1;
….
public
Form1()
{
//
// Required for
Windows Form Designer support
//
InitializeComponent();
m_Scalef = 1.0f; // for zooming purposes
m_r1 = new
Rectangle(50,50,100,100);
this.AutoScrollMinSize
= new Size(600,700);
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void
Dispose( bool disposing )
{
if(
disposing )
{
if
(components != null)
{
components.Dispose();
}
}
base.Dispose(
disposing );
}
private void Form1_Paint(object
sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics dc = e.Graphics;
dc.PageUnit = GraphicsUnit.Pixel;
dc.PageScale = m_Scalef;
dc.TranslateTransform(this.AutoScrollPosition.X/m_Scalef,
this.AutoScrollPosition.Y/m_Scalef);
Pen pn = new
Pen(Color.Blue,2);
dc.DrawEllipse(pn,m_r1);
}
private void mnuZoomin_Click(object
sender, System.EventArgs e)
{
m_Scalef = m_Scalef * 2.0f;
Invalidate(); // to trigger Paint of entire client
area
}
private void Form1_MouseDown(object
sender, System.Windows.Forms.MouseEventArgs e)
{
Graphics dc = CreateGraphics();
dc.TranslateTransform(this.AutoScrollPosition.X/m_Scalef,
this.AutoScrollPosition.Y/m_Scalef);
dc.PageUnit = GraphicsUnit.Pixel;
dc.PageScale = m_Scalef;
Point [] mousep = new
Point[1];
// make sure to
adjust mouse pos.for scroll position
Size scrollOffset = new Size(this.AutoScrollPosition);
mousep[0] = new
Point(e.X-scrollOffset.Width, e.Y-scrollOffset.Height);
dc.TransformPoints(CoordinateSpace.Page,
CoordinateSpace.Device,mousep);
Pen
pen = new Pen(Color.Green,1);
dc.DrawRectangle(pen,m_r1);
if
(m_r1.Contains(new Rectangle(mousep[0].X,
mousep[0].Y,1,1)))
MessageBox.Show("click inside
rectangle");
}
private void Form1_Load(object
sender, System.EventArgs e)
{
}
private void mnuZoomout_Click(object
sender, System.EventArgs e)
{
m_Scalef = m_Scalef / 2.0f;
Invalidate();
}
}
}
Displaying Text, Fonts:
The DC provides a DrawString method to display a text in the client area. Brushes are used with fonts to display the text in a particular color.
Example: Create a new windows application. Name the project fontex. Type the following code in the Form1 class.
public class Form1 : System.Windows.Forms.Form
{
/// <summary>
/// Required designer variable.
/// </summary>
private
System.ComponentModel.Container components = null;
private Brush blackBr = Brushes.Black;
private
Brush redBr = Brushes.Red;
private
Brush royalBlueBr = Brushes.RoyalBlue;
private Font
boldTRFont = new Font("Times New
Roman",14,FontStyle.Bold);
private Font
italicCOFont = new
Font("Courier",12,FontStyle.Italic);
private Font ARFont = new Font("Arial",10,FontStyle.Regular);
Add a Paint handler to the form with the following code in it.
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics dc = e.Graphics;
String s1 = "This is a test of
Fonts" ;
String s2 = "This line is in
italics";
String s3 = "This line is in Arial,
regular style";
dc.DrawString(s1,boldTRFont,blackBr,new Point(0,20));
dc.DrawString(s1,italicCOFont,redBr,new Point(0,40));
dc.DrawString(s1,ARFont,royalBlueBr,new Point(0,60));
}
Collecting Font information
from the Common Font Dialog:
Add a menu control to the form in the fontex example. Add a menu item called “choose” with a menu option “choose font” and “choose color” underneath it. Name the “choose” menu item as mnuChoose, “choose Font” as mnuChooseFont and “choose Color” as mnuChooseColor.
Add the following declarations to the Form1 class as,
private string sFont =
"Setting Fonts through Font Dialog" ;
private
Color fontColor;
private
System.Windows.Forms.MenuItem mnuChooseColor;
private Font font;
Modify the constructor to look as,
public Form1()
{
//
// Required for
Windows Form Designer support
//
InitializeComponent();
fontColor = Color.Black;
font = new
Font("Arial",8,FontStyle.Regular);
}
Add the following line to the existing Paint handler.
dc.DrawString(sFont,font, new
SolidBrush(fontColor),new Point(0,0));
Type the following code for the two menu event handlers.
private void
menuChooseFont_Click(object sender,
System.EventArgs e)
{
FontDialog fontDlg = new FontDialog();
//fontDlg.Color
= Color.Red ; // default color
if
(fontDlg.ShowDialog() == DialogResult.OK)
{
font = fontDlg.Font;
Invalidate();
}
}
private void mnuChooseColor_Click(object
sender, System.EventArgs e)
{
ColorDialog colorDlg = new ColorDialog();
if
(colorDlg.ShowDialog() == DialogResult.OK)
{
fontColor = colorDlg.Color;
Invalidate();
}
}
Run the application and experiment with the color and font dialogs.
Client Area Determination:
Sometimes our windows application needs to know the size of the client area. In simple situations, the code in a form class can obtain the client area as,
int
cx = this.ClientRectangle.Width;
int cy = this.ClientRectangle.Height;
C# - Image Processing
C# provides the System.Drawing.Imaging namespace that contains the types related to image processing. One of the useful classes in this namespace is called Bitmap which can easily read images from JPEG or GIF files and convert them to bitmaps. The constructor for the Bitmap class takes on an image file and generates the corresponding bitmap for it.
Bitmap b1
= new Bitmap(“t1.jpg”);
Once the image has been converted to a bitmap, you can easily apply different image processing techniques to the individual pixels. For example, if you add a small integer to each RGB byte, you will end up increasing the brightness of the picture. We will develop a small windows application that will allow us to display images and perform different operations on them.
When operating on bitmaps, we need to modify the pixel memory directly. This is where old C/C++ style pointers can be very handy. Fortunately C# allows us to deal with memory directly through the unsafe keyword. If a C# code uses an unsafe block, the entire project has to be built by specifying the “Configuration Properties” of the project to “allow unsafe blocks”. We will explain these issues through an example.
Example: Create a new Windows application. Name the project ImageProc.
Put two picture boxes on the form. Name the left picture box as “picOrig”. Name the right picture box as “picRight”. Set the border style property of the picture boxes to Fixed Single. Set the SizeMode property of the picture boxes to “StretchImage”. Try to size the picture boxes so that their width is larger than their height (similar to the aspect ratio of a 3x5 picture). Put a label “Original Image” underneath the left picture box, and a label “After Image Processing” under the right picture box.
Add a main menu to the form. Create a top level menu called “File” with a menu item “Load Image” underneath it. Name the menus as “mnuFile” and “mnuFileLoadImage” respectively.
Type the following code for the “Load Image” menu handler.
private void mnuFileLoadImage_Click(object
sender, System.EventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
dialog.Filter = "jpeg files
(*.jpg)|*.jpg";
if
(DialogResult.OK == dialog.ShowDialog())
this.picOrig.Image
= new Bitmap(dialog.FileName);
}
Run the program and click on the menu to load an image. Choose a jpg type file and see if the image gets loaded properly in the left picture box.
Add a C# class to the project (by right clicking on the project name through the project explorer). Name the class MyImageProc. Add the following method to the MyImageProc class to convert to a Gray scale image from a color image.
public static bool
CovertToGray(Bitmap b)
{
// GDI+ return
format is BGR, NOT RGB.
BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
ImageLockMode.ReadWrite,
PixelFormat.Format24bppRgb);
int stride
= bmData.Stride; // bytes in a row 3*b.Width
System.IntPtr Scan0 = bmData.Scan0;
unsafe
{
byte * p
= (byte *)(void
*)Scan0;
byte
red, green, blue;
int
nOffset = stride - b.Width*3;
for(int y=0;y < b.Height;++y)
{
for(int x=0; x < b.Width; ++x )
{
blue = p[0];
green = p[1];
red = p[2];
p[0] = p[1] = p[2] = (byte)(.299 * red
+ .587 * green + .114 *
blue);
p += 3;
}
p += nOffset;
}
}
b.UnlockBits(bmData);
return true;
}
Because the above code is using types that are defined in the System.Drawing and System.Drawing.Imaging namespaces, you will need to add the following lines, at the top of the MyImageProc class.
using System.Drawing;
using
System.Drawing.Imaging;
Add a top level menu called “Image Proc”. Underneath the “Image Proc” menu, add a menu item called “Convert to Gray”. Name the “Image proc” menu as mnuImage, and the “Convert to Gray” menu as mnuImageGray. Set the enabled property of this menu to false. Write the following event handler for the “Convert to Gray” menu item.
private void mnuImageGray_Click(object
sender, System.EventArgs e)
{
Bitmap copy = new
Bitmap((Bitmap) this.picOrig.Image);
MyImageProc.CovertToGray(copy);
picRight.Image = null;
picRight.Image = copy;
}
Modify the File->Load Image handler to enable the “Convert to Gary” menu once an image file has been loaded in the left image box.
private void mnuFileLoadImage_Click(object
sender, System.EventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
dialog.Filter = "jpeg files
(*.jpg)|*.jpg";
if
(DialogResult.OK == dialog.ShowDialog())
{
this.picOrig.Image
= new Bitmap(dialog.FileName);
mnuImageGray.Enabled = true;
}
}
Now if you try to build the program, you will get an error message as “Unsafe code may only appear if compiling with /unsafe”. To take care of this error, right click on the project name, and choose properties. Then click on the “Configuration Properties” and change the “Allow unsafe code block” settings to “True”, as shown below.
Now build the project. Run the application and load an image in the left hand picture box. Then click on the “Image Proc” menu item and choose “Convert to Gray”. You should see the left hand picture being converted to a “Gray Scale” picture and displayed in the right hand picture box.
Add a method called “Brighten” to the MyImageProc class. This method will increase the brightness of each pixel by adding a number to each of the RGB bytes. The code for the Brighten method is shown below.
public static bool Brighten(Bitmap
b, int nBrightness)
{
// GDI+ return
format is BGR, NOT RGB.
BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
ImageLockMode.ReadWrite,
PixelFormat.Format24bppRgb);
int stride
= bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
unsafe
{
int
nVal;
byte * p
= (byte *)(void
*)Scan0;
int
nOffset = stride - b.Width*3;
int
nWidth = b.Width * 3;
for(int y=0;y<b.Height;++y)
{
for (int x = 0; x
< nWidth; ++x)
{
nVal = (int)
(p[0] + nBrightness);
if
(nVal < 0) nVal = 0;
if
(nVal > 255) nVal = 255;
p[0] = (byte)nVal;
++p;
}
p += nOffset;
}
}
b.UnlockBits(bmData);
return true;
}
Add another menu item called “Brighten Image” underneath the “Image Proc” menu. Name the “Brighten Image” menu as mnuImageBrighten. Set the enabled property of this menu item to false. Type the following code in the “Brighten Image” handler.
private void mnuImageBrighten_Click(object
sender, System.EventArgs e)
{
Bitmap copy = new
Bitmap((Bitmap) this.picOrig.Image);
MyImageProc.Brighten(copy,40); // add 40 to each byte in pixel
picRight.Image = null;
picRight.Image = copy;
}
Modify the File->Load Image handler to enable the mnuImageBrighten by adding the following line to it:
mnuImageBrighten.Enabled = true;
Run the program, and choose an image, then choose the “Brighten Image” menu to brighten the image as shown below.
Rather than fixing the amount of brightness, it will be nice to let the user choose the brightness level through another dialog. We can provide a default value of 30 in this dialog. To achieve this, add a “windows form” to the project (by right clicking on the project name and choosing Add). Name the form “BrightnessDlg.cs”. Set the FormBorder property to “Fixed Single”. Also set the “Maximize Box” property to False.
Add a text box to the form. Name the text box, txtBrightness. Add two buttons called Apply and Cancel, with names of cmdApply and cmdCancel respectively. This dialog will look as,
The important code in the BrightnessDlg.cs file will look as,
public class BrightnessDlg : System.Windows.Forms.Form
{
…
public
int nBrightness
{
get
{
return
(Convert.ToInt32(txtBrightness.Text, 10));
}
set{txtBrightness.Text
= value.ToString();}
}
private
void cmdApply_Click(object
sender, System.EventArgs e)
{
this.DialogResult
= System.Windows.Forms.DialogResult.OK;
this.Close();
}
private
void cmdCancel_Click(object
sender, System.EventArgs e)
{
this.DialogResult
= System.Windows.Forms.DialogResult.Cancel;
this.Close();
}
}
}
Modify the “Brighten Image” event handler as,
private void mnuImageBrighten_Click(object
sender, System.EventArgs e)
{
BrightnessDlg dlg = new BrightnessDlg();
dlg.nBrightness = 30; // default
brightness of 30
if
(DialogResult.OK == dlg.ShowDialog())
{
Bitmap copy = new
Bitmap((Bitmap) this.picOrig.Image);
MyImageProc.Brighten(copy,dlg.nBrightness);
picRight.Image = null;
picRight.Image = copy;
mnuFileSave.Enabled = true;
}
}
Add a menu called “Save Right Image” under the File menu. Give it a name of mnuFileSave. Type the following code in the event handler.
private void mnuFileSave_Click(object
sender, System.EventArgs e)
{
SaveFileDialog dlg = new SaveFileDialog();
dlg.Filter = "jpeg files
(*.jpg)|*.jpg";
if
(DialogResult.OK == dlg.ShowDialog())
this.picRight.Image.Save(dlg.FileName,
ImageFormat.Jpeg);
}
You will need to add the following namespace to the Form1.cs file,
using
System.Drawing.Imaging;
Modify the Image->Convert to Gray handler to enable the File->Save menu by adding the following line shown in bold.
private void mnuImageGray_Click(object
sender, System.EventArgs e)
{
Bitmap copy = new
Bitmap((Bitmap) this.picOrig.Image);
MyImageProc.CovertToGray(copy);
picRight.Image = null;
picRight.Image = copy;
mnuFileSave.Enabled = true;
}
Add a menu called “Exit” under the File menu. Give it a name of mnuFileExit. Type the following code in the event handler.
private void
mnuFileExit_Click(object sender,
System.EventArgs e)
{
this.Close();
}
Build and test the program.