My 30 to launch app – Camera Hack

I’ve already let the blogging slip, I’ve been preoccupied with getting my app, Camera Hack, done by the deadlines in the http://www.30tolaunch.com/ contest. The first deadline was Feb 13, where my goal was to be one of the first 500 to submit a partially completed app with fast application switching enabled. The result of meeting this goal was a shiny new Nokia Lumia 800 at my door 3 days ago.

Lumia side note: it’s a nice looking phone, but it’s heavier than my Samsung Focus S, almost the same size but with a noticeably smaller screen, and lacks a front facing camera. It also has a Micro SIM which means I can’t even drop my current SIM into it to see how the phone/cell data part works, so it’s a little disappointing. But it has been nice for testing as I’ve been able to see what happens when using my app on a phone without a front facing camera.

Deadline number two was to actually complete my app and get it approved by the end of the contest on March 15. At this point I have “finished” my app and I have submitted it to the Marketplace for approval. It has been taking around 3 days to get through Marketplace testing lately and three of the four apps I have submitted were approved on the first try, so I’m hopeful that this submission will go smoothly. Of course there is always the possibility that because this is my first app that is using the camera and integrated into the Photo Hub of the phone that I may have missed some rules that I was supposed to follow.

Ok, that’s the boring stuff, lets talk about the app.

Camera Hack

Camera Hack Screen Shot

The concept is pretty simple, show a preview of the raw camera feed on the left, show a filtered view in real-time on the right. Have a list of filters to choose from on the bottom. Put some options on the side bar. Use the hardware camera button built into every Windows Phone (great feature btw) to snap the picture and save it to the image library so it can easily be shared using the social services built into Windows Phone (also a great feature).

