Link to home
Start Free TrialLog in
Avatar of unni_bcanti
unni_bcanti

asked on

Auto Contrast using GDI+

I need to implement an Auto contrast function in my Image Editor(MFC, GDI+).
It should work just like the Auto contrast menu option in Photoshop.
Can anyone help me with a source code / algorithm?

Thanks in advance

Unni.K.S
Avatar of AlexFM
AlexFM

There are two ways to find optimal contrast for the image: contrast stretching and histogram equalization.
Contrast stretching is based on actual minimal and maximal pixel values in the image. if min = 0 and max = 255, it's impossible to stretch this image. But if actual pixel range in the image is less than 0-255, the following formula stretches image to full range:

out_image[i][j = (2^n - 1) * (input_image[i][j] - min)/(max - min)  

It is possible to improve this formula calculating min and max using percentile by image histogram.

Histogram equalization:
out_image[i][j] = (Dm / area) * sum_of_hist[ input_image[i][j] ]

out_image[i]ij] - destination image pixel value
input_image[i][j] - source image pixel value
Dm - number of gray levels in the image (256 for 8 bpp images)
sum_of_hist - cumulative histogram of the source image
area - image size (width*height)
n - image depth (for example, 8 for 8bpp images).
Avatar of unni_bcanti

ASKER

Please give little more descriptions, like..

how to get  'max' & 'min' in first formula

or how to get 'sum_of_hist' ..

Because, my application is a simple image editor & I dont know much about manipulating an image pixel by pixel.
Pseudo code for 8 bits per pixel images.

Calculation min and max.

min = 255
max = 0
for each pixel in image
{
    if ( pixel > max ) max = pixel;
    if ( pixel < min ) min = pixel;
}

Calculating histogram:

int hist[256];
// set all hist[i] to 0

for each pixel in image
{
    hist[pixel] ++;
}

Calculating cumulative histogram from histogram:

int sum_of_hist[256];
int sum = 0;

for (int i = 0; i < 256; i++)
{
    sum += hist[i];
    sum_of_hist[i] = sum;
}

hist[i] is number of pixels in image with value i.
sum_of_h[i] is number of pixels in image with value <= i.

To access individual pixels in GDI+ image use LockBits function:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdicpp/GDIPlus/GDIPlusReference/Classes/BitmapClass/BitmapMethods/LockBits.asp

This topic contains code examples.
Hello,
Thanks Alex.
I need it for 32bpp image. So I wrote it as given below.
But I'm gettin MIN & MAX as same (4278190080).
What could be wrong?
Pls help me.

void CPhotoEditor::AutoContrast(CLSID clsID)
{
      INT W= m_Image->GetWidth();
      INT H= m_Image->GetHeight();
      BitmapData bitmapData,bmpData;
      m_Image->LockBits(&Rect(0,0,W,H),ImageLockModeRead,m_Image->GetPixelFormat(),&bitmapData);

      ARGB MIN=Color(255,255,255,255).GetValue();
      ARGB MAX=Color(0,0,0,0).GetValue();
      UINT X,Y;
      for(X=0; X<H; ++X)
      {
            for(Y=0; Y<W; ++Y)
            {
                  Color color;
                  m_Image->GetPixel(X,Y,&color);
                  ARGB pixel=color.GetValue();
                  if ( pixel > MAX ) MAX = pixel;
                  if ( pixel < MIN ) MIN = pixel;      
            }
      }
      Bitmap tempBmp(W,H);
      tempBmp.LockBits(&Rect(0,0,W,H),ImageLockModeWrite,m_Image->GetPixelFormat(),&bmpData);
      for(X=0; X<H; ++X)
      {
            for(Y=0; Y<W; ++Y)
            {
                  Color color;
                  m_Image->GetPixel(X,Y,&color);
                  tempBmp.SetPixel(X,Y, (4294967295 * (color.GetValue()- MIN)/(MAX-MIN)));                  
            }
      }
      m_Image->UnlockBits(&bitmapData);
      tempBmp.UnlockBits(&bmpData);
      tempBmp.Save(L"C:\\test.jpg",&clsID);
      
}
For RGB bitmap you need min and max for every color component. ARGB MIN=Color(255,255,255,255).GetValue() has no sence. All image processing operations are applied to every color component (R, G, B) independently.
There are two ways to access bitmap bits:
1) Fast way using LockBits. This allows to read bitmap bits using simple BYTE* pointer - see code sample from the link in my previous post.
2) Very slow GetPixel/SetPixel way.

You are using both of them, this is incorrect. If you want simple GetPixel/SetPixel way, remove LockBits stuff.
hmm.
m_Image->GetPixel() always return same value(4278190080).
something is wrong thr.
OK. Thanks. Let me try again with LockBits only.
Did you see this article?
http://www.codeguru.com/Cpp/G-M/gdi/gdi/article.php/c3667/

