A new free version of SC2 loopback is now available for iOS.
- The full version has more channels available and no iAd banners
For a recent project I needed to save a UIWebView to an image. Including not just the visible area but the whole scrollable view. There are a number of example for creating a PDF from a webview, but I couldn’t find many example of converting to an Image.
So below is a simple function to get it done. Includes some javascript calls to scroll the webview. This assumes the webview is created and visible to the user – again other example used a temporary uiwebview to render html content off screen.
- (UIImage*)webviewToImage:(UIWebView*)theWebView { int webViewHeight = [[theWebView stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight;"] integerValue]; int scrollByY = theWebView.frame.size.height; int imageName = 0; [theWebView stringByEvaluatingJavaScriptFromString:@"window.scrollTo(0,0);"]; NSMutableArray* images = [[NSMutableArray alloc] init]; CGRect screenRect = theWebView.frame; double currentWebViewHeight = webViewHeight; while (currentWebViewHeight > 0) { imageName ++; UIGraphicsBeginImageContext(screenRect.size); CGContextRef ctx = UIGraphicsGetCurrentContext(); [[UIColor blackColor] set]; CGContextFillRect(ctx, screenRect); [theWebView.layer renderInContext:ctx]; UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); if(currentWebViewHeight < scrollByY) { CGRect lastImageRect = CGRectMake(0, scrollByY - currentWebViewHeight, theWebView.frame.size.width, currentWebViewHeight); CGImageRef imageRef = CGImageCreateWithImageInRect([newImage CGImage], lastImageRect); newImage = [UIImage imageWithCGImage:imageRef]; CGImageRelease(imageRef); } [images addObject:newImage]; [theWebView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"window.scrollBy(0,%d);", scrollByY]]; currentWebViewHeight -= scrollByY; } [theWebView stringByEvaluatingJavaScriptFromString:@"window.scrollTo(0,0);"]; UIImage *resultImage; if(images.count > 1) { //join all images together.. CGSize sz; for(int i=0;i<images.count;i++) { sz.width = MAX(sz.width, ((UIImage*)[images objectAtIndex:i]).size.width ); sz.height += ((UIImage*)[images objectAtIndex:i]).size.height; } UIGraphicsBeginImageContext(sz); CGContextRef ctx = UIGraphicsGetCurrentContext(); [[UIColor blackColor] set]; CGContextFillRect(ctx, screenRect); int y=0; for(int i=0;i<images.count;i++) { UIImage* img = [images objectAtIndex:i]; [img drawAtPoint:CGPointMake(0,y)]; y += img.size.height; } resultImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); } else { resultImage = [images objectAtIndex:0]; } return resultImage; }
To kick off the new year SC2 Loopback is free for download for one week!
http://www.sc2loopback.com
Grab your copy now and enjoy the latest star craft videos on your iOS devices.
Some cool designs on the age old playing cards. Check them out, makes a great xmas idea. More apps and poker related software is in development so stay tuned!

Just released a new app in the app store. A small iOS app for watching starcraft replays and videos.
http://ww.sc2loopback.com/
Its simple app that uses some external api’s and developed with xcode, supporting iOS 4.2 on iPhones and iPads. I’ll be releasing an Android version soon and maybe a windows desktop and metro version as well.


As described here
http://forums.silverlight.net/t/119338.aspx/1
Silverlight 5 (and 4) has a bug with it’s slider control. You can not bind to the Maximum and Minimum properties of the control. To get around this I have a simple solution with the code below. Basically just inherit from Slider and add two custom dependency properties and register for the on-changed events. In the callback you can manually set the minimum and maximum values of the slider instead of bindings.
This is a pain but seems to be a easy and clean solutions.
public class CustomSlider : Slider { public static readonly DependencyProperty MinimumBugFixProperty = DependencyProperty.Register("MinimumBugFix", typeof(double), typeof(CustomSlider), new FrameworkPropertyMetadata(0, new PropertyChangedCallback(OnUpdateSliderRange))); public static readonly DependencyProperty MaximumBugFixProperty = DependencyProperty.Register("MaximumBugFix", typeof(double), typeof(CustomSlider), new FrameworkPropertyMetadata(100, new PropertyChangedCallback(OnUpdateSliderRange))); public CustomSlider() { } public double MinimumBugFix { get { return (double)GetValue(MinimumBugFixProperty); } set { SetValue(MinimumBugFixProperty, value); } } public double MaximumBugFix { get { return (double)GetValue(MaximumBugFixProperty); } set { SetValue(MaximumBugFixProperty, value); } } private static void OnUpdateSliderRange(DependencyObject d, DependencyPropertyChangedEventArgs e) { CustomSlider slider = d as CustomSlider; if (slider != null) { if(slider.Maximum != slider.MaximumBugFix) slider.Maximum = slider.MaximumBugFix; if(slider.Minimum != slider.MinimumBugFix) slider.Minimum = slider.MinimumBugFix; } } }
My biggest WTF moment so far with silverlight.. there is no Visibility.Hidden enum value.
So much for reusing all those valueconverters.. oh the pain!
namespace System.Windows { // Summary: // Specifies the display state of an element. public enum Visibility { // Summary: // Display the element. Visible = 0, // // Summary: // Do not display the element, and do not reserve space for it in layout. Collapsed = 1, } }
The code section below is an example of how to calculate the dominant color in a bitmap. The code uses euclidean distance calculations for each color channel in each pixel. Then it sorts the pixels to find the color which is the closest to all other colors.
To improve the output I take to top %2 of colors and average them. Also the Alpha channel is treated differently though it could be changed to use the same distance logic.
The results are much better than the average color calculation. Though it is more computationally expensive so it will take longer to complete.
<code> public static Color CalculateDominantColor(BitmapSource source) { if (source.Format.BitsPerPixel != 32 || source.Format != PixelFormats.Bgra32) throw new ApplicationException("expected 32bit image"); Dictionary<Color, double> colorDist = new Dictionary<Color, double>(); System.Windows.Size sz = new System.Windows.Size(source.PixelWidth, source.PixelHeight); //read bitmap int pixelsSz = (int)sz.Width * (int)sz.Height * (source.Format.BitsPerPixel / 8); int stride = ((int)sz.Width * source.Format.BitsPerPixel + 7) / 8; int pixelBytes = (source.Format.BitsPerPixel / 8); byte[] pixels = new byte[pixelsSz]; source.CopyPixels(pixels, stride, 0); const int alphaThershold = 10; UInt64 pixelCount = 0; UInt64 avgAlpha = 0; for (int y = 0; y < sz.Height; y++) { for (int x = 0; x < sz.Width; x++) { int index = (int)((y * sz.Width) + x) * (pixelBytes); byte r1, g1, b1, a1; r1 = g1 = b1 = a1 = 0; a1 = pixels[index + 3]; r1 = pixels[index + 2]; g1 = pixels[index + 1]; b1 = pixels[index]; if (a1 <= alphaThershold) continue; //ignore pixelCount++; avgAlpha += (UInt64)a1; Color cl = Color.FromArgb(0, r1, g1, b1); double dist = 0; if (!colorDist.ContainsKey(cl)) { colorDist.Add(cl, 0); for (int y2 = 0; y2 < sz.Height; y2++) { for (int x2 = 0; x2 < sz.Width; x2++) { int index2 = (int)(y2 * sz.Width) + x2; byte r2, g2, b2, a2; r2 = g2 = b2 = a2 = 0; a2 = pixels[index2 + 3]; r2 = pixels[index2 + 2]; g2 = pixels[index2 + 1]; b2 = pixels[index2]; if (a2 <= alphaThershold) continue; //ignore dist += Math.Sqrt(Math.Pow(r2 - r1, 2) + Math.Pow(g2 - g1, 2) + Math.Pow(b2 - b1, 2)); } } colorDist[cl] = dist; } } } //clamp alpha avgAlpha = avgAlpha / pixelCount; if (avgAlpha >= (255 - alphaThershold)) avgAlpha = 255; //take weighted average of top 2% of colors var clrs = (from entry in colorDist orderby entry.Value ascending select new { Color = entry.Key, Dist = 1.0/Math.Max(1,entry.Value) }).ToList().Take( Math.Max(1, (int)(colorDist.Count * 0.02)) ); double sumDist = clrs.Sum(x => x.Dist); Color result = Color.FromArgb((byte)avgAlpha, (byte)(clrs.Sum(x => x.Color.R * x.Dist) / sumDist), (byte)(clrs.Sum(x => x.Color.G * x.Dist) / sumDist), (byte)(clrs.Sum(x => x.Color.B * x.Dist) / sumDist)); return result; } </code>
Here is an example of a really easy way to resize a bitmap using WPF imaging classes.
<code> public static BitmapSource ResizeBitmap(BitmapSource source, int nWidth, int nHeight) { TransformedBitmap tbBitmap = new TransformedBitmap(source, new ScaleTransform(nWidth / source.PixelWidth, nHeight / source.PixelHeight, 0, 0)); return tbBitmap; } </code>
Here is a simple method to calculate the average color in a bitmap (WPF C#). The result is not always the best, it can tend to be muddy or more like a grey-scale effect. An improved method is to find the dominant color, though more computationally expensive – I will post an example soon.
<code> public static Color CalculateAverageColor(BitmapSource source) { if (source.Format.BitsPerPixel != 32) throw new ApplicationException("expected 32bit image"); Color cl; System.Windows.Size sz = new System.Windows.Size(source.PixelWidth, source.PixelHeight); //read bitmap int pixelsSz = (int)sz.Width * (int)sz.Height * (source.Format.BitsPerPixel / 8); int stride = ((int)sz.Width * source.Format.BitsPerPixel + 7) / 8; byte[] pixels = new byte[pixelsSz]; source.CopyPixels(pixels, stride, 0); //find the average color for the image const int alphaThershold = 10; UInt64 r, g, b, a; r = g = b = a = 0; UInt64 pixelCount = 0; for (int y = 0; y < sz.Height; y++) { for (int x = 0; x < sz.Width; x++) { int index = (int)((y * sz.Width) + x) * 4; if (pixels[index + 3] <= alphaThershold) //ignore transparent continue; pixelCount++; a += pixels[index + 3]; r += pixels[index + 2]; g += pixels[index + 1]; b += pixels[index]; } } //average color result cl = Color.FromArgb((int)(a / pixelCount), (int)(r / pixelCount), (int)(g / pixelCount), (int)(b / pixelCount)); return cl; } </code>