SkinToneEffect

Mar 18, 2012 at 12:48 AM
Edited Mar 18, 2012 at 12:51 AM
public class SkinToneEffect : IEffect
   {
       public YCbCrColor LowerThreshold { get; set; }
       public YCbCrColor UpperThreshold { get; set; }

       public Color Color { get; set; }
       public float Amout { get; set; }

       public string Name { get { return "Tone"; } }


       public SkinToneEffect(Color c, float a)
       {
           LowerThreshold = YCbCrColor.Min;
           UpperThreshold = YCbCrColor.Max;

           Color = c;
           Amout = a;
       }

      /// <summary>
      /// Processes a bitmap and returns a new processed WriteabelBitmap.
      /// </summary>
      /// <param name="input">The input bitmap.</param>
      /// <returns>The result of WriteabelBitmap processing.</returns>
      public WriteableBitmap Process(WriteableBitmap input)
      {
         // Prepare some variables
         var width = input.PixelWidth;
         var height = input.PixelHeight;
         return Process(input.Pixels, width, height).ToWriteableBitmap(width, height);
      }

      /// <summary>
      /// Processes an ARGB32 integer bitmap and returns the new processed bitmap data.
      /// </summary>
      /// <param name="inputPixels">The input bitmap as integer array.</param>
      /// <param name="width">The width of the bitmap.</param>
      /// <param name="height">The height of the bitmap.</param>
      /// <returns>The result of the processing.</returns>
      public int[] Process(int[] inputPixels, int width, int height)
      {
          var resultPixels = new int[inputPixels.Length];

          // Threshold every pixel
          for (int i = 0; i < inputPixels.Length; i++)
          {
              int c = inputPixels[i];

              var ycbcr = YCbCrColor.FromArgbColori(c);
              if (ycbcr.Y >= LowerThreshold.Y && ycbcr.Y <= UpperThreshold.Y
               && ycbcr.Cb >= LowerThreshold.Cb && ycbcr.Cb <= UpperThreshold.Cb
               && ycbcr.Cr >= LowerThreshold.Cr && ycbcr.Cr <= UpperThreshold.Cr)
              {
                  // skin tone match 
                  System.Windows.Media.Color sc = System.Windows.Media.Color.FromArgb((byte)(c >> 24), (byte)(c >> 16), (byte)(c >> 8), (byte)c);

                  Microsoft.Xna.Framework.Color xc = new Microsoft.Xna.Framework.Color(sc.R, sc.G, sc.B, sc.A);
                  xc = Color.Lerp(xc, Color, Amout);

                  c = (255 << 24) | ((byte)(xc.R > 255 ? 255 : xc.R) << 16) | ((byte)(xc.G > 255 ? 255 : xc.G) << 8) | (byte)(xc.B > 255 ? 255 : xc.B);
              }
              
              resultPixels[i] = c;
          }

          return resultPixels;
      }
   }
/// <summary>
    /// YCbCr color representation.
    /// Y in the reange [0, 1].
    /// Cb in the reange [-0.5, 0.5].
    /// Cr in the reange [-0.5, 0.5].
    /// According to http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html#RTFToC28
    /// </summary>
    public class YCbCrColor
    {
        public static YCbCrColor Min { get { return new YCbCrColor(0, -0.5f, -0.5f); } }
        public static YCbCrColor Max { get { return new YCbCrColor(1, 0.5f, 0.5f); } }

        public double Y { get; set; }
        public double Cb { get; set; }
        public double Cr { get; set; }

        public YCbCrColor(double y, double cb, double cr)
        {
            Y = y;
            Cb = cb;
            Cr = cr;
        }

        public void Modify(double brightness, double blue, double red)
        {
            this.Y = Math.Min(Math.Max(this.Y + brightness, 0), 1);
            this.Cb = Math.Min(Math.Max(this.Cb + blue, -0.5), 0.5);
            this.Cr = Math.Min(Math.Max(this.Cr + red, -0.5), 0.5);
        }

        public Color ToArgbColor()
        {
            int c = ToArgbColori();
            return Color.FromArgb((byte)(c >> 24), (byte)(c >> 16), (byte)(c >> 8), (byte)c);
        }

        public int ToArgbColori()
        {
            // Convert to RGB
            var r = (Y + 1.402f * Cr) * 255;
            var g = (Y - 0.344136f * Cb - 0.714136f * Cr) * 255;
            var b = (Y + 1.772f * Cb) * 255;

            return (255 << 24) | ((byte)(r > 255 ? 255 : r) << 16) | ((byte)(g > 255 ? 255 : g) << 8) | (byte)(b > 255 ? 255 : b);
        }

        public YCbCrColor Interpolate(YCbCrColor c2, float amount)
        {
            return new YCbCrColor(Y + (c2.Y - Y) * amount,
                                    Cb + (c2.Cb - Cb) * amount,
                                    Cr + (c2.Cr - Cr) * amount);
        }

        public static YCbCrColor FromRgb(byte r, byte g, byte b)
        {
            // Create new YCbCr color from rgb color
            const float f = 1f / 255f;
            var rf = r * f;
            var gf = g * f;
            var bf = b * f;

            var y = 0.299f * rf + 0.587f * gf + 0.114f * bf;
            var cb = -0.168736f * rf + -0.331264f * gf + 0.5f * bf;
            var cr = 0.5f * rf + -0.418688f * gf + -0.081312f * bf;

            return new YCbCrColor(y, cb, cr);
        }

        public static YCbCrColor FromArgbColor(Color color)
        {
            return FromRgb(color.R, color.G, color.B);
        }

        public static YCbCrColor FromArgbColori(int color)
        {
            return FromRgb((byte)(color >> 16), (byte)(color >> 8), (byte)(color));
        }
    }
Most of it is Rene's code (mix and match :)) Sorry Rene
Based on my post at http://wp.me/p1FGGY-8E