|
||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionIn 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 1. How to get the point coordinate on a curveI 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 // 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 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 controlThe 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, 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 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 levelI didn’t use the System.Drawing.Imaging.BitmapData bmpData =
bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
bmp.PixelFormat);
Also, I did not use " // 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 // 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 Any suggestion is appreciated.
|
|||||||||||||||||||||||||||||||||||||||||||||||