Article

# Adjusting an Image Curve with C#

By , 11 Jun 2008
 Rate this:

## 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.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.

Unknown
No Biography provided

 First Prev Next
 My vote of 5 manoj kumar choubey 20-Feb-12 20:52
 can you help me? csr_hema 15-Jul-08 20:29
 Thank you for supplying me this article for studying, but i want to get the point coordinate on a curve, not use the function like you gived, i have used the function graphics.drawcurve to get a curve, but i have no idea to get the point coordinate on this curve,the curve is drawed by some points, that can be add more when the user drag the curve, can you help me?
 Replace buttons "Zoom In" and "Zoom Out" [modified] Slavamix 12-Jun-08 2:35
 Last Visit: 31-Dec-99 18:00     Last Update: 8-Mar-14 19:13 Refresh 1