12,826,657 members (26,925 online)
Add your own
alternative version

#### Stats

39.7K views
1.4K downloads
34 bookmarked
Posted 12 Jun 2008

# Adjusting an Image Curve with C#

, 12 Jun 2008 CPOL
 Rate this:
Please Sign up or sign in to vote.
Introducing my second user control for image editing.

## Introduction

In image editing, a curve is a remapping of image tonality, specified as a function from the input level to the output level, used as a way to emphasize colors or other elements in a picture. Here is a user control written in C# to adjust image curves. We know C# provides a method to draw curves, but I don’t know how to get the coordinates of any point on the curve drawn using `DrawCurve`.

## 1. How to get the point coordinate on a curve

I designed a work space with a size 255 X 255, and set up 256 points with the x-axis representing the input level (0 to 255) and the y-axis representing the output level (0 to 255). And, I used `DrawLines` to get a curve (actually a polyline). The work space was transformed to a user control by the `Matrix` class.

```// set up points
Point[] wLevelPts = new Point[256];

// setup work space origin
Point Origin = new Point(labelX0.Left, labelY0.Bottom);

// Work Space
Point wsPt = new Point(labelX0.Left - 1, labelY2.Top - 1);
int wsWidth = labelX2.Right - labelX0.Left + 2;
int wsHeight = labelY0.Bottom - labelY2.Top + 2;
workSpace = new Rectangle(wsPt, new Size(wsWidth, wsHeight));

// transformation from work space to control
mxWtoC = new Matrix(1, 0, 0, -1, 0, 0);//reflect across x-axis
mxWtoC.Scale((float)(labelX2.Right - labelX0.Left) / 255f, (float)(labelY0.Bottom
- labelY2.Top) / 255f);
mxWtoC.Translate(Origin.X, Origin.Y, MatrixOrder.Append);

// transformation from control to work space
mxCtoW = mxWtoC.Clone();
mxCtoW.Invert();```

I used the function `B(t) = (1 - t)^2*p0 + 2t(1 - t)*p1 + t^2*p2, 0 < t < 1` to construct a quadratic Bezier curve with three points `p0`, `p1`, `p2`, and got all the points that represented the image pixel input and output level on the curve:

```private void getBezierPoints(Point sPt, Point cPt, Point ePt)
{
wLevelPts[sPt.X].Y = sPt.Y;
if (ePt.X - sPt.X > 2)
{
int aa = ePt.X - sPt.X;
int k = sPt.X;

double[] a = new double[3];
double[] b = new double[3];
a[0] = sPt.X;
a[1] = cPt.X;
a[2] = ePt.X;
b[0] = sPt.Y;
b[1] = cPt.Y;
b[2] = ePt.Y;

int interpolation = 5 * aa;

double tUnit = 1.0 / interpolation;
for (int i = 1; i < interpolation + 1; i++)
{
double t = tUnit * i;

// use function B(t) to get x-coordinate
int X = (int)((1.0 - t) * (1.0 - t) * a[0] + 2.0 * t *
(1 - t) * a[1] + t * t * a[2]);

if (X > k && X < ePt.X)
{
int bb = X - k;

// use function B(t) to get y-coordinate
double Y = (1.0 - t) * (1.0 - t) * b[0] + 2.0 * t *
(1 - t) * b[1] + t * t * b[2];

// if two points not close, do interpolation
for (int j = 1; j < bb + 1; j++)
{
double c = (double)wLevelPts[k].Y * (double)(bb - j) /
(double)bb + Y * (double)j / (double)bb;
if (c < 0) c = 0;
if (c > 255) c = 255;
wLevelPts[k + j].Y = (int)c;
}
k = k + bb;
}
}
}
}```

## 2. How to change the curve on the user control

The curve was constructed using three points. To change the curve, these three points must be moved by the user. I had two endpoints for the quadratic Bezier curve which could be moved by moving the label controls, the small black squares on the user control.

```private void labelPt3_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
isLblMoving = true;
((Label)sender).Tag = new Point(e.X, e.Y);
}
}

private void labelPt3_MouseMove(object sender, MouseEventArgs e)
{
Point[] lblPts = new Point[] { pt0, pt1, pt2, pt3, pt4 };

// transform points on work space to control
mxWtoC.TransformPoints(lblPts);
if (e.Button == MouseButtons.Left && isLblMoving)
{
Label pt = (Label)sender;
Point p = (Point)pt.Tag;
int x = pt.Left + e.X - p.X;
int y = pt.Top + e.Y - p.Y;
if (y < lblPts[4].Y) y = lblPts[4].Y;
if (y > lblPts[0].Y) y = lblPts[0].Y;
if (pt == labelPt1)
{
if (x > lblPts[2].X) x = lblPts[2].X;
if (x < lblPts[0].X) x = lblPts[0].X;

// get new position on work space
pt1 = ControlToWorkspace(new Point(x, y));

// get points on curve
getLevelPoints(1);
}
if (pt == labelPt2)
{
if (x < lblPts[1].X) x = lblPts[1].X;
if (x > lblPts[3].X) x = lblPts[3].X;

pt2 = ControlToWorkspace(new Point(x, y));
getLevelPoints(2);
}
if (pt == labelPt3)
{
if (x < lblPts[2].X) x = lblPts[2].X;
if (x > lblPts[4].X) x = lblPts[4].X;

pt3 = ControlToWorkspace(new Point(x, y));
getLevelPoints(3);
}

pt.Top = y - 2;
pt.Left = x - 2;

Invalidate();
}
}

private void labelPt3_MouseUp(object sender, MouseEventArgs e)
{
isLblMoving = false;

... ...
}```

