Re: rgb -> hsv and back
- From: Georg Leugner <piece gmx net>
- To: Larry Ewing <lewing novell com>
- Cc: f-spot-list gnome org
- Subject: Re: rgb -> hsv and back
- Date: Mon, 21 Apr 2008 16:32:44 +0200
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]