This way allows to make GDI+ image processing using simple hogh-level functions. However, it is impossible to find optimal contrast by this way. This article shows how to work with every pixel in GDI+ ARGB bitmap:
http://www.codeguru.com/Cpp/G-M/gdi/gdi/article.php/c3705/
Thanks for the links Alex. I copied some functions to my class and implmented as follows.
But no difference in output.. Pls help...

      INT W= m_Image->GetWidth();
      INT H= m_Image->GetHeight();
      
      UINT MIN_R,MIN_G,MIN_B;MIN_R=MIN_G=MIN_B=255;
      UINT MAX_R,MAX_G,MAX_B; MAX_R=MAX_G=MAX_B=0;
      int* pixels=Bitmap2PixelData(m_Image);
      int i, a, r, g, b;

      for(i=0; i < (W * H); i++)
      {
            pixel2ARGB(pixels[i], a,r,g,b);
            if ( r > MAX_R ) MAX_R = r;
            if ( r < MIN_R ) MIN_R = r;
            if ( g > MAX_G ) MAX_G = g;
            if ( g < MIN_G ) MIN_G = g;
            if ( b > MAX_B ) MAX_B = b;
            if ( b < MIN_B ) MIN_B = b;
      }
      for(i=0; i < (W * H); i++)
      {
            pixel2ARGB(pixels[i], a,r,g,b);
            r = 255 * (r - MIN_R)/(MAX_R - MIN_R);
            g = 255 * (g - MIN_G)/(MAX_G - MIN_G);
            b = 255 * (b - MIN_B)/(MAX_B - MIN_B);
            pixels[i] = ARGB2Pixel(a,r,g,b);
      }
      delete m_Image;
      m_Image=PixelData2BitMap(W,H,pixels);
      delete []pixels;
      Invalidate();
What are min and max pixel values? If min = 0 and max = 255, this formula doesn't change anything:

r = 255 * (r - MIN_R)/(MAX_R - MIN_R) = 255 * r / 255

If actual pixel values are in the range less than 0-255 (for example, 100-200), formula stretches them to full range improving contrast. Code working with pixels looks OK. However to test it, make the following change:

          r = 255; // * (r - MIN_R)/(MAX_R - MIN_R);
          g = 255; // * (g - MIN_G)/(MAX_G - MIN_G);
          b = 255; // * (b - MIN_B)/(MAX_B - MIN_B);

If you get white picture, all GDI+ stuff is OK and you need to work with math. Try other images with different pixel range. Try histogram equalization.
hmmm. yes.
I'm getting a white picture.
And I traced MIN & MAX values , all are getting 0 & 255.
I think it is not possible to calculate it in this way.
becuz, if I split up r,g,b values somewhere there will be a 255 and somewhere thr will be a 0 for each color. thus min & max will not change. So, image also will not change.
So, I think I must calculate with the 32 bit pixel value without spliting.
But if I write the pixel values to output window with TRACE, it gives only negative values.

I tried histogram also by splitting up each color. but that makes everything to black.
Pls help.

      INT W= m_Image->GetWidth();
      INT H= m_Image->GetHeight();
      
      int* pixels=Bitmap2PixelData(m_Image);
      int i, a, r, g, b;
      int hist_r[256],hist_g[256],hist_b[256];
      for(i=0;i<256;i++)
      {
            hist_r[i]=hist_g[i]=hist_b[i]=0;
      }

      for(i=0; i < (W * H); i++)
      {
            pixel2ARGB(pixels[i], a,r,g,b);
            hist_r[r]++;
            hist_g[g]++;
            hist_b[b]++;
      }

      int sum_of_hist_r[256],sum_of_hist_g[256],sum_of_hist_b[256];
      int sum_r, sum_g, sum_b;
      sum_r=sum_g=sum_b=0;

      for (i = 0; i < 256; i++)
      {
            sum_r += hist_r[i];
            sum_of_hist_r[i] = sum_r;
            sum_g += hist_g[i];
            sum_of_hist_g[i] = sum_g;
            sum_b += hist_b[i];
            sum_of_hist_b[i] = sum_b;
      }

      for(i=0; i < (W * H); i++)
      {
            pixel2ARGB(pixels[i], a,r,g,b);
            r = (256 / (W*H)) * sum_of_hist_r[r];
            g = (256 / (W*H)) * sum_of_hist_g[g];
            b = (256 / (W*H)) * sum_of_hist_b[b];
            pixels[i] = ARGB2Pixel(a,r,g,b);
      }

ASKER CERTIFIED SOLUTION
Avatar of AlexFM
AlexFM

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I think my answer is OK.
Hi Alex, Your answer is theoritically OK. and it works fine if I implement the math formula with only one channel of color. like, if I apply formula on extracted red bits of an image it seems fine, it works same with the Green & Blue as well. But if I combine all separated channels together and  make a 32 bit or 24 bit image, output seems awkward.
I think some more things has to be done for 32 bit images.
This is known image processing problem, applying the same algorithm for every color of RGB image changes colors relation. Like every image processing problem, this one doesn't have magic solution.
I also work with RGB images, and in such case there are two choices:
1) Convert every color independently
2) Select one main color, calculate required parameters and apply them to all three colors.

In any case algorithms are not applied to whole RGBW DWORD value.
I think that this discussion is out of range of pure programming problem, you need to find answer in some image processing book or message board.