<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Safe from the Losing Fight &#187; Core Image</title>
	<atom:link href="http://losingfight.com/blog/category/core-image/feed/" rel="self" type="application/rss+xml" />
	<link>http://losingfight.com/blog</link>
	<description>making matt mullenweg regret his role as an enabler</description>
	<lastBuildDate>Fri, 09 Apr 2010 15:14:34 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
		<atom:link rel='hub' href='http://losingfight.com/blog/?pushpress=hub'/>
		<item>
		<title>How to implement sharpen and blur tools</title>
		<link>http://losingfight.com/blog/2009/09/17/how-to-implement-sharpen-and-blur-tools/</link>
		<comments>http://losingfight.com/blog/2009/09/17/how-to-implement-sharpen-and-blur-tools/#comments</comments>
		<pubDate>Fri, 18 Sep 2009 05:53:48 +0000</pubDate>
		<dc:creator>andy</dc:creator>
				<category><![CDATA[Core Image]]></category>
		<category><![CDATA[Macintosh]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://losingfight.com/blog/?p=212</guid>
		<description><![CDATA[The sharpen and blur tools are simple bitmap tools. They allow you to blur or sharpen parts of an image using a brush metaphor. In addition to being able to control the strength of the sharpen or blur, these tools also typically allow you to control the blend mode when applying the effect.
The sharpen tool [...]]]></description>
			<content:encoded><![CDATA[<p>The sharpen and blur tools are simple bitmap tools. They allow you to blur or sharpen parts of an image using a brush metaphor. In addition to being able to control the strength of the sharpen or blur, these tools also typically allow you to control the blend mode when applying the effect.</p>
<p>The sharpen tool has the following effect on this tweed pattern:</p>
<p><img src="/blog/wp-content/uploads/2009/09/TweedSharpen50.png" alt="Tweed pattern with sharpen applied" width="200" height="200" /></p>
<p>Notice the stripe through the middle where the edges are more strongly defined.</p>
<p>The blur tool has the opposite effect of sharpen on the tweed pattern:</p>
<p><img src="/blog/wp-content/uploads/2009/09/TweedBlur50.png" alt="Tweed pattern with blur applied" width="200" height="200" /></p>
<h2>Implementation Overview</h2>
<p>Like the <a href="/blog/2009/09/16/how-to-implement-a-sponge-tool/">sponge tool</a>, the sharpen and blur tools are subclasses of the <var>FilterBrush</var> class introduced in the <a href="/blog/2007/09/26/how-to-implement-dodge-and-burn-tools/">dodge and burn tutorial</a>. However, unlike sponge, they do require a slight modification to the <var>GenericBrush</var> class to work.</p>
<p>Since almost all of the code in this tutorial has been covered before, I&#8217;ll just highlight the single change to the <var>GenericBrush</var> class, and the new <var>Sharpen</var> and <var>Blur</var> classes. If you want to see everything, <a href="/blog/wp-content/uploads/2009/09/SharpenBlur.zip" title="Sharpen and blur sample code">download the code</a>.</p>
<h2>Changing GenericBrush</h2>
<p>If you recall, at the very beginning of this article I mentioned that sharpen and blur tools typically allow the user to modify the blend mode. This is the blend mode used when stamping the filtered image back on to the original image. <var>GenericBrush</var> contains the code that handles the stamping, so it will need to be modified.</p>
<p>We change the <var>render:at:</var> method for <var>GenericBrush</var> to be:</p>
<p><code>
<pre>
- (void) render:(Canvas *)canvas at:(NSPoint)point
{
	CGContextRef canvasContext = [canvas context];
	CGContextSaveGState(canvasContext);

	CGPoint bottomLeft = CGPointMake( point.x - CGImageGetWidth(mMask) * 0.5, point.y - CGImageGetHeight(mMask) * 0.5 );

	// Our brush has a shape and soft edges. These are replicated by using our
	//	brush tip as a mask to clip to. No matter what we render after this,
	//	it will be in the proper shape of our brush.
	CGRect brushBounds = CGRectMake(bottomLeft.x, bottomLeft.y, CGImageGetWidth(mMask), CGImageGetHeight(mMask));

	CGContextClipToMask(canvasContext, brushBounds, mMask);
	CGContextSetBlendMode(canvasContext, [self blendMode]);
	[self renderInCanvas:canvas bounds:brushBounds at:point];

	CGContextRestoreGState(canvasContext);
}
</pre>
<p></code></p>
<p>The only change made is the call to <var>CGContextSetBlendMode</var> that allows brush subclasses to determine the blend mode to use. The default implementation is simply:</p>
<p><code>
<pre>
- (CGBlendMode) blendMode
{
	return kCGBlendModeNormal;
}
</pre>
<p></code></p>
<p>That&#8217;s it for <var>GenericBrush</var>.</p>
<h2>Sharpen Tool</h2>
<p>The sharpen tool has just two parameters: <var>mMode</var>, which is the blend mode, and <var>mStrength</var> which determines how strong the sharpen effect is applied. They are initialized in <var>init</var> like so:</p>
<p><code>
<pre>
- (id) init
{
	self = [super init];
	if ( self != nil ) {
		// Set the default values for our parameters
		mMode = kCGBlendModeDarken;
		mStrength = 1.0;
	}
	return self;
}
</pre>
<p></code></p>
<p>Here are some examples of the sharpen tool parameters:</p>
<table>
<tr>
<th>mMode</th>
<th>mStrength</th>
<th>Result</th>
</tr>
<tr>
<td>kCGBlendModeNormal</td>
<td>0.5</td>
<td><img src="/blog/wp-content/uploads/2009/09/TweedSharpenNormal50.png" alt="Tweed with sharpen, normal blend mode, 50%" border="0" width="200" height="200" /></td>
</tr>
<tr>
<td>kCGBlendModeNormal</td>
<td>1.0</td>
<td><img src="/blog/wp-content/uploads/2009/09/TweedSharpenNormal100.png" alt="Tweed with sharpen, normal blend mode, 100%" border="0" width="200" height="200" /></td>
</tr>
<tr>
<td>kCGBlendModeDarken</td>
<td>1.0</td>
<td><img src="/blog/wp-content/uploads/2009/09/TweedSharpenDarken100.png" alt="Tweed with sharpen, darken blend mode, 100%" border="0" width="200" height="200" /></td>
</tr>
<tr>
<td>kCGBlendModeLighten</td>
<td>1.0</td>
<td><img src="/blog/wp-content/uploads/2009/09/TweedSharpenLighten100.png" alt="Tweed with sharpen, lighten blend mode, 100%" border="0" width="200" height="200" /></td>
</tr>
</table>
<p>The blend modes typically offered for a sharpen tool are kCGBlendModeNormal, kCGBlendModeDarken, kCGBlendModeLighten, kCGBlendModeHue, kCGBlendModeSaturation, kCGBlendModeColor, and kCGBlendModeLuminosity. In the examples above, kCGBlendModeDarken causes the sharpening to only affect light pixels and kCGBlendModeLighten only affects dark pixels.</p>
<p>The heart of the sharpen tool is the filter it creates. The code for that is:</p>
<p><code>
<pre>
- (CIFilter *) filter
{
	// We need to create and configure our filter here
	CIFilter * filter = [CIFilter filterWithName:@"CIUnsharpMask"];
	[filter setDefaults];
	[filter setValue:[NSNumber numberWithFloat:mStrength * 100.0] forKey:@"inputRadius"];
	return filter;
}
</pre>
<p></code></p>
<p>You&#8217;ll notice that I&#8217;m using the unsharp mask filter to do the sharpening. I should point out that most image editors don&#8217;t use this filter to do the sharpening. Instead, they seem to use a convolution filter to do the sharpening. However, the convolution filter means it is easy for the user to overdue the effect. Unsharp mask does not have that problem, plus unsharp mask is provided by the system.</p>
<p>The final bit of the sharpen tool is returning the correct blend mode:</p>
<p><code>
<pre>
- (CGBlendMode) blendMode
{
	return mMode;
}
</pre>
<p></code></p>
<h2>Blur Tool</h2>
<p>The blur tool is very similar to the sharpen tool. It even takes the same parameters. The only real difference is the filter that it creates, so that&#8217;s what I&#8217;ll present first:</p>
<p><code>
<pre>
- (CIFilter *) filter
{
	// We need to create and configure our filter here.
	CIFilter * filter = [CIFilter filterWithName:@"CIBoxBlur"];
	[filter setDefaults];
	[filter setValue:[NSNumber numberWithFloat:mStrength * 7.0] forKey:@"inputRadius"];

	return filter;
}
</pre>
<p></code></p>
<p>Here I use the built in CIBoxBlur. <var>mStrength</var> ranges from 0 to 1, inclusive. I set the coefficient of 7 simply by trial and error until I got something that &#8220;looked right.&#8221;</p>
<p>Here are some examples of the blur tool:</p>
<table>
<tr>
<th>mMode</th>
<th>mStrength</th>
<th>Result</th>
</tr>
<tr>
<td>kCGBlendModeNormal</td>
<td>0.5</td>
<td><img src="/blog/wp-content/uploads/2009/09/TweedBlurNormal50.png" alt="Tweed with 50% blur, normal blend mode" border="0" width="200" height="200" /></td>
</tr>
<tr>
<td>kCGBlendModeNormal</td>
<td>1.0</td>
<td><img src="/blog/wp-content/uploads/2009/09/TweedBlurNormal100.png" alt="Tweed with 100% blur, normal blend mode" border="0" width="200" height="200" /></td>
</tr>
<tr>
<td>kCGBlendModeDarken</td>
<td>1.0</td>
<td><img src="/blog/wp-content/uploads/2009/09/TweedBlurDarken100.png" alt="Tweed with 100% blur, darken blend mode" border="0" width="200" height="200" /></td>
</tr>
<tr>
<td>kCGBlendModeLighten</td>
<td>1.0</td>
<td><img src="/blog/wp-content/uploads/2009/09/TweedBlurLighten100.png" alt="Tweed with 100% blur, lighten blend mode" border="0" width="200" height="200" /></td>
</tr>
</table>
<h2>Conclusion</h2>
<p>Sharpen and blur are the last of the common filter brushes. Enjoy, and <a href="/blog/wp-content/uploads/2009/09/SharpenBlur.zip" title="Sharpen and blur sample code">download the sample code</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://losingfight.com/blog/2009/09/17/how-to-implement-sharpen-and-blur-tools/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How to implement a sponge tool</title>
		<link>http://losingfight.com/blog/2009/09/16/how-to-implement-a-sponge-tool/</link>
		<comments>http://losingfight.com/blog/2009/09/16/how-to-implement-a-sponge-tool/#comments</comments>
		<pubDate>Wed, 16 Sep 2009 07:25:29 +0000</pubDate>
		<dc:creator>andy</dc:creator>
				<category><![CDATA[Core Image]]></category>
		<category><![CDATA[Macintosh]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://losingfight.com/blog/?p=195</guid>
		<description><![CDATA[There aren&#8217;t many bitmap tools that I look at and think &#8220;hmmm&#8230; I could probably crank that out in about half an hour.&#8221; But because of some previous work I had done, it turned out I wasn&#8217;t too far from the mark when it comes to the sponge tool.
Overview
In concept, the sponge tool is a [...]]]></description>
			<content:encoded><![CDATA[<p>There aren&#8217;t many bitmap tools that I look at and think &#8220;hmmm&#8230; I could probably crank that out in about half an hour.&#8221; But because of some <a href="/blog/2007/09/26/how-to-implement-dodge-and-burn-tools/">previous work I had done</a>, it turned out I wasn&#8217;t too far from the mark when it comes to the sponge tool.</p>
<h2>Overview</h2>
<p>In concept, the sponge tool is a simple tool. It either increases or decreases the saturation of an image. Saturation describes how intense a given color is. 0% saturation means the color is gray. In the implementation that I will show, the sponge tool will behave like a brush.</p>
<p>Thus, if I have a simple red square with about 50% saturation, the sponge tool in the saturation mode affects it like so:</p>
<p><img src="/blog/wp-content/uploads/2009/09/Saturation.png" alt="Red square with saturation applied" border="0" width="200" height="200" /></p>
<p>The desaturation mode has the opposite effect on the same image:</p>
<p><img src="/blog/wp-content/uploads/2009/09/Desaturation.png" alt="Red square with desaturation applied" width="200" height="200" /></p>
<p>In addition to the saturate/desaturate mode, the sponge tool has  a &#8220;flow&#8221; parameter, which determines how much to saturate or desaturate the image.</p>
<h2>Implementation Overview</h2>
<p>The sponge tool is simply a subclass of the <var>FilterBrush</var> class, which I created for the <a href="/blog/2007/09/26/how-to-implement-dodge-and-burn-tools/">dodge and burn tutorial</a>. In fact the only difference between this tutorial and that one is the new subclass <var>Sponge</var>. For that reason I&#8217;ll only be covering the <var>Sponge</var> class in this tutorial. For an explanation as to how the rest of the code works, please refer back to the <a href="/blog/2007/09/26/how-to-implement-dodge-and-burn-tools/">dodge and burn tutorial</a>.</p>
<p>As always, I&#8217;ve provided <a href="/blog/wp-content/uploads/2009/09/Sponge.zip" title="Sponge sample code">sample code for this article</a>.</p>
<h2>Sponge</h2>
<h3>Parameters</h3>
<p>The sponge tool has two parameters which are initialized in the init method:</p>
<p><code></p>
<pre>
- (id) init
{
	self = [super init];
	if ( self != nil ) {
		// Set the default values for our parameters
		mFlow = 0.5;
		mMode = kSpongeMode_Desaturate;
	}
	return self;
}
</pre>
<p></code></p>
<p>The two parameters for the sponge tool are <var>mFlow</var> and <var>mMode</var>.</p>
<ul>
<li><var>mFlow</var> Flow determines how strong the saturation or desaturation effect is applied to the image. It ranges from 0.0 to 1.0, where 0.0 means the effect isn&#8217;t applied, and 1.0 is where the effect is at its strongest.
<p>	Sponge in saturate mode, varying flow:</p>
<table>
<tr>
<th>mFlow</th>
<th>Result</th>
</tr>
<tr>
<td>0.25</td>
<td><img src="/blog/wp-content/uploads/2009/09/Saturation_25.png" alt="Red square with 25% saturation applied" border="0" width="200" height="200" /></td>
</tr>
<tr>
<td>0.5</td>
<td><img src="/blog/wp-content/uploads/2009/09/Saturation.png" alt="Red square with 50% saturation applied" border="0" width="200" height="200" /></td>
</tr>
<tr>
<td>1.0</td>
<td><img src="/blog/wp-content/uploads/2009/09/Saturation_100.png" alt="Red square with 100% saturation applied" border="0" width="200" height="200" /></td>
</tr>
</table>
</li>
<li><var>mMode</var> Mode determines if the sponge saturates or desaturates the image. It has only two settings: saturate or desaturate. Saturate increases the color&#8217;s saturation, while desaturate decreases saturation.
<p>	Sponge examples:</p>
<table>
<tr>
<th>mMode</th>
<th>Result</th>
</tr>
<tr>
<td>kSpongeMode_Saturate</td>
<td><img src="/blog/wp-content/uploads/2009/09/Saturation.png" alt="Red square with saturation applied" border="0" width="200" height="200" /></td>
</tr>
<tr>
<td>kSpongeMode_Desaturate</td>
<td><img src="/blog/wp-content/uploads/2009/09/Desaturation.png" alt="Red square with desaturation applied" width="200" height="200" /></td>
</tr>
</table>
</li>
</ul>
<p>In the examples I used a red rectangle with about a 50% saturation. That way, both saturation and desaturation effects will show up on it.</p>
<h3>Creating the filter</h3>
<p>If you recall from the dodge and burn tutorial, the only real responsibility of a <var>FilterBrush</var> subclass is to create a filter that will be applied to each brush stamp.</p>
<p><code></p>
<pre>
- (CIFilter *) filter
{
	// We need to create and configure our filter here. CIColorControls has controls
	//	for saturation which is what we care about.
	CIFilter * filter = [CIFilter filterWithName:@"CIColorControls"];
	[filter setDefaults];
	if ( mMode == kSpongeMode_Saturate )
		[filter setValue:[NSNumber numberWithFloat:1 + mFlow] forKey:@"inputSaturation"];
	else
		[filter setValue:[NSNumber numberWithFloat:1 - mFlow] forKey:@"inputSaturation"];

	return filter;
}
</pre>
<p></code></p>
<p>Unlike the dodge and burn tools, I don&#8217;t use a custom filter. Instead, I use the system provided CIColorControls, which has a parameter for saturation. The saturation parameter has a range of 0 to 2, where 1 is the identity value. Thus, if I&#8217;m trying to saturate the image, I add 1.0 to mFlow, and if I&#8217;m trying to desaturate the image, I subtract mFlow from 1.0.</p>
<p>That&#8217;s it. The rest of the heavy lifting is done by the framework I created in the dodge and burn tutorial.</p>
<h2>Conclusion</h2>
<p>The main thing this tutorial demonstrates to me is that the time spent building a framework for filter brushes paid off. It should help in the future if I decide to tackle the blur and sharpen brushes as well.</p>
<p>Hopefully you found this post enlightening. Don&#8217;t forget to <a href="/blog/wp-content/uploads/2009/09/Sponge.zip" title="Sponge sample code">download the source code</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://losingfight.com/blog/2009/09/16/how-to-implement-a-sponge-tool/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How to implement dodge and burn tools</title>
		<link>http://losingfight.com/blog/2007/09/26/how-to-implement-dodge-and-burn-tools/</link>
		<comments>http://losingfight.com/blog/2007/09/26/how-to-implement-dodge-and-burn-tools/#comments</comments>
		<pubDate>Thu, 27 Sep 2007 01:03:37 +0000</pubDate>
		<dc:creator>andy</dc:creator>
				<category><![CDATA[Core Image]]></category>
		<category><![CDATA[Macintosh]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://www.losingfight.com/blog/2007/09/26/how-to-implement-dodge-and-burn-tools/</guid>
		<description><![CDATA[Today we&#8217;re going to cover some photographic tools, namely dodge and burn. When I started looking into dodge and burn, I actually didn&#8217;t know they had a real world analog, but they do. So to begin with, I&#8217;m going to cover what the tools are supposed to be mimicking, then delve into how we&#8217;re going [...]]]></description>
			<content:encoded><![CDATA[<p>Today we&#8217;re going to cover some photographic tools, namely dodge and burn. When I started looking into dodge and burn, I actually didn&#8217;t know they had a real world analog, but they do. So to begin with, I&#8217;m going to cover what the tools are supposed to be mimicking, then delve into how we&#8217;re going to achieve that.</p>
<h2>Overview</h2>
<p>Dodge and burn are photographic processes that lighten or darken a specific part of an image, respectively. Dodge works by placing an object between the light and the photo, such that the area that should be lightened is blocked from the light. Burn works the same way, except that the card is placed such that it disallows light to every part of the photo except the one the developer wants to darken. If you&#8217;re interested, Wikipedia has more in depth coverage of <a href="http://en.wikipedia.org/wiki/Dodge_and_burn">dodge and burn</a>.</p>
<p>Our dodge and burn tools are simply going to lighten and darken the image. As in most bitmap editors, our dodge and burn tools will act like brushes.</p>
<p>For example, if we have a simple black and white linear gradient, the dodge tool affects it like so:</p>
<p><img src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/dodge_tool.png" alt="Dodge midtones, 100%" /></p>
<p>The burn tool has the opposite effect on the same image:</p>
<p><img src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/burn_tool.png" alt="Burn midtones, 100%" /></p>
<p>In addition to being able to adjust the strength, often called exposure, of the dodge and burn tools, the user will also be able to determine the range of colors the tools will affect the most: highlights, midtones, or shadows. Although our dodge and burn tools do not exactly mimic the tools found in large commercial packages, I believe they get reasonably close.</p>
<h2>Implementation Overview</h2>
<p>Since dodge and burn are brushes, they inherit all the properties of our <a href="/blog/2007/08/18/how-to-implement-a-basic-bitmap-brush/">basic bitmap brush</a>. The difference is, instead of using a solid color for the brush tip, they use the source image, after it has been altered by a Core Image filter.</p>
<p>Since the basic brush implementation has been covered before, we&#8217;re only going to cover the basic algorithms used in the Core Image filters for dodge and burn. Both the dodge and burn tools only affect a specific range of colors at a time: highlights, midtones, and shadows. Because of this, there will be six filter algorithms shown here (three for dodge and three for burn).</p>
<p>The filters affect each of the color components in the same way. i.e. The function applied to the red channel is the same as the function applied to the green and blue channels. In addition, the range parameter is also per color component. Highlights are the component values closest to 1.0, while midtones are around 0.5, and shadows are close to 0.0. Finally, we&#8217;re not going to cover how to implement the exposure parameter here in the overview, but instead show the general filter functions.</p>
<h3>Dodge Highlights</h3>
<p>The dodge highlights filter will lighten all components, but will affect the components near 1.0 the most. The function for this filter is:</p>
<p><samp><br />
new component = component + e ^ component &#8211; 1.0<br />
</samp></p>
<p>where <var>component</var> is one of pixel component values (red, green, or blue) and <var>e</var> is the math constant.</p>
<p>This yields the graph:</p>
<p><img src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/dodge_highlights_graph.png" alt="Dodge highlights graph" width="430" height="418" /></p>
<p>As you can see, for component values closer to 0 (i.e. shadow values), the function is close to the identity function (new component = component, aka y = x). As the component values approach 1, the function diverges from the identity greatly in that the new component value reaches 1 much quicker. The result is that components closer to 1 are pushed towards 1 more quickly than they normally would. i.e. Highlights are lightened.</p>
<h3>Dodge Midtones</h3>
<p>Dodging midtones will lighten all components, but it will affect the components near 0.5 the most. The function for this filter is:</p>
<p><samp><br />
new component = component + 0.25 * sin(component * PI)<br />
</samp></p>
<p>where <var>component</var> is one of pixel component values (red, green, or blue) and <var>PI</var> is the math constant.  The constant of 0.25 is just to limit the amplitude.</p>
<p>This yields the graph:</p>
<p><img src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/dodge_midtones_graph.png" alt="Dodge midtones graph" width="428" height="421" /></p>
<p>As the graph demonstrates, the function intersects at (0, 0), and (1, 1), just like the identity function, but bows upward in the middle, near 0.5. The result is that components near 0.5 are pushed closer to 1, or in other words, lightened.</p>
<h3>Dodge Shadows</h3>
<p>Dodge shadows, like all the other dodge filters, will lighten all colors, but will affect the components closest to 0 the most. The function for this filter is linear:</p>
<p><samp><br />
new component = 0.5 * component + 0.5<br />
</samp></p>
<p>where <var>component</var> is one of pixel component values (red, green, or blue).</p>
<p>This yields the graph:</p>
<p><img src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/dodge_shadows_graph.png" alt="Dodge shadows graph" border="0" width="432" height="418" /></p>
<p>The function intersects at (1, 1), like the identity function, but quickly diverges from it, as the component approaches 0. As it approaches 0, the new components are lightened, in relation to the identity.</p>
<h3>Burn Highlights</h3>
<p>Burning the highlights will darken all colors, but will effect the components closest to 1 the most. The function for this filter is:</p>
<p><samp><br />
new component = 0.25 * component<br />
</samp></p>
<p>where <var>component</var> is one of pixel component values (red, green, or blue).</p>
<p>This yields the graph:</p>
<p><img src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/burn_highlights_graph.png" alt="Burn highlights graph" border="0" width="427" height="419" /></p>
<p>Note that the function intersects at (0, 0) like the identity function, but then skews the values closer to 1 to closer to 0. i.e. It darkens the highlights.</p>
<h3>Burn Midtones</h3>
<p>Burning midtones will darken all components, but affects the components closest to 0.5 the most. The function for this filter is almost identical to that for dodging midtones:</p>
<p><samp><br />
new component = component &#8211; 0.25 * sin(component * PI)<br />
</samp></p>
<p>where <var>component</var> is one of pixel component values (red, green, or blue) and <var>PI</var> is the math constant.  The constant of 0.25 is just to limit the amplitude.</p>
<p>This yields the graph:</p>
<p><img src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/burn_midtones_graph.png" alt="Burn midtones graph" border="0" width="431" height="420" /></p>
<p>As the graph demonstrates, the function intersects at (0, 0), and (1, 1), just like the identity function, but bows downward in the middle, near 0.5. The result is that components near 0.5 are pushed closer to 0, or in other words, darkened.</p>
<h3>Burn Shadows</h3>
<p>The burn shadows filter will darken all components, but will affect the components near 0 the most. The function for this filter is similar to that of dodge highlights:</p>
<p><samp><br />
new component = component + (1.0 &#8211; e ^ (component &#8211; 1.0))<br />
</samp></p>
<p>where <var>component</var> is one of pixel component values (red, green, or blue) and <var>e</var> is the math constant.</p>
<p>This yields the graph:</p>
<p><img src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/burn_shadows_graph.png" alt="Burn shadows graph.png" border="0" width="429" height="416" /></p>
<p>As you can see, for component values closer to 1 (i.e. highlight values), the function is close to the identity function (new component = component aka y = x). As the component values approach 0, the function diverges from the identity greatly in that the new component value reaches 0 much quicker. The result is that components closer to 0 are pushed towards 0 more quickly than they normally would. i.e. Shadows are darkened.</p>
<h2>Code Architecture</h2>
<p>As always, I&#8217;ve provided <a href="http://www.losingfight.com/blog/wp-content/uploads/2007/09/DodgeBurn.zip" title="Dodge and burn sample code">sample code for this article</a>. Unlike previous sample code, the code for dodge and burn is somewhat forward looking, in that it provides a framework for future filter brushes. In the future, we can simply discuss the subclasses of <var>FilterBrush</var> and the corresponding filter instead of covering all the other brush related code that just changed a little.</p>
<p>Although all the brushing code is heavily borrowed from the <a href="/blog/2007/09/04/how-to-implement-smudge-and-stamp-tools/">smudge tool</a>, it has been refactored so that it can support generic filter brushes. We&#8217;ll go over the changes required to support that, but in general, we&#8217;ll ignore the code we&#8217;ve already covered in previous articles.</p>
<p>Because the code attempts to set up somewhat of a framework, there are nine classes that make up the sample code: <var>MyDocument</var>, <var>CanvasView</var>, <var>Canvas</var>, <var>GenericBrush</var>, <var>FilterBrush</var>, <var>Dodge</var>, <var>Burn</var>, <var>BurnBrushFilter</var>, and <var>DodgeBrushFilter</var>. Fortunately most of these are carry overs which we&#8217;ll ignore since we&#8217;ve already dealt with them.</p>
<p><var>MyDocument</var>, <var>CanvasView</var> and <var>Canvas</var> are the same as they ever were, although <var>Canvas</var> has two new methods which I&#8217;ll present later. <var>GenericBrush</var> is all generic brushing code that we&#8217;ve carried over ever since the <a href="/blog/2007/08/18/how-to-implement-a-basic-bitmap-brush/">first brush article</a>. It contains a couple of new hooks that subclasses can override.</p>
<p><var>FilterBrush</var> is where the new code begins. It is an abstract subclass of <var>GenericBrush</var> and deals with rendering a filtered section if the canvas as a brush stamp. Concrete subclasses only need to overload a method to return a configured CIFilter to work.</p>
<p><var>Dodge</var> and <var>Burn</var> are subclasses of <var>FilterBrush</var>. They load their respective filters, <var>DodgeBrushFilter</var> and <var>BurnBrushFilter</var>, and override a method to return instances to their <var>FilterBrush</var> base class.</p>
<p><var>DodgeBrushFilter</var> and <var>BurnBrushFilter</var> are CIFilter subclasses, and are nearly identical except for which kernels they load up. They are responsible for actually applying the filter.</p>
<p>We&#8217;ll first cover the new methods in <var>Canvas</var> and <var>GenericBrush</var>, then move on to <var>FilterBrush</var>, <var>Dodge</var>, and <var>DodgeBrushFilter</var>. Since <var>Burn</var> and <var>BurnBrushFilter</var> are so similar to their dodge counterparts, we&#8217;ll ignore them. However, all the Core Image kernel code will be covered since it is different for each.</p>
<h2>Canvas</h2>
<p>There are only two new methods on the <var>Canvas</var> class and they&#8217;re just used for supporting applying filters to the canvas.</p>
<p>The first one returns the CGContextRef for the canvas:</p>
<p><code></p>
<pre>
- (CGContextRef) context
{
	// Just grab the context off the layer
	return CGLayerGetContext(mLayer);
}
</pre>
<p></code></p>
<p>Nothing to really discuss here: we just ask our backing CGLayerRef for the CGContextRef.</p>
<p>The next method gets a CIImage that can be used for the input of a CIFilter. Because of a bug, it&#8217;s a bit more complicated that it should be:</p>
<p><code></p>
<pre>
- (CIImage *) image
{
	// We should theoretically be able to just return [CIImage imageWithCGLayer:],
	//	but we're not able to and get the proper results. Core Image does something
	//	a little indeterminate with the pixels: the effect is applied, but the component
	//	values are shifted.
	// This seems to be caused by the fact we're using a CGLayerRef for both the
	//	source and destination of the effect. I asked on the quartz-dev list
	//	if this was supported or not, and received no answer.
	// The work around is create a deep copy of the layer in a bitmap context.
	//	It is heavy, but it works. Simply created a duplicate CGLayerRef here
	//	doesn't seem to work.
#if 1
	CGSize size = CGLayerGetSize(mLayer);
	size_t width = size.width;
	size_t height = size.height;
	size_t bitsPerComponent = 8;
	size_t bytesPerRow = (width * 4+ 0x0000000F) &#038; ~0x0000000F; // 16 byte aligned is good
	size_t dataSize = bytesPerRow * height;
	void* data = calloc(1, dataSize);
	CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);

	CGContextRef bitmapContext = CGBitmapContextCreate(data, width, height, bitsPerComponent, bytesPerRow, colorspace,  kCGImageAlphaPremultipliedFirst);

	CGColorSpaceRelease(colorspace);

	CGContextDrawLayerAtPoint(bitmapContext, CGPointMake(0, 0), mLayer);

	CGImageRef imageRef = CGBitmapContextCreateImage(bitmapContext);
	CGContextRelease(bitmapContext);
	free(data);

	CIImage* image = [CIImage imageWithCGImage:imageRef];
	CGImageRelease(imageRef);

	return image;
#else
	return [CIImage imageWithCGLayer:mLayer];
#endif
}
</pre>
<p></code></p>
<p>Technically speaking, we should be able to just call the static CIImage method imageWithCGLayer to get our CIImage. Unfortunately, there appears to be a bug in Core Image when you use the same CGLayerRef as both the source and destination of a CIFilter. In that case it tends to lighten the effect a lot more than it should. I am not sure why.</p>
<p>The workaround is to create a bitmap context, copy our layer into it, create a CGImageRef from that, then produce a CIImage from the CGImageRef. It&#8217;s a lot more involved, not to mention slower.</p>
<h2>GenericBrush</h2>
<p>The <var>GenericBrush</var> is the same brushing code that it has always been. In the sample code however, we&#8217;ve pulled it out into its own class and added a few methods that can be overridden by subclasses. By default, these methods do nothing:</p>
<p><code></p>
<pre>
// Subclass overrides so that they can know when we about to start and stop brushing
- (void) startBrush:(Canvas *)canvas;
- (void) stopBrush;

	// Override for sublcasses to implement brush render
- (void) renderInCanvas:(Canvas *)canvas bounds:(CGRect)bounds at:(NSPoint)point;
</pre>
<p></code></p>
<p>startBrush and stopBrush are optional overrides, used in case the brush needs to do special set up or take down before or after a brush stroke. startBrush is invoked inside the mouseDown handler before the first stamp is rendered, and stopBrush is invoked in the mouseUp handler after the last stamp is rendered.</p>
<p>renderInCanvas is a required override for subclasses. It renders a single brush stamp. Before invoking renderInCanvas, the <var>GenericBrush</var> class will have already set up the canvas with the proper clipping and whatnot. The subclass only needs to render a stamp at the specified point.</p>
<h2>FilterBrush</h2>
<p>The <var>FilterBrush</var> class derives from <var>GenericBrush</var> and provides a base class for all future filter-based brushes, which, in this case, will be <var>Dodge</var> and <var>Burn</var>. It does all the set up for Core Image and the actual rendering of the single brush stamp.</p>
<h3>Initialization</h3>
<p>Although <var>FilterBrush</var> doesn&#8217;t add any new properties to the brush, it does need to cache some data, as seen in the init method:</p>
<p><code></p>
<pre>
- (id) init
{
	self = [super init];
	if ( self != nil ) {
		// Initialize to nil
		mFilter = nil;
		mCIContext = nil;
	}
	return self;
}
</pre>
<p></code></p>
<p>In order to get decent performance out of brushing with a filter, we have to cache both the CIFilter we&#8217;re applying and the CIContext we&#8217;re rendering into. If we don&#8217;t, performance is bad enough that the brush is unusable at any size.</p>
<h3>Setting up the Brush Stroke</h3>
<p>In order to implement the caching, we use the startBrush and stopBrush methods we just covered in <var>GenericBrush</var>. This means we cache the filter data for one brush stroke. With some more complicated logic, we could potentially be more aggressive with the caching and get even better performance, by caching for more than one stroke.</p>
<p>First, let&#8217;s cover the startBrush method:</p>
<p><code></p>
<pre>
- (void) startBrush:(Canvas *)canvas
{
	// This gets called right at the mouse down, before we start stamping. We
	//	should do any set up here.

	// Ask the subclass for our filter. Cache it since it is expensive to keep
	//	creating for each stamp we render.
	mFilter = [[self filter] retain];
</pre>
<p></code></p>
<p>The first step is to get and cache our filter. We don&#8217;t know what the filter is, being an abstract class and all, but our subclass does, so ask them. We, as the base class, expect the filter to already be configured and ready to go, with the exception of the input image:</p>
<p><code></p>
<pre>
	// Ask the canvas for a representation of itself as an image. Pass that off
	//	to our filter to be applied to.
	CIImage* inputImage = [canvas image];
	[mFilter setValue:inputImage forKey:@"inputImage"];
</pre>
<p></code></p>
<p>We ask the canvas for a CIImage representation of itself, and then give that to the filter as input.</p>
<p>Finally, we need to cache the CIContext we&#8217;ll be rendering into:</p>
<p><code></p>
<pre>
	// Since we're a filter brush, we're rendering right back onto the canvas,
	//	so ask the canvas for the context to render into. Convert the normal
	//	context into a Core Image context, and cache it off (it's expensive to
	//	destory).
	CGContextRef context = [canvas context];
	mCIContext = [[CIContext contextWithCGContext:context options:nil] retain];
}
</pre>
<p></code></p>
<p>Not much here. We ask the canvas for its context, then build a CIContext from it.</p>
<h3>Rendering a Stamp</h3>
<p>Now that we have our filter and context set up, we&#8217;re going to start getting render requests from the base class. We handle that in renderInCanvas:</p>
<p><code></p>
<pre>
- (void) renderInCanvas:(Canvas *)canvas bounds:(CGRect)bounds at:(NSPoint)point
{
	// Render a single stamp. In our case, that simply means asking the Core Image
	//	context to draw the output of our filter, at the specified stamp location.
	//	Our base class that called us has already set up the mask clipping so
	//	our stamp will be properly shaped.
	[mCIContext drawImage:[mFilter valueForKey:@"outputImage"] atPoint:bounds.origin fromRect:bounds];
}
</pre>
<p></code></p>
<p>Since we already have the filter and context, and the base class has already set up the context and helpfully passed in the stamp bounds, all we do is ask the CIContext to render the resulting image. Easy as pie.</p>
<h3>Tearing down the Brush Stroke</h3>
<p>When we are done with a single brush stroke, we need to clean up our cache. We do that in stopBrush:</p>
<p><code></p>
<pre>
- (void) stopBrush
{
	// This gets called after the mouse up, and the last stamp is rendered. We
	//	should do any clean up here.

	// We're done with the filter and context, so free them up.
	[mFilter release];
	[mCIContext release];
	mFilter = nil;
	mCIContext = nil;
}
</pre>
<p></code></p>
<p>This is pretty self-explainatory: we release the cached filter and context. Note that this is a fairly expensive operation.</p>
<h3>Spacing</h3>
<p>As we saw with the <a href="/blog/2007/09/04/how-to-implement-smudge-and-stamp-tools/">smudge tool</a>, stamp spacing changes based the kind of brush. The filter tools, dodge and burn, are no different. So we overload the spacing method:</p>
<p><code></p>
<pre>
- (float) spacing
{
	// By filter brushes typically want closer spacing so the effect is smoother
	return 1.0;
}
</pre>
<p></code></p>
<p>Through trial and error I discovered decreasing the spacing to one pixel improved the quality of the rendering, so we do that here.</p>
<h3>Loading an Image Unit Plugin</h3>
<p>Although not used in the <var>FilterBrush</var> class itself, <var>FilterBrush</var> does provide a helper method to load up image unit plugins. This is useful for subclassed brushes who have custom filters stored in the application bundle:</p>
<p><code></p>
<pre>
- (void) loadFilter:(NSString *)filterName fromPlugin:(NSString *)pluginName
{
	// Helper function to ensure the given filter is loaded, so we can use it.

	// Ask the system to load up all the plugins it knows about
	[CIPlugIn loadAllPlugIns];
	NSArray *filterList = [CIFilter filterNamesInCategories:nil];

	// Check to see if our filter is loaded (it should be if we added to a system
	//	path.). If it is in a plugin in the application bundle, then it won't
	//	be found.
	if( ![filterList containsObject:filterName]) {
		// It wasn't loaded by default, so manually load it

		// Construct the path to the plugin bundle. We assume it's in the application
		//	bundle, in the plugins folder.
		NSString *path = [[[NSBundle mainBundle] builtInPlugInsPath] stringByAppendingPathComponent:pluginName];

		// Explicitly load the plugin, given the path
		[CIPlugIn loadPlugIn:[NSURL fileURLWithPath:path] allowNonExecutable:NO];
	}
}
</pre>
<p></code></p>
<p>This is fairly standard Core Image code. We ask the system to load up all the known Core Image plugins, then look for our specific filter. If it&#8217;s there, then we&#8217;re done. If not, then we look in our Plug-Ins folder inside our application bundle for the plug-in, and ask CIPlugIn to manually load that plug-in.</p>
<h2>Dodge</h2>
<p>The <var>Dodge</var> class implements the dodge tool by deriving from the <var>FilterBrush</var>, and overriding the filter method. It has a couple of parameters, which are the same as the <var>Burn</var> tool.</p>
<h3>Parameters</h3>
<p>The two parameters are initialized in the init method (in addition to the parameters in the <var>GenericBrush</var> init):</p>
<p><code></p>
<pre>
- (id) init
{
	self = [super init];
	if ( self != nil ) {
		// First, make sure our filter is loaded. We don't have to do it now
		//	but its convienent here.
		[self loadFilter:@"DodgeBrushFilter" fromPlugin:@"Filters.plugin"];

		// Set the default values for our parameters
		mExposure = 1.0;
		mRange = kDodgeRange_Midtones;
	}
	return self;
}
</pre>
<p></code></p>
<p>There are two new parameters for the <var>Dodge</var> and <var>Burn</var> tools: <var>mExposure</var> and <var>mRange</var>.</p>
<ul>
<li><var>mExposure</var> Exposure determines how strong the dodge or burn is applied. It ranges from 0.0 to 1.0, where 0.0 means the effect isn&#8217;t applied, and 1.0 is where the effect is at its strongest.
<p>	Dodge examples:</p>
<ul>
<li>mExposure = 0.25, <img style="vertical-align: bottom;" src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/dodge_midtones_25.png" alt="Dodge exposure 0.25" border="0" width="200" height="200" /></li>
<li>mExposure = 0.5, <img style="vertical-align: bottom;" src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/dodge_midtones_50.png" alt="Dodge exposure 0.50" border="0" width="200" height="200" /></li>
<li>mExposure = 1.0, <img style="vertical-align: bottom;" src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/dodge_midtones_100.png" alt="Dodge exposure 1.0" border="0" width="200" height="200" /></li>
</ul>
</li>
<li><var>mRange</var> Range determines the range of pixels that the dodge tool is applied to. It is an enumeration that includes highlights, midtones, and shadows. Highlights are those closest to 1, midtones those closest to 0.5, and shadows are those closest to 0.
<p>		Dodge examples:</p>
<ul>
<li>mRange = kDodgeRange_Highlights, <img style="vertical-align: bottom;" src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/dodge_highlights.png" alt="Dodge range highlights" border="0" width="200" height="200" /></li>
<li>mRange = kDodgeRange_Midtones, <img style="vertical-align: bottom;" src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/dodge_midtones.png" alt="Dodge range midtones" border="0" width="200" height="200" /></li>
<li>mRange = kDodgeRange_Shadows, <img style="vertical-align: bottom;" src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/dodge_shadows.png" alt="Dodge range shadows" border="0" width="200" height="199" /></li>
</ul>
</li>
</ul>
<p>We use a black to white linear gradient because it represents the entire component range, from shadows to highlights.</p>
<p>The only other thing to note about the init method is that we manually load up our filter, by invoking the parent class helper method.</p>
<h3>Creating a filter</h3>
<p>The only real task of the <var>Dodge</var> tool is to create the appropriate filter based on its parameters. This is accomplished in one method, filter:</p>
<p><code></p>
<pre>
- (CIFilter *) filter
{
	// We need to create and configure our filter here.

	// Pull out our special filter, and set the exposure to be exactly
	//	what was given to us.
	CIFilter * filter = [CIFilter filterWithName:@"DodgeBrushFilter"];
	[filter setDefaults];
	[filter setValue:[NSNumber numberWithFloat:mExposure] forKey:@"inputExposure"];

	// Configuring the range is slightly more complicated because we have to
	//	convert an enumeration to a straight number.
	switch ( mRange ) {
		case kDodgeRange_Highlights:
			[filter setValue:[NSNumber numberWithInt:kDodgeFilter_Highlights] forKey:@"inputRange"];
			break;
		case kDodgeRange_Midtones:
			[filter setValue:[NSNumber numberWithInt:kDodgeFilter_Midtones] forKey:@"inputRange"];
			break;
		case kDodgeRange_Shadows:
			[filter setValue:[NSNumber numberWithInt:kDodgeFilter_Shadows] forKey:@"inputRange"];
			break;
	}

	return filter;
}
</pre>
<p></code></p>
<p>Here we simply ask CIFilter for an instance of our dodge filter (not the system one), which we loaded up in our init method. Note that our dodge tool parameters have a one to one mapping with the dodge filter parameters, so we just pass them through unchanged. After we&#8217;re done configuring our filter, we return it.</p>
<p>It should be noted that the <var>Burn</var> class is identical to the <var>Dodge</var> class, except that it uses the burn filter instead of the dodge filter.</p>
<h2>DodgeBrushFilter</h2>
<p><var>DodgeBrushFilter</var> is a CIFilter derived class that is housed in an image unit plugin that our main application loads. It is a fairly standard filter, whose main task is to select the correct kernel to apply to the image passed in.</p>
<p>Like most CIFilter classes, we load the kernels in the init method:</p>
<p><code></p>
<pre>
static NSArray	*sKernels = nil;

- (id) init
{
	// If we haven't loaded up our array of kernels, do so now
	if ( sKernels == nil ) {
		// Look for our kernel code file inside of our bundle
		NSBundle *bundle = [NSBundle bundleForClass:[self class]];
		NSString *code = [NSString stringWithContentsOfFile:[bundle pathForResource:@"DodgeBrushFilter" ofType:@"cikernel"]];

		// We have three kernels in the file: highlights, midtones, and shadows.
		//	The range parameter selects which one we'll use. Cache them.
		sKernels = [[CIKernel kernelsWithString:code] retain];
	}

	return [super init];
}
</pre>
<p></code></p>
<p>We keep our kernels in a static variable so we don&#8217;t waste time loading them each time. The kernels are all stored in one file, DodgeBrushFilter.cikernel, which is kept inside the Image Unit Plugin bundle. We load the cikernel file, convert it to an array of CIKernels, and cache those off. There are three kernels loaded: one for each range.</p>
<p>Our dodge filter has two custom parameters, which we declare in the customAttributes method:</p>
<p><code></p>
<pre>
- (NSDictionary *) customAttributes
{
	// Return the custom attributes, which, in our case, is just the
	//	exposure and range parameters.
	return [NSDictionary dictionaryWithObjectsAndKeys:

		[NSDictionary dictionaryWithObjectsAndKeys:
			[NSNumber numberWithFloat: 0.0], kCIAttributeMin,
			[NSNumber numberWithFloat: 1.0], kCIAttributeMax,
			[NSNumber numberWithFloat: 0.0], kCIAttributeSliderMin,
			[NSNumber numberWithFloat: 1.0], kCIAttributeSliderMax,
			[NSNumber numberWithFloat: 0.5], kCIAttributeDefault,
			[NSNumber numberWithFloat: 0.0], kCIAttributeIdentity,
			kCIAttributeTypeScalar, kCIAttributeType,
			nil], @"inputExposure",

		[NSDictionary dictionaryWithObjectsAndKeys:
			[NSNumber numberWithInt: kDodgeFilter_Highlights], kCIAttributeMin,
			[NSNumber numberWithInt: kDodgeFilter_Shadows], kCIAttributeMax,
			[NSNumber numberWithInt: kDodgeFilter_Highlights], kCIAttributeSliderMin,
			[NSNumber numberWithInt: kDodgeFilter_Shadows], kCIAttributeSliderMax,
			[NSNumber numberWithInt: kDodgeFilter_Midtones], kCIAttributeDefault,
			kCIAttributeTypeScalar, kCIAttributeType,
			nil], @"inputRange",

		nil];
}
</pre>
<p></code></p>
<p>As with the dodge brush, the parameters are exposure and range. They mean exactly the same thing they did in the <var>Dodge</var> class.</p>
<p>The last part of our filter is the outputImage method, which actually constructs the output CIImage:</p>
<p><code></p>
<pre>
- (CIImage *) outputImage
{
	// The idea is to apply the kernel selected by the range parameter, to
	//	the image passed in. We don't really do any fancy preprocessing.
	CISampler *sampler = [CISampler samplerWithImage:inputImage];
	CIKernel *kernel = [sKernels objectAtIndex: [inputRange intValue]];

	// Just apply the chosen kernel
	return [self apply:kernel, sampler, inputExposure, kCIApplyOptionDefinition, [sampler definition], nil];
}
</pre>
<p></code></p>
<p>This is also a reasonably simple method. We create a sampler from our input image, and pull a kernel out of our cached array using the range parameter as an index. We then apply the chosen kernel to our image, and we&#8217;re done.</p>
<p>Like with the brushes, the burn filter is identical to the dodge filter, except that it loads up the burn kernels and applies those instead.</p>
<h2>Kernels</h2>
<p>The real meat of all this code are the kernels. They are straightforward implementations of the functions presented in the overview section, so not a lot of explanation will be given. The only difference is the kernels deal with implementing the exposure parameter, which the function presented in the overview section ignored.</p>
<h3>Dodge Highlights</h3>
<p><code></p>
<pre>
kernel vec4 dodgeHighlights(sampler image, float exposure)
{
	vec4 source = unpremultiply(sample(image, samplerCoord(image)));

	float factor = exposure;

	source.rgb = source.rgb + factor * (exp(source.rgb) - 1.0);

	return premultiply(source);
}
</pre>
<p></code></p>
<p>Dodge highlights is almost identical to the overview function presented. Note that we apply the function to all three components (RGB) at once, and the exp() function is <var>e</var> raised to the value passed in. In this case, the exposure is used directly to scale the steepness of the function.</p>
<h3>Dodge Midtones</h3>
<p><code></p>
<pre>
kernel vec4 dodgeMidtones(sampler image, float exposure)
{
	vec4 source = unpremultiply(sample(image, samplerCoord(image)));

	float pi = radians(180.0);
	float factor = exposure * 0.25;

	source.rgb = source.rgb + factor * sin(source.rgb * pi);

	return premultiply(source);
}
</pre>
<p></code></p>
<p>Dodge midtones is fairly straightforward. We scale the exposure by a quarter just so we don&#8217;t push values to 1 too quickly.</p>
<h3>Dodge Shadows</h3>
<p><code></p>
<pre>
kernel vec4 dodgeShadows(sampler image, float exposure)
{
	vec4 source = unpremultiply(sample(image, samplerCoord(image)));
	float factor = (1.0 - exposure * 0.5);

	source.rgb = factor * source.rgb + (1.0 - factor);

	return premultiply(source);
}
</pre>
<p></code></p>
<p>For dodging shadows, we scale the exposure by half so we don&#8217;t push everything to 1 at its peak. Also, since we&#8217;re using a linear function, we have to make the y intercept dependent on the slope. i.e. Both slope and y intercept are based on the exposure.</p>
<h3>Burn Highlights</h3>
<p><code></p>
<pre>
kernel vec4 burnHighlights(sampler image, float exposure)
{
	vec4 source = unpremultiply(sample(image, samplerCoord(image)));

	float factor = (1.0 - exposure * 0.75);

	source.rgb = factor * source.rgb;

	return premultiply(source);
}
</pre>
<p></code></p>
<p>Note that burning highlights is also a linear function. However there is no y-intercept because we want it to intersect at (0, 0). Also note that we scale the exposure by 3/4.</p>
<h3>Burn Midtones</h3>
<p><code></p>
<pre>
kernel vec4 burnMidtones(sampler image, float exposure)
{
	vec4 source = unpremultiply(sample(image, samplerCoord(image)));

	float pi = radians(180.0);
	float factor = exposure * 0.25;

	source.rgb = source.rgb - factor * sin(source.rgb * pi);

	return premultiply(source);
}
</pre>
<p></code></p>
<p>Burning midtones is identical to dodging midtones, except that we subtract off the sine wave. Like before, we scale the exposure by 1/4 before using it.</p>
<h3>Burn Shadows</h3>
<p><code></p>
<pre>
kernel vec4 burnShadows(sampler image, float exposure)
{
	vec4 source = unpremultiply(sample(image, samplerCoord(image)));

	float factor = exposure;

	source.rgb = source.rgb + factor * (1.0 - exp(1.0 - source.rgb));

	return premultiply(source);
}
</pre>
<p></code></p>
<p>Burning shadows is the same idea as dodging highlights, except that we&#8217;ve flipped the function upside down.</p>
<h2>Method to the Madness</h2>
<p>It took me a long time to figure out the current kernels for each of the ranges. On the surface, dodge and burn sound easy: you just lighten or darken the image. But when I started playing with existing dodge and burn tools, I found that it wasn&#8217;t quite that simple.</p>
<p>I started out with simple linear functions for the highlights and shadows. With some tweaks this worked respectably well for dodging shadows and burning highlights, but was way off for dodging highlights and burning shadows. In other implementations these tools pushed components to the extreme (either 0 or 1 depending on the tool) very quickly. It seemed exponential to me, which lead to the current implementation eventually. I started with base 2 instead of <var>e</var> though, on the presumption that 2 was more likely because it would execute faster.</p>
<p>Dealing with midtones took me a while. I knew I needed a curve that would be highest in the middle, but wasn&#8217;t sure which function would yield the best results. I actually started out with a <a href="http://en.wikipedia.org/wiki/Gaussian_function">gaussian function</a>, but I concluded that was way too complex for anyone to really use for a filter. I then played around with making a curve with smooth step, but I wasn&#8217;t happy with the results. I&#8217;m not sure why sin wasn&#8217;t the first function to pop into my head when I knew I needed a curve, but it turned out to be the one to yield the best results.</p>
<p>I&#8217;m not sure if this how the big graphics apps implement dodge and burn, but the results seem reasonably close to me. Unfortunately no one seems to publish how they implemented dodge and burn. As a result, this took me a long time to figure out. I literally started writing this article three times before, but stopped because I wasn&#8217;t happy with the results of the tools. I also spent a lot of time in Grapher, Apple&#8217;s graphing calculator program staring at the graphs of various functions.</p>
<h2>Conclusion</h2>
<p>In addition to showcasing some relatively complex brushes, this article introduces a framework that we can build off of to implement other filter brushes. For example: sharpen, blur, and sponge tools.</p>
<p>This is one of the more satisfying articles to write, just because of what it took to implement the dodge and burn tools. Enjoy, and be sure to <a href="http://www.losingfight.com/blog/wp-content/uploads/2007/09/DodgeBurn.zip" title="Dodge and burn sample code">download the sample code</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://losingfight.com/blog/2007/09/26/how-to-implement-dodge-and-burn-tools/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>An alternate way to implement marching ants</title>
		<link>http://losingfight.com/blog/2007/08/29/an-alternate-way-to-implement-marching-ants/</link>
		<comments>http://losingfight.com/blog/2007/08/29/an-alternate-way-to-implement-marching-ants/#comments</comments>
		<pubDate>Thu, 30 Aug 2007 03:51:15 +0000</pubDate>
		<dc:creator>andy</dc:creator>
				<category><![CDATA[Core Image]]></category>
		<category><![CDATA[Macintosh]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://www.losingfight.com/blog/2007/08/29/an-alternate-way-to-implement-marching-ants/</guid>
		<description><![CDATA[After my previous article on how to implement a magic wand tool, I got an email from Will Thimbleby suggesting an alternate way of implementing marching ants. You might know Will from the most excellent vector graphics program, LineForm. So if you like the new approach, go buy a few dozen copies of LineForm.
Overview
As I [...]]]></description>
			<content:encoded><![CDATA[<p>After my previous article on how to implement a magic wand tool, I got an email from Will Thimbleby suggesting an alternate way of implementing marching ants. You might know Will from the most excellent vector graphics program, <a href="http://www.freeverse.com/apps/app/?id=6020">LineForm</a>. So if you like the new approach, go buy a few dozen copies of LineForm.</p>
<h2>Overview</h2>
<p>As I mentioned in the <a href="/blog/2007/08/28/how-to-implement-a-magic-wand-tool/">previous article</a>, converting an image mask to a vector path is not exactly a cheap operation. Will suggested much simpler way that involved using strictly Core Image.</p>
<p>The basic idea is to use the CIStripesGenerator Core Image filter to generate us up some black and white vertical lines. We then do an edge detection filter, CIEdges, on our selection mask to calculate a new mask, representing where the marching ants should show up. We then do a multiply composite, using CIMultiplyCompositing, to merge the striped lines with our marching ants mask. The result is the stripes only show up at the edges of the selection mask. Ta-da, marching ants.</p>
<p>OK, its a little more complicated than that, but the previous paragraph should give you a pretty good idea what&#8217;s going on.</p>
<h2>Code</h2>
<p>Like before, I have sample code to go along with what I&#8217;m going to show. However, unlike before, this sample code is heavily based on the <a href="/blog/2007/08/28/how-to-implement-a-magic-wand-tool/">previous article</a>&#8217;s sample code. In fact, I really only modified one class from the Magic Wand sample code. So instead of going through all that code again, I&#8217;m going to assume you know how it works, and only highlight the new stuff.</p>
<h3>SelectionBuilder</h3>
<p>OK, I have to admit right off the top I lied about only having to modify one class. I had to modify <var>SelectionBuilder</var> slightly in order to get the generated mask to work with Core Image.</p>
<p>Instead of generating a true image mask via CGImageMaskCreate, I had to create a CGImageRef with a grayscale colorspace and no alpha. This meant that I had to:</p>
<ol>
<li>Flip the colors. In <var>SelectionBuilder</var>, black now means not in the selection, while white means in the selection. In the init method, the mask data is calloc&#8217;d and left zeroed out. When we mark a point in the selection, we set it to white.</li>
<li>Use CGImageCreate instead of CGImageMaskCreate.</li>
</ol>
<p>Fortunately, CoreGraphics doesn&#8217;t care, outside of mask creation, if it&#8217;s really an image or an image mask. So no other classes or code had to be modified for this particular change.</p>
<h3>CanvasView</h3>
<p><var>CanvasView</var> is really the class that had to change, and it was mainly in the drawRect method. Other than that, it was simply stripping out the <var>mCachedPath</var> member data since it wasn&#8217;t needed anymore. In fact, I&#8217;m only going to cover the drawRect method. If you would like to see how the rest of the code changed <a href="http://www.losingfight.com/blog/wp-content/uploads/2007/08/MagicWandv2.zip">download the sample code</a>.</p>
<p>The new drawRect method starts out normal enough:</p>
<p><code></p>
<pre>
- (void)drawRect:(NSRect)rect {
	// Simply ask the canvas to draw into the current context, given the
	//	rectangle specified. A more sophisticated view might draw a border
	//	around the canvas, or a pasteboard in the case that the view was
	//	bigger than the canvas.
	NSGraphicsContext* context = [NSGraphicsContext currentContext];

	[mCanvas drawRect:rect inContext:context];

	// If we don't have a selection, bail now
	if ( mSelection == nil )
		return;
</pre>
<p></code></p>
<p>We just draw the contents of the canvas. If we don&#8217;t have a selection, we can stop right here (but that wouldn&#8217;t be very interesting, now would it?).</p>
<p>The first thing we need to do is convert our selection mask into something Core Image can use:</p>
<p><code></p>
<pre>
	// Create a CIImage from our selection image. It's important that our mSelection
	//	has to be an actual image, not an image mask as created by CGImageMaskCreate.
	//	CIImage will not create the proper image with a CGImageRef created with
	//	CGImageMaskCreate.
	CIImage *selectionImage = [CIImage imageWithCGImage:mSelection];

	// The first thing we want to do is edge detection. We make the assumption
	//	that our mask has only two colors: black and white. If we were to do
	//	some antialiasing in it, we might have to do some posterization to
	//	reduce the number of colors before running the edges filter.
	CIFilter* edgesFilter = [CIFilter filterWithName:@"CIEdges"];
	[edgesFilter setDefaults];
	[edgesFilter setValue:selectionImage forKey:@"inputImage"];

	// In order to use our mask, convert it into an alpha channel
	CIFilter* maskToAlphaFilter = [CIFilter filterWithName:@"CIMaskToAlpha"];
	[maskToAlphaFilter setDefaults];
	[maskToAlphaFilter setValue:[edgesFilter valueForKey:@"outputImage"] forKey:@"inputImage"];
</pre>
<p></code></p>
<p>We also go ahead and do an edge detection on our mask. Since we know that our mask only ever has two colors, we don&#8217;t need to do any posterization on it beforehand. In a real system, we might have antialiasing, and might need to reduce the number of colors with posterization. We then convert our new mask into an alpha channel so we can use it in a compositing filter later.</p>
<p>To illustrate this, suppose our selection is this:</p>
<p><img src="http://www.losingfight.com/blog/wp-content/uploads/2007/08/ModernArtSelected1.png" alt="Bitmap graphic with selection" /></p>
<p>our image mask would then be:</p>
<p><img src="http://www.losingfight.com/blog/wp-content/uploads/2007/08/ModernArtMask.png" alt="Bitmap graphic selection mask" /></p>
<p>After we apply the edges filter to our mask, it would be:</p>
<p><img src="http://www.losingfight.com/blog/wp-content/uploads/2007/08/ModernArtEdges.png" alt="Bitmap graphic selection mask edges" /></p>
<p>As you can see the mask is now white where we want our marching ants to appear. Applying the mask to alpha filter then means it has an alpha of 1.0 (opaque) where it is white, and an alpha of 0.0 (transparent) where it is black.</p>
<p>Now that we have our mask, we need to generate our stripes that we&#8217;re going to use for the ants:</p>
<p><code></p>
<pre>
	// Generate vertical black and white stripes that are 4 pixels wide.
	//	We animate the marching ants here by shifting the y axis to the right
	//	each time through.
	CIFilter* stripesFilter = [CIFilter filterWithName:@"CIStripesGenerator"];
	[stripesFilter setDefaults];
	[stripesFilter setValue: [CIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:1.0] forKey:@"inputColor0"];
	[stripesFilter setValue: [CIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0] forKey:@"inputColor1"];
	[stripesFilter setValue: [NSNumber numberWithFloat:4.0] forKey:@"inputWidth"];
	[stripesFilter setValue: [CIVector vectorWithX:mPhase Y:150.0] forKey:@"inputCenter"];
</pre>
<p></code></p>
<p>We use the CIStripesGenerator to create some vertical black and white alternating lines. We set the width to four simply because that was the line dash width we used in the original marching ants algorithm. However, because of the next step, the line segments won&#8217;t exactly be four pixels wide everywhere.</p>
<p>We also implement the animation of the marching ants here. One of the parameters of the stripes filter is where the center of the generated lines are. By incrementing the x value of the center point, we shift the vertical lines to the right each time through the animation, which makes the ants &#8220;march.&#8221;</p>
<p>Our initial stripes filter image would look like this:</p>
<p><img src="http://www.losingfight.com/blog/wp-content/uploads/2007/08/StripesInitial.png" alt="Generated stripes graphic" /></p>
<p>In order to get our stripes to show up on all edges of a selection correctly, we need to tilt the stripes in one direction:</p>
<p><code></p>
<pre>
	// We have vertical stripes, which will look good on the top and bottom edges of
	//	the selection, but will appear as a solid colored line on the left and right.
	//	So that most border shapes will appear dashed, rotate the vertical lines
	//	by 45 degrees.
	CIFilter *affineTransform = [CIFilter filterWithName:@"CIAffineTransform"];
	NSAffineTransform *rotateTransform = [NSAffineTransform transform];
	[rotateTransform rotateByDegrees:-45];
	[affineTransform setDefaults];
	[affineTransform setValue:[stripesFilter valueForKey:@"outputImage"] forKey:@"inputImage"];
	[affineTransform setValue:rotateTransform forKey:@"inputTransform"];
</pre>
<p></code></p>
<p>The problem with leaving the stripes vertical is that they wouldn&#8217;t look right on the vertical edges of the selection. The top and bottom edges of the selection would nicely alternate between black and white, but the left and right edges would be one solid color.</p>
<p>To fix this we apply an affine transform to rotate the lines 45 degrees. Our stripes now look like:</p>
<p><img src="http://www.losingfight.com/blog/wp-content/uploads/2007/08/StripesRotated.png" alt="Generated stripes graphic, rotated 45 degrees" /></p>
<p>We now have our two parts: the stripes that will be our ants, and the mask that marks where they should go. We only need to combine them:</p>
<p><code></p>
<pre>
	// The last filter we apply combines our newly created stripes with our mask.
	CIFilter *multiplyFilter = [CIFilter filterWithName:@"CIMultiplyCompositing"];
	[multiplyFilter setDefaults];
	[multiplyFilter setValue:[maskToAlphaFilter valueForKey:@"outputImage"] forKey:@"inputImage"];
	[multiplyFilter setValue:[affineTransform valueForKey:@"outputImage"] forKey:@"inputBackgroundImage"];

	// Finally, render our creation to the view.
	CIContext *ciContext = [context CIContext];
	CGRect imageRect = CGRectMake(0, 0, CGImageGetWidth(mSelection), CGImageGetHeight(mSelection));
	[ciContext drawImage:[multiplyFilter valueForKey:@"outputImage"] inRect:imageRect fromRect:imageRect];
</pre>
<p></code></p>
<p>We use the multiply compositing filter to combine the images. This works because our edge mask&#8217;s alpha is 1.0 at the edges and 0.0 everywhere else. When you multiply the alpha channels of the two images together, it filters out everything but the edges, thus giving us ants around the selection.</p>
<p>Since we now have our fully formed ants, we render them to the screen using a CIContext created from our NSGraphicsContext.</p>
<p>Just for completeness, here&#8217;s the last bit of the drawRect fuction:</p>
<p><code></p>
<pre>
	// The marching ants need to animate, so fire off a timer half a second later.
	//	It will update the mPhase member and then invalidate the view so
	//	it will redraw.
	[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(onSelectionTimer:) userInfo:nil repeats:NO];
}
</pre>
<p></code></p>
<p>Nothing new here: we just fire off a timer that will increment the phase member and invalidate the view so the ants march.</p>
<p>That&#8217;s it, we&#8217;re done. Much less involved for us than the previous algorithm.</p>
<h2>Conclusion</h2>
<p>Once again, I&#8217;d like to thank Will Thimbleby for suggesting this approach. I have to admit: I think using a NSBezierPath to render the ants looks better. However for sufficiently complex or large image masks, it may be prohibitively expensive to use.</p>
<p><a href="http://www.losingfight.com/blog/wp-content/uploads/2007/08/MagicWandv2.zip">Download the sample code</a></p>
]]></content:encoded>
			<wfw:commentRss>http://losingfight.com/blog/2007/08/29/an-alternate-way-to-implement-marching-ants/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Late Night Cocoa: Image Unit Plugins</title>
		<link>http://losingfight.com/blog/2007/06/08/late-night-cocoa-image-unit-plugins/</link>
		<comments>http://losingfight.com/blog/2007/06/08/late-night-cocoa-image-unit-plugins/#comments</comments>
		<pubDate>Fri, 08 Jun 2007 21:27:09 +0000</pubDate>
		<dc:creator>andy</dc:creator>
				<category><![CDATA[Core Image]]></category>
		<category><![CDATA[Macintosh]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://www.losingfight.com/blog/2007/06/08/late-night-cocoa-image-unit-plugins/</guid>
		<description><![CDATA[It&#8217;s always kind of a horror to hear yourself speak, but Scotty has done a great job of making me sound like I walk upright, at least on weekends, on the latest episode of Late Night Cocoa. I was hoping to come out sounding like James Earl Jones, but I suppose there are limits to [...]]]></description>
			<content:encoded><![CDATA[<p>It&#8217;s always kind of a horror to hear yourself speak, but Scotty has done a great job of making me sound like I walk upright, at least on weekends, on the latest episode of <a href="http://latenightcocoa.com">Late Night Cocoa</a>. I was hoping to come out sounding like James Earl Jones, but I suppose there are limits to modern technology and podcasting ethics. Maybe the fine folks at <a href="http://www.rogueamoeba.com/">Rogue Amoeba</a> will come out with something, perhaps an amoeba shaped plush toy.</p>
<p>Anyway, if you&#8217;d like to hear someone (me, specifically) drone on about Core Image Filters and Image Unit Plugins, you should tune into <a href="http://latenightcocoa.com/node/86">Late Night Cocoa: Core Image Filters</a>, if its not already in your iTunes feed. It has lots of detailed information on Core Image filters, spoken in a high nasally voice.</p>
<p>Just think of it as James Earl Jones&#8217; voice in drag.</p>
]]></content:encoded>
			<wfw:commentRss>http://losingfight.com/blog/2007/06/08/late-night-cocoa-image-unit-plugins/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Image Filter Theatre: Errata</title>
		<link>http://losingfight.com/blog/2007/01/26/image-filter-theatre-errata/</link>
		<comments>http://losingfight.com/blog/2007/01/26/image-filter-theatre-errata/#comments</comments>
		<pubDate>Fri, 26 Jan 2007 23:53:46 +0000</pubDate>
		<dc:creator>andy</dc:creator>
				<category><![CDATA[Core Image]]></category>
		<category><![CDATA[Macintosh]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://www.losingfight.com/blog/2007/01/26/image-filter-theatre-errata/</guid>
		<description><![CDATA[In my previous two Core Image filters, I attempted to make the minimal amount of changes to the project template in order to make them work. It turns out I was a little too minimalist.
If only one of the previous examples is in your Image Unit folder, then everything works fine. The problem comes in [...]]]></description>
			<content:encoded><![CDATA[<p>In my <a href="/blog/2007/01/13/grayscale-for-the-greater-good/">previous</a> <a href="/blog/2007/01/25/image-filter-theatre-hsb-mixer/">two</a> Core Image filters, I attempted to make the minimal amount of changes to the project template in order to make them work. It turns out I was a little too minimalist.</p>
<p>If only one of the previous examples is in your Image Unit folder, then everything works fine. The problem comes in when you have both of them in the Image Unit folder. In that case, neither show up in the UI. The problem is Core Image cannot uniquely identify the filters (i.e. it thinks they are the same, and gets confused.)</p>
<p>At first, I thought I just needed to add a unique bundle identifier to my Image Unit package. Unfortunately, that is not the case, which makes sense because you can have several filters in one Image Unit package.</p>
<p>The problem lies in the Description.plist. From the template, there are three keys which have the value &#8220;MyKernelFilter&#8221;, and one key which itself is &#8220;MyKernelFilter.&#8221; Two of the values and the key need to be changed to something unique.</p>
<p>If you look under the first key in the Description file, CIPlugInFilterList, you&#8217;ll see a dictionary of filter descriptions. You&#8217;ll note that in my examples, such as the HSB Mixer, I left the key for my filter description alone, as MyKernelFilter. I need to change it to be unique, like so:<br />
<code><br />
...<br />
&lt;key&gt;CIPlugInFilterList&lt;/key&gt;<br />
&lt;dict&gt;<br />
	&lt;key&gt;HSBMixer&lt;/key&gt;<br />
	&lt;dict&gt;<br />
...<br />
</code></p>
<p>Now that I&#8217;ve picked &#8220;HSBMixer&#8221; as my unique key, I need to replace MyKernelFilter with it in a couple of places, namely the CIFilterClass and CIKernelFile values. So at the bottom of the Description file:<br />
<code><br />
...<br />
&lt;key&gt;CIFilterClass&lt;/key&gt;<br />
&lt;string&gt;HSBMixer&lt;/string&gt;<br />
&lt;key&gt;CIHasCustomInterface&lt;/key&gt;<br />
&lt;false/&gt;<br />
&lt;key&gt;CIKernelFile&lt;/key&gt;<br />
&lt;string&gt;HSBMixer&lt;/string&gt;<br />
...<br />
</code></p>
<p>CIHasCustomInterface didn&#8217;t change, I just left it in there because it was sandwiched between the two values that I did change.</p>
<p>That should cover it. If you rebuild and add both of the examples to your Image Units folder, they will both show up. This is pretty tedious work, so in future posts, I will just assume this is known and skip over it.</p>
]]></content:encoded>
			<wfw:commentRss>http://losingfight.com/blog/2007/01/26/image-filter-theatre-errata/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Image Filter Theatre: HSB Mixer</title>
		<link>http://losingfight.com/blog/2007/01/25/image-filter-theatre-hsb-mixer/</link>
		<comments>http://losingfight.com/blog/2007/01/25/image-filter-theatre-hsb-mixer/#comments</comments>
		<pubDate>Fri, 26 Jan 2007 00:35:29 +0000</pubDate>
		<dc:creator>andy</dc:creator>
				<category><![CDATA[Core Image]]></category>
		<category><![CDATA[Macintosh]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://www.losingfight.com/blog/2007/01/25/image-filter-theatre-hsb-mixer/</guid>
		<description><![CDATA[For my next trick Rocky, I&#8217;m gonna pull a rabbit outta my hat. Or maybe just a HSB mixer.
I&#8217;m going to build off what I learned from my previous Core Image example, this time adding user parameters, demonstrating how to build functions in the Core Image Kernel Language, and other exciting magic tricks.
This filter is [...]]]></description>
			<content:encoded><![CDATA[<p>For my next trick Rocky, I&#8217;m gonna pull a rabbit outta my hat. Or maybe just a HSB mixer.</p>
<p>I&#8217;m going to build off what I learned from my previous Core Image example, this time adding user parameters, demonstrating how to build functions in the <a href="http://developer.apple.com/documentation/GraphicsImaging/Reference/CIKernelLangRef/Introduction/chapter_1_section_1.html">Core Image Kernel Language</a>, and other exciting magic tricks.</p>
<p>This filter is going to simply allow the user to make adjustments to the image in the HSB color space. So I probably need to come up with a few definitions as to what HSB color space is.</p>
<h3>HSB Color Space</h3>
<p><a href="http://en.wikipedia.org/wiki/HSV_color_space">HSB</a>, aka HSV, is a color space that defines color by three components: hue, saturation, and brightness (sometimes called value). Hue describes the color type (e.g. red, green, blue, etc) and is measured in degrees, from 0 to 360. Saturation is the vibrancy of the color, typically measured from 0 to 100. The less saturation, the more gray a color appears. The value, or brightness of a color, is essentially what it sounds like, and typically ranges from 0 to 100.</p>
<p>The HSB color space is a cone, with the hue running the circumference of the base, saturation running from the center to the edge of the cone, and brightness running the height of the cone. It made a lot more sense to me when I looked a few models, which can be found at Wikipedia&#8217;s entry on the <a href="http://en.wikipedia.org/wiki/HSV_color_space">HSV color space</a>.</p>
<h3>Project and Resources</h3>
<p>Like last time, I want to create a project from the &#8220;Image Unit Plug-in for Objective-C&#8221; template. I&#8217;ve named mine &#8220;HSB Mixer.&#8221;</p>
<p>Next, I need to modify the Description.plist file to tell Core Image about the parameters my filter has, and what category my filter belongs to, which effects where it shows up in the UI.</p>
<p>Under the CIAttributeFilterCategories key in the plist, I find the different default categories HSB Mixer belongs to. Although the HSB Mixer will work with both video and still images, it&#8217;s not really a &#8220;stylize&#8221; filter, but more of a color adjustment. To that end, I change CICategoryStylize to CICategoryColorAdjustment:<br />
<code><br />
&lt;key&gt;CIAttributeFilterCategories&lt;/key&gt;<br />
&lt;array><br />
	&lt;string>CICategoryColorAdjustment&lt;/string&gt;<br />
	&lt;string>CICategoryVideo&lt;/string&gt;<br />
	&lt;string>CICategoryStillImage&lt;/string&gt;<br />
&lt;/array&gt;<br />
</code></p>
<p>Now I need to add my parameters. By default, the image to modify is the first parameter, followed by two number parameters. Since this isn&#8217;t a generation filter, I need the image parameter, but instead of the two default number parameters, I need three number parameters for hue, saturation, and brightness. So I delete the last two parameters (the last two dict elements) under the CIInputs key.</p>
<p>The first parameter that I add is hue:<br />
<code><br />
&lt;dict&gt;<br />
	&lt;key&gt;CIAttributeClass&lt;/key&gt;<br />
	&lt;string&gt;NSNumber&lt;/string&gt;<br />
	&lt;key&gt;CIAttributeDefault&lt;/key&gt;<br />
	&lt;real&gt;0&lt;/real&gt;<br />
	&lt;key&gt;CIAttributeDisplayName&lt;/key&gt;<br />
	&lt;string&gt;inputHue&lt;/string&gt;<br />
	&lt;key&gt;CIAttributeIdentity&lt;/key&gt;<br />
	&lt;real&gt;0&lt;/real&gt;<br />
	&lt;key&gt;CIAttributeName&lt;/key&gt;<br />
	&lt;string&gt;inputHue&lt;/string&gt;<br />
	&lt;key&gt;CIAttributeSliderMax&lt;/key&gt;<br />
	&lt;real&gt;6&lt;/real&gt;<br />
	&lt;key&gt;CIAttributeSliderMin&lt;/key&gt;<br />
	&lt;real&gt;-6&lt;/real&gt;<br />
&lt;/dict&gt;<br />
</code></p>
<p>The first key is CIAttributeClass, and it simply states the corresponding Cocoa class to use for the given parameter. All the possible values can be found in the document <a href="http://developer.apple.com/documentation/GraphicsImaging/Conceptual/CoreImaging/ci_image_units/chapter_5_section_6.html">Modify the Description Property List</a> in Table 4-2.</p>
<p>The CIAttributeDefault key describes the default value of the parameter in the UI. I choose 0 as the default value, because it is also the identity value (see below).</p>
<p>CIAttributeDisplayName is a key into the Description.strings file, and determines the localized name that is shown to the user.</p>
<p>The CIAttributeIdentity key specifies a value for the parameter which produces the identity of the input image. i.e. It&#8217;s a value such that the filter doesn&#8217;t change the source image in any way. In this case, a value of zero means the filter doesn&#8217;t change the hue of the image.</p>
<p>CIAttributeName is an internal name for the parameter. Core Image Filters are &#8220;Live Effects&#8221; in <a href="http://www.adobe.com/products/fireworks/">Fireworks</a> parlance, meaning they store their parameters so they can be modified later, and they don&#8217;t modify the source image directly. Instead, they generate a new image based on the source image plus the filter parameters. In other words, they&#8217;re editable. To that end, each of the parameters I define need to be stored in an NSDictionary, and CIAttributeName is simply the key for each parameter in that dictionary.</p>
<p>CIAttributeSliderMin and CIAttributeSliderMax define the range of the slider control used to represent the parameter. Although hue ranges from 0 to 360 degrees, I&#8217;m going to scale it down to 0 to 6, using real numbers. Since I want to be able to both add and subtract from the hue, so all values are achievable, I let the range be -6 to 6.</p>
<p>The parameters for saturation and value are the same as hue, except they range from -1 to 1, and obviously have different names:<br />
<code><br />
&lt;dict&gt;<br />
	&lt;key&gt;CIAttributeClass&lt;/key&gt;<br />
	&lt;string&gt;NSNumber&lt;/string&gt;<br />
	&lt;key&gt;CIAttributeDefault&lt;/key&gt;<br />
	&lt;real&gt;0&lt;/real&gt;<br />
	&lt;key&gt;CIAttributeDisplayName&lt;/key&gt;<br />
	&lt;string&gt;inputSaturation&lt;/string&gt;<br />
	&lt;key&gt;CIAttributeIdentity&lt;/key&gt;<br />
	&lt;real&gt;0&lt;/real&gt;<br />
	&lt;key&gt;CIAttributeName&lt;/key&gt;<br />
	&lt;string&gt;inputSaturation&lt;/string&gt;<br />
	&lt;key&gt;CIAttributeSliderMax&lt;/key&gt;<br />
	&lt;real&gt;1&lt;/real&gt;<br />
	&lt;key&gt;CIAttributeSliderMin&lt;/key&gt;<br />
	&lt;real&gt;-1&lt;/real&gt;<br />
&lt;/dict&gt;<br />
&lt;dict&gt;<br />
	&lt;key&gt;CIAttributeClass&lt;/key&gt;<br />
	&lt;string&gt;NSNumber&lt;/string&gt;<br />
	&lt;key&gt;CIAttributeDefault&lt;/key&gt;<br />
	&lt;real&gt;0&lt;/real&gt;<br />
	&lt;key&gt;CIAttributeDisplayName&lt;/key&gt;<br />
	&lt;string&gt;inputValue&lt;/string&gt;<br />
	&lt;key&gt;CIAttributeIdentity&lt;/key&gt;<br />
	&lt;real&gt;0&lt;/real&gt;<br />
	&lt;key&gt;CIAttributeName&lt;/key&gt;<br />
	&lt;string&gt;inputValue&lt;/string&gt;<br />
	&lt;key&gt;CIAttributeSliderMax&lt;/key&gt;<br />
	&lt;real&gt;1&lt;/real&gt;<br />
	&lt;key&gt;CIAttributeSliderMin&lt;/key&gt;<br />
	&lt;real&gt;-1&lt;/real&gt;<br />
&lt;/dict&gt;<br />
</code></p>
<p>Finally, we need to modify Description.strings to have localized versions of our parameter display names:<br />
<code><br />
"MyKernelFilter" = "HSB Mixer";<br />
"inputHue" = "Hue";<br />
"inputSaturation" = "Saturation";<br />
"inputValue" = "Brightness";<br />
</code></p>
<h3>Kernel Code</h3>
<p>The algorithm for the kernel is simple:</p>
<ol>
<li>Sample the current pixel</li>
<li>Convert the current pixel from RGB to HSV</li>
<li>Add the hue, saturation, and value input parameters to the corresponding component in the current HSV pixel</li>
<li>Convert the adjusted HSV pixel back to RGB</li>
<li>Return the new RGB pixel</li>
</ol>
<p>The kernel function is defined as:<br />
<code><br />
kernel vec4 hueSaturationKernel(sampler image, float inputHue, float inputSaturation, float inputValue)<br />
{<br />
    // Get source pixel<br />
    vec4   p = sample(image, samplerCoord(image));</p>
<p>    // Convert to HSV color space<br />
	vec4 hsvPixel = rgbToHsv(p);</p>
<p>	// Add on our values (but be sure to clip)<br />
	hsvPixel.r = clamp(hsvPixel.r + inputHue, 0.0, 6.0);<br />
	hsvPixel.g = clamp(hsvPixel.g + inputSaturation, 0.0, 1.0);<br />
	hsvPixel.b = clamp(hsvPixel.b + inputValue, 0.0, 1.0);</p>
<p>	// Convert back to RGB color space<br />
	return hsvToRgb(hsvPixel);<br />
}<br />
</code></p>
<p>Note that my user parameters are just passed in, in the order I declared them in Description.plist. The kernel function itself is pretty straightforward. <strong>rgbToHsv()</strong> and <strong>hsvToRgb()</strong> are the only user defined functions, and they convert to the HSV color space and back. Notice that the vec4 data type always assumes rgba, so when you see hsvPixel.r here, it actually corresponds to the hue. Similarly hsvPixel.g corresponds to saturation and hsvPixel.b corresponds to value, although the alpha channel remains untouched.</p>
<p>The tricky part is dealing with HSV to RGB and RGB to HSV conversions. There is code all over the place to deal with this, but I used the <a href="http://jgt.akpeters.com/papers/SmithLyons96/hsv_rgb.html">C code from an ACM paper</a> as a template. The algorithm there scales the hue down to be from 0 to 6, which is why my input parameters do as well. I won&#8217;t go into the math behind the conversions here, because it is covered in any graphics textbook, as well as several places on the web, including Wikipedia.</p>
<p>The easier of the conversions, code wise, is RGB to HSV:<br />
<code><br />
vec4 rgbToHsv(vec4 rgb)<br />
{<br />
	float x = min(rgb.r, min(rgb.g, rgb.b));<br />
	float v = max(rgb.r, max(rgb.g, rgb.b));<br />
	float f = (rgb.r == x) ? rgb.g - rgb.b : ((rgb.g == x) ? rgb.b - rgb.r : rgb.r - rgb.g);<br />
	float i = (rgb.r == x) ? 3.0 : ((rgb.g == x) ? 5.0 : 1.0);<br />
	float h = i - (f / (v - x));<br />
	float s = (v - x) / v;</p>
<p>	return (v == x) ? vec4(-1, 0, v, rgb.a) : vec4(h, s, v, rgb.a);<br />
}<br />
</code></p>
<p>Just about everything in this function is straightforward. <strong>min()</strong> and <strong>max()</strong> are built in functions that will work on any numerical type.</p>
<p>You might notice that there is potentially wasted computation here, in the special case of <strong>v == x</strong>. In that case, I just return v, meaning I didn&#8217;t need to compute <strong>f</strong>, <strong>i</strong>, <strong>h</strong>, or <strong>s</strong>. The reason for this is that the Core Image Kernel Language doesn&#8217;t support if statements that are data dependent. That is, the expression inside of an if statement can only contain a constant.</p>
<p>Correct:<br />
<code><br />
if ( true ) v = x;<br />
if ( false ) x = v;<br />
</code></p>
<p>Incorrect:<br />
<code><br />
if ( v == x ) v = x; // Error!<br />
bool val = true;<br />
if ( val ) x = v; // Error!<br />
</code></p>
<p>The kernel language does however allow use of the ternary operator, so that&#8217;s what I&#8217;m forced to use here. The side effect of that is that I cannot return early, even if all the relevant calculations are complete. I&#8217;m guessing the reason for this restriction is related to the abilities of most GPU&#8217;s.</p>
<p>The HSV to RGB conversion is sightly more complicated:<br />
<code><br />
vec4 hsvToRgb(vec4 hsv)<br />
{<br />
	float h = hsv.r;<br />
	float s = hsv.g;<br />
	float v = hsv.b;<br />
	int i = int(floor(h));<br />
	float f = isOdd(i) ? h - float(i) : 1.0 - (h - float(i));<br />
	float m = v * (1.0 - s);<br />
	float n = v * (1.0 - s * f);<br />
	vec4 result = (i == 0) ? vec4(v, n, m, hsv.a) : ((i == 1) ? vec4(n, v, m, hsv.a) : ((i == 2) ? vec4(m, v, n, hsv.a) : ((i == 3) ? vec4(m, n, v, hsv.a) : ((i == 4) ? vec4(n, m, v, hsv.a) : ((i == 5) ? vec4(v, m, n, hsv.a) : vec4(v, n, m, hsv.a))))));</p>
<p>	return (h == -1.0) ? vec4(v, v, v, hsv.a) : result;<br />
}<br />
</code></p>
<p>The first thing to note is that all type conversions must be explicit. To get <strong>i</strong> I call <strong>floor()</strong>, a built in function, on <strong>h</strong>, then explicitly cast it to an int. I&#8217;m not sure of design decisions made here, but I would hazard to guess that float to int conversions, and vice versa, are expensive.</p>
<p>The only other thing to note here is the last user defined function, <strong>isOdd()</strong>:<br />
<code><br />
bool isOdd(int v)<br />
{<br />
	float dividend = float(v) / 2.0;<br />
	return dividend != floor(dividend);<br />
}<br />
</code></p>
<p>I call attention to this function because of its technique. Usually, to determine if a number is odd, I bitwise AND against a bitmask of 0&#215;0001, or take the modulus by 2 and see if it has a remainder. However, the kernel language does not support any bitwise operators or modulus. Here, I divide by two and see if has fractional part by comparing the dividend against the floor of itself. This is error prone because of rounding errors. Another way would be to take the difference between the dividend and its floor, and see if it is greater than a specified small number.</p>
<h3>Trying it out</h3>
<p><a class="imagelink" href="/blog/wp-content/uploads/2007/01/HSBMixerExample.png" title="Flower run through HSB Mixer"><img id="image111" src="/blog/wp-content/uploads/2007/01/HSBMixerExample.thumbnail.png" alt="Flower run through HSB Mixer" /></a></p>
<p>Like before, I like using the Core Image Fun House for experimenting with my filters. I just need to copy my built filter to ~/Library/Graphics/Image Units/ to do that.</p>
<p>Here&#8217;s Flowers.jpg with the HSB Mixer applied to it. The parameters are Hue: 2.0, Saturation: -0.48, Brightness: 0.0.</p>
<h3>Conclusion</h3>
<p>Hopefully you&#8217;ve learned more about Core Image Filters from this post. We&#8217;ve learned that simply declaring input parameters in a plist file, Core Image clients can automatically construct UI for your filter. Also, we&#8217;ve learned that although the kernel language is powerful, it has some serious limitations that we have to work around.</p>
<p><a id="p112" href="/blog/wp-content/uploads/2007/01/HSB Mixer.zip">Download HSB Mixer Source Code</a></p>
]]></content:encoded>
			<wfw:commentRss>http://losingfight.com/blog/2007/01/25/image-filter-theatre-hsb-mixer/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Grayscale for the Greater Good</title>
		<link>http://losingfight.com/blog/2007/01/13/grayscale-for-the-greater-good/</link>
		<comments>http://losingfight.com/blog/2007/01/13/grayscale-for-the-greater-good/#comments</comments>
		<pubDate>Sun, 14 Jan 2007 03:52:08 +0000</pubDate>
		<dc:creator>andy</dc:creator>
				<category><![CDATA[Core Image]]></category>
		<category><![CDATA[Macintosh]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://www.losingfight.com/blog/2007/01/13/grayscale-for-the-greater-good/</guid>
		<description><![CDATA[Ever since I worked on the greatest web graphics program ever, that&#8217;d be Fireworks for the uninitiated, I&#8217;ve been intrigued by Ted Turner&#8217;s idea of colorizing old movies, and image filters.
It wasn&#8217;t until the Fireworks 8 cycle that I really got a chance to start playing with image filters. I wrote several, but because I [...]]]></description>
			<content:encoded><![CDATA[<p>Ever since I worked on the greatest web graphics program ever, that&#8217;d be <a href="http://www.adobe.com/products/fireworks/">Fireworks</a> for the uninitiated, I&#8217;ve been intrigued by Ted Turner&#8217;s idea of colorizing old movies, and image filters.</p>
<p>It wasn&#8217;t until the Fireworks 8 cycle that I really got a chance to start playing with image filters. I wrote several, but because I was pulled off Fireworks just a few months into Fireworks 8, only a couple of them actually made it into the final product. That didn&#8217;t end my interest in filters, but it did end my chance of publishing them.</p>
<p>I&#8217;ve also come to the conclusion that Ted Turner got it backwards. Instead of colorizing old movies, he should have been making all the new movies grayscale. As any artist can tell you, making images and/or movies black and white automatically makes them artsy. At least, that&#8217;s what all the artists I just made up in my head say.</p>
<p>The epiphany came today when I realized I should combine these two ideas, and create a filter that removes color. Well, that and show how to write Core Image filters. That might be interesting too.</p>
<h3>Creating a project</h3>
<p><a class="imagelink" href="/blog/wp-content/uploads/2007/01/Picture 13.png" title="New Image Plug-in"><img id="image106" src="/blog/wp-content/uploads/2007/01/Picture 13.thumbnail.png" alt="New Image Plug-in" /></a></p>
<p>I want to create a standalone Image Unit Plug-in. Fortunately, Xcode has a template just for that. To get this artistic revolution started, I select File > New Project&#8230; from Xcode&#8217;s menu. From here, I pick the &#8220;Image Unit Plug-in for Objective-C&#8221; template.</p>
<p>Next, I name my project &#8220;Grayscale,&#8221; because that&#8217;s what the plugin will do, and because I&#8217;m not a crazy Brit who insists on spelling gray with an &#8220;e.&#8221; The way they prattle on about it, you&#8217;d think they&#8217;d invented the language or something.</p>
<h3>Modifying the description</h3>
<p>Now that I have a project, the first thing I want to do is modify the description property list. The Description.plist is what tells Core Image what the parameters for the filter are, the category of the filter, and where to find the kernel, which, surprisingly, isn&#8217;t always at KFC.</p>
<p>In order to find my filter later to use it,  I need to give it a meaningful category. Apple has several predefined categories that I have to choose from, thus cramping my artistic style. Despite this, I can still improve on the default choice of CICategoryStylize, by changing it to CICategoryColorEffect.</p>
<p>After my change, the Description.plist category section looks like this:<br />
<code><br />
&lt;key&gt;CIAttributeFilterCategories&lt;/key&gt;<br />
&lt;array&gt;<br />
	&lt;string&gt;CICategoryColorEffect&lt;/string&gt;<br />
	&lt;string&gt;CICategoryVideo&lt;/string&gt;<br />
	&lt;string&gt;CICategoryStillImage&lt;/string&gt;<br />
&lt;/array&gt;<br />
</code></p>
<p><a class="imagelink" href="/blog/wp-content/uploads/2007/01/Picture 16.png" title="Description.plist Filter Parameters"><img id="image107" src="/blog/wp-content/uploads/2007/01/Picture 16.thumbnail.png" alt="Description.plist Filter Parameters" /></a></p>
<p>The most important thing to modify the in Description.plist is the description of the filter&#8217;s parameters. It turns out that I don&#8217;t need any input parameters for this colorless revolution, with the exception of the source image. The default Description.plist comes with three parameters, so I&#8217;ll delete the last two parameters.</p>
<p>That concludes everything I need to do with Description.plist. However, I still need to change the display name that shows up in the UI for my filter. For that I open up Description.strings:<br />
<code><br />
"MyKernelFilter" = "Demo CIKernel Only Filter";<br />
"inputScale" = "Scale";<br />
"inputGreenWeight" = "Green Weight";<br />
</code></p>
<p>Since I don&#8217;t have any user inputs, I can ditch the two last strings, inputScale and inputGreenWeight. MyKernelFilter is the name of my filter, so I&#8217;ll change it to &#8220;Grayscale.&#8221;<br />
<code><br />
"MyKernelFilter" = "Grayscale";<br />
</code></p>
<p>That does it for all the meta-information for my filter plug-in.</p>
<h3>Kernel</h3>
<p>Now that I&#8217;ve set up all the resources for my filter, I need to write the actual code. The kernel code is written in what is called <a href="http://developer.apple.com/documentation/GraphicsImaging/Reference/CIKernelLangRef/Introduction/chapter_1_section_1.html">Core Image Kernel Language</a>, which is a subset of the OpenGL Shading Language.</p>
<p>To start, I open up MyKernelFilter.cikernel, and delete it&#8217;s current contents. In its place, I add the following code:<br />
<code><br />
kernel vec4 grayscaleKernel(sampler image)<br />
{<br />
    // Get source pixel<br />
    vec4   p = sample(image, samplerCoord(image));</p>
<p>	// Calculate the intensity<br />
	float intensity = clamp(0.3* p.r + 0.59* p.g + 0.11* p.b, 0.0, 1.0);</p>
<p>	// Set the destination pixel based on intensity<br />
    return vec4(intensity, intensity, intensity, p.a);<br />
}<br />
</code></p>
<p>It doesn&#8217;t really matter what I name the function, as long as it is tagged with the <strong>kernel</strong> keyword. The kernel, in this case, acts as a map from the source pixel value, to the destination pixel value. You&#8217;ll note that it returns a vector of four elements (Red/Green/Blue/Alpha) and takes the source image as the only parameter. If I had other parameters, they would come after the image parameter, just like in the Description.plist file.</p>
<p>The first line simply retrieves the pixel from the source image:<br />
<code><br />
vec4   p = sample(image, samplerCoord(image));<br />
</code></p>
<p>As established earlier, <strong>image</strong> is the source image passed in as a parameter. <strong>samplerCoord()</strong> returns the coordinates of the source pixel inside the source image. <strong>sample()</strong> does what it says, and will return a single pixel value given an image and coordinates.</p>
<p>The second line calculates the intensity of the source pixel:<br />
<code><br />
float intensity = clamp(0.3* p.r + 0.59* p.g + 0.11* p.b, 0.0, 1.0);<br />
</code></p>
<p>To do this I add the weighted RGB components together. The reason behind the weights goes beyond what I want to cover here, but I use intensity calculation from the <a href="http://www.graphicdesigndictionary.com/terms-yiq.html">YIQ color model</a>. For safety reasons I clamp the value to between 0 and 1, because each color component has to stay in that range.</p>
<p>Finally, I return the value of the destination pixel:<br />
<code><br />
return vec4(intensity, intensity, intensity, p.a);<br />
</code></p>
<p>I&#8217;m simply setting each of the RGB components to the intensity, which will make the color a shade of gray. Also note that I&#8217;m leaving the alpha channel alone.</p>
<p>That&#8217;s it as far as the coding goes. I just need to select Build > Build from the menu to complete my filter.</p>
<h3>Making cone cells obsolete</h3>
<p>Before I can do anything with my Grayscale filter, I need to install it. Core Image looks in a couple of different places, but the easiest folder to stick it in is: ~/Library/Graphics/Image Units/.</p>
<p><a class="imagelink" href="/blog/wp-content/uploads/2007/01/Picture 17.png" title="Gray wolf"><img id="image108" src="/blog/wp-content/uploads/2007/01/Picture 17.thumbnail.png" alt="Gray wolf" /></a></p>
<p>Although I could use Quartz Composer to try out my filter, I like the Core Image Fun House, because it&#8217;s simpler and more to the point. It can be found at /Developer/Applications/Graphics Tools/. When I open the Fun House, I&#8217;m prompted to pick a source image. I usually pick the Wolf.jpg, whom I&#8217;ve nicknamed <a href="http://rentzsch.com/">&#8220;Rentzsch&#8221;</a>, for reasons that escape me.</p>
<p>After picking an image, I click the plus button, and pick my Grayscale filter from under the Color Effect category.</p>
<p>And there you have it. A very artistic, extraordinary, gray wolf, of whom there are none like. Hmmm&#8230; maybe I should have created a green filter.</p>
<h3>Conclusion</h3>
<p>Although I avoided Quartz Composer for this simple example, it is pretty much required for any filter of sufficient complexity. When building, Xcode doesn&#8217;t actually compile the kernel or do any type of checking. As far as I can tell, only the Core Image Kernel tool in Quartz Composer will do checking. Unfortunately, Quartz Composer doesn&#8217;t allow me to inspect variables or step through statements. It only does basic syntax and type checking.</p>
<p>Also, the shading language, especially with Apple&#8217;s restrictions, is pretty limiting. Even with simple kernels, I struggled to get them to work with the kernel language.</p>
<p>Despite the restrictions, I&#8217;m having a lot of fun playing around with Core Image kernels. I&#8217;ve written a few others that I may, at some point in the future, show off here. Until then, have fun viewing the world through the eyes of a dog.</p>
<p><a id="p109" href="/blog/wp-content/uploads/2007/01/Grayscale.zip">Download Grayscale Core Image Filter</a></p>
]]></content:encoded>
			<wfw:commentRss>http://losingfight.com/blog/2007/01/13/grayscale-for-the-greater-good/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
	</channel>
</rss>