Microsoft has great sample code and articles on how to work with the camera and get all the functionality working from using the flash, to switching between the Primary and Rear cameras (http://msdn.microsoft.com/en-us/library/ff431744%28VS.92%29.aspx#BKMK_CameraAndPhotos), so I wasn’t too concerned with getting those parts working. What I started with was how do you create those interesting effects that make the app worth using. And create them in a way that is both understandable and not crazy slow (we need real time preview here after all and we need to keep the processing time of 7MP photos to a minimum or no one will want to use the app.)

Plus I didn’t really know what would make a good effect. How much do you change the contrast, what color do you tint it to, what other values should you change? So I went to Google for guidance and found a JavaScript library that was perfect. Alex Michael wrote this cool library called Filtrr that can take an image on a page, apply a bunch of filters to it and dump it back into a canvas element. You can see it work here: http://alexmic.github.com/filtrr/. (Alex has been working on a second better version since I first started this project, so that link may fail at some point. Check out his site through his name for updates if it doesn’t work.)

Obviously, I wasn’t going to use JavaScript in my app, so some porting to C# was necessary, but his code gave me a big jump start. In the interest of simplicity and faster running code, I left out some things like blurs and blending and focused primary on things that changed one pixel at a time.

Here is my version (please forgive my absolute lack of following even my own naming conventions, never really did go through and clean it up properly):

using System;

public class Filtr
{
		public static double safe(double i)
		{
				i = i <= 255 ? i : 255;
				i = i >= 0 ? i : 0;
				return i;
		}

		public static rbg Adjust(rbg Rbg, Double Rs, Double Gs, Double Bs)
		{
				Rbg.r = Filtr.safe(Rbg.r * (1 + Rs));
				Rbg.g = Filtr.safe(Rbg.g * (1 + Gs));
				Rbg.b = Filtr.safe(Rbg.b * (1 + Bs));

				return Rbg;
		}

		public static rbg Brightness(rbg Rbg, Double T)
		{
				Rbg.r = Filtr.safe(Rbg.r + T);
				Rbg.g = Filtr.safe(Rbg.g + T);
				Rbg.b = Filtr.safe(Rbg.b + T);

				return Rbg;
		}

		public static rbg Fill(rbg Rbg, Double Rf, Double Gf, Double Bf)
		{
				Rbg.r = Filtr.safe(Rf);
				Rbg.g = Filtr.safe(Gf);
				Rbg.b = Filtr.safe(Bf);

				return Rbg;
		}

		public static rbg Opacity(rbg Rbg, Double O)
		{
				Rbg.a = Filtr.safe(Rbg.a * O);

				return Rbg;
		}

		public static rbg Saturation(rbg Rbg, Double T)
		{
				var avg = (Rbg.r + Rbg.g + Rbg.b) / 3;

				Rbg.r = Filtr.safe(Rbg.r + T * (Rbg.r - avg));
				Rbg.g = Filtr.safe(Rbg.g + T * (Rbg.g - avg));
				Rbg.b = Filtr.safe(Rbg.b + T * (Rbg.b - avg));

				return Rbg;
		}

		public static rbg Threshold(rbg Rbg, Double T)
		{
				var c = 255;
				if (Rbg.r < T || Rbg.g < T || Rbg.b < T)
				{
						c = 0;
				}

				Rbg.r = c;
				Rbg.g = c;
				Rbg.b = c;

				return Rbg;
		}

		public static rbg Posterize(rbg Rbg, Double levels)
		{
				var step = Math.Floor(255 / levels);

				Rbg.r = Filtr.safe(Math.Floor(Rbg.r / step) * step);
				Rbg.g = Filtr.safe(Math.Floor(Rbg.g / step) * step);
				Rbg.b = Filtr.safe(Math.Floor(Rbg.b / step) * step);
				
				return Rbg;
		}

		public static rbg Gamma(rbg Rbg, Double value)
		{
				Rbg.r = Filtr.safe(Math.Pow(Rbg.r, value));
				Rbg.g = Filtr.safe(Math.Pow(Rbg.g, value));
				Rbg.b = Filtr.safe(Math.Pow(Rbg.b, value));
				
				return Rbg;
		}

		public static rbg Negative(rbg Rbg)
		{
				Rbg.r = Filtr.safe(255 - Rbg.r);
				Rbg.g = Filtr.safe(255 - Rbg.g);
				Rbg.b = Filtr.safe(255 - Rbg.b);
				
				return Rbg;
		}

		public static rbg GrayScale(rbg Rbg)
		{
				var avg = (Rbg.r + Rbg.g + Rbg.b) / 3;

				Rbg.r = Filtr.safe(avg);
				Rbg.g = Filtr.safe(avg);
				Rbg.b = Filtr.safe(avg);
				
				return Rbg;
		}

		public static rbg Tint(rbg Rbg, rbg minRbg, rbg maxRbg)
		{
				Rbg.r = Filtr.safe((Rbg.r - minRbg.r) * (255 / (maxRbg.r - minRbg.r)));
				Rbg.g = Filtr.safe((Rbg.g - minRbg.g) * (255 / (maxRbg.g - minRbg.g)));
				Rbg.b = Filtr.safe((Rbg.b - minRbg.b) * (255 / (maxRbg.b - minRbg.b)));
				
				return Rbg;
		}

		public static rbg Mask(rbg Rbg, rbg mRbg)
		{
				Rbg.r = Filtr.safe(((int)Rbg.r & (int)mRbg.r));
				Rbg.g = Filtr.safe(((int)Rbg.g & (int)mRbg.g));
				Rbg.b = Filtr.safe(((int)Rbg.b & (int)mRbg.b));
				
				return Rbg;
		}

		public static rbg Sepia(rbg Rbg)
		{
				Rbg.r = Filtr.safe((Rbg.r * 0.393) + (Rbg.g * 0.769) + (Rbg.b * 0.189));
				Rbg.g = Filtr.safe((Rbg.r * 0.349) + (Rbg.g * 0.686) + (Rbg.b * 0.168));
				Rbg.b = Filtr.safe((Rbg.r * 0.272) + (Rbg.g * 0.534) + (Rbg.b * 0.131));
				
				return Rbg;
		}

		public static rbg Bias(rbg Rbg, Double val)
		{
				Rbg.r = Filtr.safe(Rbg.r * (Rbg.r / 255 / ((1 / val - 1.9) * (.9 - Rbg.r / 255) + 1)));
				Rbg.g = Filtr.safe(Rbg.g * (Rbg.g / 255 / ((1 / val - 1.9) * (.9 - Rbg.g / 255) + 1)));
				Rbg.b = Filtr.safe(Rbg.b * (Rbg.b / 255 / ((1 / val - 1.9) * (.9 - Rbg.b / 255) + 1)));
				
				return Rbg;
		}

		public static rbg Contrast(rbg Rbg, Double val)
		{
				Rbg.r = Filtr.safe(255 * (((Rbg.r / 255) - .5) * val + .5));
				Rbg.g = Filtr.safe(255 * (((Rbg.g / 255) - .5) * val + .5));
				Rbg.b = Filtr.safe(255 * (((Rbg.b / 255) - .5) * val + .5));
				
				return Rbg;
		}
}

public class rbg
{
		public double r { get; set; }
		public double b { get; set; }
		public double g { get; set; }
		public double a { get; set; }

		public rbg()
		{

		}

		public rbg(int color)
		{
				a = color >> 24;
				r = (color & 0x00ff0000) >> 16;
				g = (color & 0x0000ff00) >> 8;
				b = (color & 0x000000ff);
		}

		public rbg(double R, double B, double G, double A)
		{
				a = A;
				r = R;
				g = G;
				b = B;
		}

		public void ConvertFromInt(int color)
		{
				a = color >> 24;
				r = (color & 0x00ff0000) >> 16;
				g = (color & 0x0000ff00) >> 8;
				b = (color & 0x000000ff);
		}

		public int ConvertToInt()
		{
				return ((int)a << 24) | ((int)r << 16) | ((int)g << 8) | (int)b;
		}
}

These classes make it nice and easy to loop through every pixel from a writable bitmap, perform one or more transformations to it and end up with a final transformed image.

var bitmap = (WriteableBitmap)(e.Argument);
var inRBG = new rbg();
var minTint1 = new rbg();
minTint1.r = 60;
minTint1.g = 35;
minTint1.b = 10;
var maxTint1 = new rbg();
maxTint1.r = 170;
maxTint1.g = 140;
maxTint1.b = 160;

for (int i = 0; i < bitmap.Pixels.Length; i++)
{
		inRBG.ConvertFromInt(bitmap.Pixels[i]);
		bitmap.Pixels[i] = Filtr.Brightness(Filtr.Contrast(Filtr.Tint(inRbg, minTint1, maxTint1), 1), 10);
}

In future posts I’ll cover how I worked with background threads, adding an about page, how I handled my app bar when not all phones will support both cameras and not all cameras have a flash, and anything else I can come up with that might be interesting.

Advertisement

2 comments

Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: