Re: rgb -> hsv and back



So it has been done... I implemented RGB to HSV, or be precise HSI, at
my own, because I didn't get the lcms stuff...
I'm a little bit disappointed, because the result is in gray is more
impressive, but I have some more ideas.

So let's come to the good things:
* the result is most of the time nicer than the "Histogram Equalizer" in
gthumb and that is the same algorithm.
* the result is in dark images most of the time nicer than 'color
improve' from f-spot

Now to the ugly stuff:
* I am maybe not too objective about the quality of output image
* noise and image can be seen better
* the performance could be really improved. Help wanted.
* the conversion from RGB to HSI and back again manipulates the image
due to rounding faults. More Help wanted...

I put the code into src/Filters (that's not a good place for
HSIImage.cs, i know...).

Regards,

georg


Am Montag, den 14.04.2008, 10:58 -0500 schrieb Larry Ewing:
> The interactive color adjustment dialog does a lot of this sort of
> transform but it does it via an adjustment profile that it creates on
> the fly.  It may be hard to follow the code because we use lcms and the
> actual image color profile to convert to luminance space, adjust, then
> convert back again in single pass. That code could be adapted to convert
> to a colorspace of your choosing which could then be used collect the
> statistics you want to create an adjustment back to rgb.
> 
> If you would like try that direction I can help walk you through the
> darker corners of the code.  Otherwise as Stephane points our there
> isn't much existing infrastructure to help you out at the moment.
> 
> --Larry 
> 
> On Mon, 2008-04-14 at 14:37 +0200, Stephane Delcroix wrote:
> > Hey,
> > 
> > 1. no, there's no doc but the code itself...
> > 2. AFAIK, (Gdk)pixbuf doesn't support HSV. so "converting a RGB pixbuf
> > to HSV" doesn't make a lot of sense
> > so, you'll probably have to do the RGB->HSV conversion (and back)
> > yourself, but you might find some converter already available on the
> > web. My .02€ tip: don't do that in managed code, it'll be way too slow.
> > 
> > s
> > 
> > On Sun, 2008-04-13 at 19:59 +0200, Georg Leugner wrote:
> > > Hallo,
> > > 
> > > I'm currently coding on the one click contrast filter like picassa has.
> > > But I need some information:
> > > 1. First and most important. Is there somewhere hidden more
> > > documentation than on the get involved page?
> > > 2. I need to convert a RGB pixbuf to HSV. I couldn't find a method which
> > > does that, but i found some methods using hue, saturation... Do I miss
> > > something?
> > > 
> > > Kind regards,
> > > 
> > > georg
> > > 
> > > 
> > > _______________________________________________
> > > F-spot-list mailing list
> > > F-spot-list gnome org
> > > http://mail.gnome.org/mailman/listinfo/f-spot-list
> > > 
> > 
> > _______________________________________________
> > F-spot-list mailing list
> > F-spot-list gnome org
> > http://mail.gnome.org/mailman/listinfo/f-spot-list
// HSIImage.cs created with MonoDevelop
// User: georg at 16:47 20.04.2008
//
// To change standard headers go to Edit->Preferences->Coding->Standard Headers
//

using System;
using System.IO;
using Gdk;
using Cms;
using Mono.Unix;

namespace FSpot.Filters
{
	public class HSIImage
	{
		
		const double EPSMIN = 0.0005;
		const double EPSMAX = 0.9995;		

		Gdk.Pixbuf pixImage;	
		HSI [,] hsiImage;
		int rowstride;
		int	width;
		int	height;
		bool alpha;
		double sixty;
		double deg;
		
		//HSI and RGB classes
		public class RGB {
			public int r;
			public int g;
			public int b;
		};

		public class HSI {
			public double h;
			public double s;
			public int i;
		};		
		

		public HSIImage()
		{
			deg = Math.PI / 180.0 ;
			sixty = 60 * deg;
			rowstride = 0;
			width = 0;
			height = 0;
			pixImage = null;	
			hsiImage = null;
			alpha = false;
		}
		
		
		// getter and setter
		public HSI [,] getHSI (){
			return hsiImage;
		}
		
		public void setHSI (HSI [,] src) {
			hsiImage = src;
		}
		
		public int getRowstride () {
			return rowstride;
		}
		
		public int getWidth () {
			return width;
		}
		
		public int getHeight () {
			return height;
		}
		
		//creates an HSI image from give input
		public void createHSIImage (Gdk.Pixbuf src) {
			System.Console.Write ("createHSIImage\n");			
			if (src.BitsPerSample != 8)
				throw new System.Exception ("Invalid bits per sample");
			pixImage = src;
			alpha = src.HasAlpha;
			rowstride = src.Rowstride;
			width = src.Width;
			height = src.Height;
			hsiImage = new HSI [height+1,width+1];
			unsafe {	
				byte * srcb = (byte *)src.Pixels;
				byte * pixels = srcb;	
				RGB input = new RGB();
				for (int j = 0; j <= height; j++) {
					for (int i = 0; i <= width; i++) {
						input.r = (int) srcb [0];
						input.g = (int) srcb [1];
						input.b = (int) srcb [2];
						hsiImage [j,i] = RGBtoHSI (input);
						if (alpha)
							srcb+=4;
						else 
							srcb+=3;
					}
					srcb =  ((byte *) pixels) + j * rowstride;
				}
			}
		}
		
		//creates an RGB image from HSI
		public Gdk.Pixbuf copyToOutPixBuf () {
			System.Console.Write ("copy2Pixbuf\n");
			if (pixImage.BitsPerSample != 8)
				throw new System.Exception ("Invalid bits per sample");
			//FIXME:
			//Performance could be improved
			Gdk.Pixbuf outPixbuf = pixImage.Copy();
			int rowstride = pixImage.Rowstride;
			int width = pixImage.Width;
			int height = pixImage.Height;
			unsafe {	
				byte * srcb = (byte *)outPixbuf.Pixels;
				byte * pixel = srcb;	
				RGB output = new RGB();
				for (int j = 0; j < height+1; j++) {					
					for (int i = 0; i < width+1; i++) {			
						output = HSItoRGB (hsiImage [j,i]);
						pixel[0] = (byte) (output.r);
						pixel[1] = (byte) (output.g);
						pixel[2] = (byte) (output.b);
						if (alpha)
							pixel+=4;
						else
							pixel+=3;						
					}
					pixel =  ((byte *) srcb) + j * rowstride;
				}
			}
			return outPixbuf;
		}
		
		//creates HSI from given RGB Pixel
		private HSI RGBtoHSI (RGB src) {
			HSI result = new HSI();
			double norm = 0;
			double r =(double) src.r;
			double g =(double) src.g;
			double b =(double) src.b;
			double omega = 0;
			double intensity = 0;
			double hue = 0;
			double saturation = 0;

			//Intensity
			intensity = (double) (src.r + src.g + src.b) / (3.0 *255);			
			
			//norm colours
			norm = Math.Sqrt (Math.Pow (r,2) + Math.Pow (g,2) + Math.Pow (b,2));
			r = (double) (src.r / norm);
			g = (double) (src.g / norm);
			b = (double) (src.b / norm);

			//Saturation
			if (src.r + src.g + src.b == 765) {
				saturation = 0;
				hue = 0;
			}
			else {
				double tmp = Math.Min (r, Math.Min(g, b));
				saturation = 1.0 - ((3.0 * tmp)/ (double)(r + g + b));
				if (saturation < EPSMIN) {
					saturation = 0;
				}
				else if (saturation > EPSMAX) {
					saturation = 1;
				}
			}
			if (saturation != 0) {
				omega = 0.5 * ((r-g) + (r-b)) / Math.Sqrt (Math.Pow ((r-g),2) + (r-b)*(g-b)); 
				omega = Math.Acos(omega);
				if (src.b <= src.g) {
					hue = omega;
				}
				else {
					hue = 2 * Math.PI - omega;
				}
			}
			//convert it to degrees
			result.h = (int) Math.Round ((hue * 180.0) / Math.PI);
			result.s = saturation;
			result.i = (int) Math.Round (intensity * 255);
			return result;
		}

		//makes  RGB from given HSI Pixel
		private RGB HSItoRGB (HSI src) {
			RGB result = new RGB();
			//check for white
			if (src.i == 255) {
				result.r = 255; 
				result.g = 255;
				result.b = 255;
			}
			//check for black
			else if (src.i == 0) {
				result.r = 0; 
				result.g = 0;
				result.b = 0;								
			}
			else {
				double intesity = ((double) src.i) / 255.0;
				double r = 0;
				double g = 0;
				double b = 0;
				if (src.h<120) {
					double hi = src.h * deg;					
					b = (intesity * (1 - src.s));
					r = (src.s * Math.Cos (hi)) /  (Math.Cos (sixty - hi));
					r = intesity * (1 + r) ;
					g = intesity * 3 - (b + r);
				}
				else if (src.h < 240 && src.h >= 120) {
					double hi = (src.h * deg) - 2 * sixty;
					r = (intesity * (1 - src.s));		
					g = (src.s * Math.Cos (hi)) / (Math.Cos (sixty - hi));
					g = (1 + g) * intesity;			
	 				b = intesity * 3 - (g + r);
				}
				else { //h >=240
					double hi = (src.h * deg) - 4 * sixty;
					g = (intesity * (1 - src.s));
					b = (src.s * Math.Cos (hi)) / (Math.Cos (sixty - hi));
					b = intesity * (1 + b);
	 				r = intesity * 3 - (g + b);
				}
				result.r = (int) Math.Round(r * 255);
				result.g = (int) Math.Round(g * 255);
				result.b = (int) Math.Round(b * 255);
				if (result.r > 255) {
					result.r = 255;
				}
				if (result.g > 255) {
					result.g = 255;
				}
				if (result.b > 255) {
					result.b = 255;
				}			
			}		
			return result;
		}
		
		//FIXME: test
		public void test (Gdk.Pixbuf src) {
			createHSIImage (src);
			int errorcount = 0;
			Gdk.Pixbuf dest = copyToOutPixBuf ();
			int rowstride = pixImage.Rowstride;
			int width = pixImage.Width;
			int height = pixImage.Height;
			unsafe {	
				byte * destb = (byte *)dest.Pixels;
				byte * srcb = (byte *)pixImage.Pixels;
				byte * srcpixel = srcb;
				byte * destpixel = destb;
				for (int j = 0; j < height+1; j++) {					
					for (int i = 0; i < width+1; i++) {			
						if (srcpixel[0] !=  destpixel [0] || 						    
						    	srcpixel[1] != destpixel [1] ||
						    	srcpixel[2] != destpixel [2]) {
							errorcount++;
							System.Console.WriteLine ("Error");
							System.Console.WriteLine ("Src R " + srcpixel[0] +
							                      " Src G " + srcpixel[1] +
							                      " Src b " + srcpixel[2]
							                      );
							System.Console.WriteLine ("Dest R " + destpixel[0] +
							                      " Dest G " + destpixel[1] +
							                      " Dest b " + destpixel[2]
							                      );
							System.Console.WriteLine ("Errorcount " + errorcount);
							RGB testRGB = new RGB();
							testRGB.r = srcpixel[0];
							testRGB.g = srcpixel[1];
							testRGB.b = srcpixel[2];
							HSI testHSI = RGBtoHSI (testRGB);
							System.Console.WriteLine ("HSI H " + testHSI.i +
							                      " S " + testHSI.s +
							                      " I " + testHSI.i
							                      );
						}
						if (alpha) {
							srcpixel+=4;
							destpixel+=4;
						}
						else {
							srcpixel+=3;
							destpixel+=3;
						}					
					}
					srcpixel =  ((byte *) srcb) + j * rowstride;
					destpixel =  ((byte *) destb) + j * rowstride;
				}
			}			
		}
	}
}
// HistogramEqualizer.cs created with MonoDevelop
// User: georg at 15:57 14.04.2008
//
// To change standard headers go to Edit->Preferences->Coding->Standard Headers
//

using System;
using System.IO;
using Gdk;
using Cms;
using Mono.Unix;


namespace FSpot.Filters
{
	public class HistogramEqualizer : IFilter
	{
		
		//Hsi stuff
		HSIImage.HSI [,] hsiImage;
		
		//hsi histogramm
		int [] hsiHistogram;
		int [] transformationTable; 
		int rmin;
		int rmax;
		int binCount;
		HSIImage aImage;
		
		public HistogramEqualizer()
		{
			rmin = 0;
			rmax = 0;
			binCount = 0;
			hsiImage = null;
			hsiHistogram = null;			
			transformationTable = null;
			aImage = null;
		}
		
		public bool Convert (FilterRequest req) {
			Gdk.Pixbuf pixImage;
			aImage = new HSIImage ();
			Uri dest_uri = req.TempUri (System.IO.Path.GetExtension (req.Current.LocalPath));
			using (ImageFile img = ImageFile.Create (req.Current)) {
				using (pixImage = img.Load ()) {
					string destination_extension = Path.GetExtension (dest_uri.LocalPath);			
					aImage.createHSIImage (pixImage);
					hsiImage = aImage.getHSI ();
					createHSIHistogram ();					
					createTranslationTable ();
					transformImage ();
					aImage.setHSI (hsiImage);
					Gdk.Pixbuf outPixbuf = aImage.copyToOutPixBuf ();	
					
					if (Path.GetExtension (req.Current.LocalPath).ToLower () == Path.GetExtension (dest_uri.LocalPath).ToLower ()) {
						using (Stream output = File.OpenWrite (dest_uri.LocalPath)) {
							img.Save (outPixbuf, output);
						}
					} else if (destination_extension == ".jpg") {
						Exif.ExifData exif_data;
						exif_data = new Exif.ExifData (req.Current.LocalPath);
						PixbufUtils.SaveJpeg (outPixbuf, dest_uri.LocalPath, 90, exif_data);
					} else 
						throw new NotImplementedException (String.Format (Catalog.GetString ("No way to save files of type \"{0}\""), destination_extension));
				}
			}
			req.Current = dest_uri;
			return true;
		}
		
		private void createHSIHistogram () {
			if (hsiImage == null)
				throw new System.Exception ("no Input found");
			hsiHistogram = new int [256];
			rmin = 255;
			rmax = 0;
			binCount = 0;
			int intensityValue = 0;
			int height = aImage.getHeight();
			int width = aImage.getWidth ();
			for (int i = 0; i< height; i++) {
				for (int j = 0; j < width; j++) {
					intensityValue =  hsiImage[i,j].i;
					rmin = Math.Min (intensityValue, rmin);
					rmax = Math.Max (intensityValue, rmax);
					if  (hsiHistogram [intensityValue] == 0) {
						binCount ++;
					}
					hsiHistogram [intensityValue] ++;
				}
			}
		}
		
		private void createTranslationTable () {
			if (hsiHistogram == null)
				throw new System.Exception ("histogram not initialized");
			int scaler = (rmax - rmin);
			double allPixels = aImage.getHeight () * aImage.getWidth ();
			transformationTable = new int [256];
			double sum = 0;
			double tmp = 0;
			for (int i = 0; i <= 255; ++i) {
				tmp += (hsiHistogram [i] / allPixels) * scaler;
				transformationTable [i] = (int) (tmp + rmin);
			}			
		}
		
		private void transformImage () {			
			if (hsiHistogram == null)
				throw new System.Exception ("no Transformationtable");			
			int height = aImage.getHeight();
			int width = aImage.getWidth ();
			for (int i = 0; i<= height; ++i) {
				for (int j = 0; j <= width; ++j) {
					hsiImage[i,j].i = (int) ((transformationTable [hsiImage[i,j].i]));
				}
			}
			
		}	
	}
}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]