And, the middle point to control the quadratic Bezier curve was directly got form this user control's mouse event:

```private void ImageCurve_MouseDown(object sender, MouseEventArgs e)
{
Point[] pts = new Point[] { pt0, pt1, pt2, pt3, pt4 };
mxWtoC.TransformPoints(pts);

Rectangle r1 = new Rectangle(pts[1].X, pts[4].Y,
pts[2].X - pts[1].X, pts[0].Y - pts[4].Y);
Rectangle r2 = new Rectangle(pts[2].X, pts[4].Y, pts[3].X -
pts[2].X, pts[0].Y - pts[4].Y);

// if between pt1 and pt2, move cPt1
if (e.Button == MouseButtons.Left && (pts[2].X - pts[1].X) > 2
&& r1.Contains(new Point(e.X, e.Y)))
{
isCpt1 = true;//move cPt1
}

// if between pt2 and pt3, move cPt2
if (e.Button == MouseButtons.Left && (pts[3].X - pts[2].X) > 2
&& r2.Contains(new Point(e.X, e.Y)))
{
isCpt2 = true;//move cPt2
}
}

private void ImageCurve_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{

if (isCpt1)
{
cPt1 = ControlToWorkspace(new Point(e.X, e.Y));
getBezierPoints(pt1, cPt1, pt2);
}
if (isCpt2)
{
cPt2 = ControlToWorkspace(new Point(e.X, e.Y));
getBezierPoints(pt2, cPt2, pt3);
}
}
Invalidate();
}

private void ImageCurve_MouseUp(object sender, MouseEventArgs e)
{
if (isCpt1) isCpt1 = false;
if (isCpt2) isCpt2 = false;

... ...
}```

For using this user control to adjust the image curve, I wrote a custom event, `LevelChanged`:

```public class LevelChangedEventArgs : EventArgs
{
private byte[] levelValue;

public LevelChangedEventArgs(byte[] LevelValue)
{
levelValue = LevelValue;
}

public byte[] LevelValue
{
get { return levelValue; }
}
}```

And, it is called in the `MouseUp` event for moving the control points that construct the curve. When the user changes the curve and the mouse is up, the event `LevelChanged` will be triggered.

```private void ImageCurve_MouseUp(object sender, MouseEventArgs e)
{
... ...

getLevelbytes();
OnLevelChanged(new LevelChangedEventArgs(LevelValue)); // call event
}

... ...

private void labelPt3_MouseUp(object sender, MouseEventArgs e)
{
... ...

getLevelbytes();
OnLevelChanged(new LevelChangedEventArgs(LevelValue)); // call event
}```

## 3. How to get an image pixel and change its level

I didn’t use the `Getpixel` and `Setpixel` methods just because they are very slow. I used the `Bitmap.LockBits` method to lock the image and performed the pixel level modifications directly on the RGB data in memory.

```System.Drawing.Imaging.BitmapData bmpData =
bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
bmp.PixelFormat);```

Also, I did not use "`unsafe`" code and pointers. But, I tried the `System.Runtime.InteropServices.Marshal.Copy` method to copy bytes to and from the location of the image in memory. It worked very well.

```// Copy the RGB values into the array.
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);

... ...

// Copy the RGB values back to the bitmap
System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);```

I tried using two `for…` loops to reach the selected pixels, but it was much slower than using one `for…` loop plus a conditional `if…` statement:

```// I try use for... for... two loops, but it is much slower
// than one loop

for (int i = scanStart; i < scanEnd + 1; i++)//only one loop
{
int w = i % bmpData.Stride;
int p = w % 3;
if (w > bytesStart && w < bytesEnd && p == integer)
{
rgbValues[i] = Levels[rgbValues[i]];
}
}```

Finally, I used my own `ImagePanel` to display the image in which the curve level was being changed and to select the part of the image to be adjusted.

Any suggestion is appreciated.

## License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

## About the Author

 Unknown
No Biography provided

## Comments and Discussions

 View All Threads First Prev Next
 My vote of 5 manoj kumar choubey20-Feb-12 21:52 manoj kumar choubey 20-Feb-12 21:52
 Last Visit: 31-Dec-99 19:00     Last Update: 28-Mar-17 16:32 Refresh 1

General    News    Suggestion    Question    Bug    Answer    Joke    Praise    Rant    Admin

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.170326.1 | Last Updated 12 Jun 2008
Article Copyright 2008 by YLS CS
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid