5,659,906 members and growing! (17,918 online)
Email Password   helpLost your password?
Multimedia » General Graphics » General     Intermediate License: The Code Project Open License (CPOL)

Adjusting an Image Curve with C#

By YLS CS

Introducing my second user control for image editing.
C#, Windows, GDI+, Dev

Posted: 12 Jun 2008
Updated: 12 Jun 2008
Views: 6,361
Bookmarked: 13 times
Announcements
Loading...



Search    
Advanced Search
Sitemap
4 votes for this Article.
Popularity: 2.78 Rating: 4.63 out of 5
0 votes, 0.0%
1
0 votes, 0.0%
2
0 votes, 0.0%
3
2 votes, 50.0%
4
2 votes, 50.0%
5

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

YLS CS



Location: United States United States

Other popular General Graphics articles:

  • A flexible charting library for .NET
    Looking for a way to draw 2D line graphs with C#? Here's yet another charting class library with a high degree of configurability, that is also easy to use.
  • CxImage
    CxImage is a C++ class to load, save, display, transform BMP, JPEG, GIF, PNG, TIFF, MNG, ICO, PCX, TGA, WMF, WBMP, JBG, J2K images.
  • 3D Pie Chart
    A class library for drawing 3D pie charts.
  • Really cool visual FX
    A set of classes for doing stunning visual effects, including water, plasma and fire.
  • ImageStone
    An article on a library for image manipulation.
Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 2 of 2 (Total in Forum: 2) (Refresh)FirstPrevNext
Generalcan you help me?membercsr_hema21:29 15 Jul '08  
GeneralReplace buttons "Zoom In" and "Zoom Out" [modified]memberSlavamix3:35 12 Jun '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 12 Jun 2008
Editor: Smitha Vijayan
Copyright 2008 by YLS CS
Everything else Copyright © CodeProject, 1999-2008
Web18 | Advertise on the Code Project