<?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; Macintosh</title>
	<atom:link href="http://losingfight.com/blog/category/macintosh/feed/" rel="self" type="application/rss+xml" />
	<link>http://losingfight.com/blog</link>
	<description>what happens when a hick from tennessee figures out how to turn the computer on</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>Compiling OpenCL Kernels with Xcode</title>
		<link>http://losingfight.com/blog/2009/12/17/compiling-opencl-kernels-with-xcode/</link>
		<comments>http://losingfight.com/blog/2009/12/17/compiling-opencl-kernels-with-xcode/#comments</comments>
		<pubDate>Thu, 17 Dec 2009 23:49:44 +0000</pubDate>
		<dc:creator>andy</dc:creator>
				<category><![CDATA[Macintosh]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://losingfight.com/blog/?p=358</guid>
		<description><![CDATA[The talk I&#8217;m working on for NSConference 2010 involves a decent amount of OpenCL kernel code. Originally, I thought I would develop the kernel code the same way I develop Core Image kernel code: build the kernel in Quartz Composer and then copy paste the code into Xcode when I got it working. 
Unfortunately, I&#8217;ve [...]]]></description>
			<content:encoded><![CDATA[<p>The talk I&#8217;m working on for <a href="http://www.nsconference.com">NSConference 2010</a> involves a decent amount of OpenCL kernel code. Originally, I thought I would develop the kernel code the same way I develop Core Image kernel code: build the kernel in Quartz Composer and then copy paste the code into Xcode when I got it working. </p>
<p>Unfortunately, I&#8217;ve not had much luck with Quartz Composer in way of meaningful compiler errors. Sometimes it returns good errors, sometimes inscrutable ones.</p>
<div align="center">
<a href="/blog/wp-content/uploads/2009/12/QuartzComposer1.png"><img src="/blog/wp-content/uploads/2009/12/QuartzComposer1.png" alt="Quartz Composer" border="0" width="259" height="320" /></a></p>
<div style="font-size:0.7em;">Composer: Wait, wait, I remember this&#8230; it&#8217;s COBOL, right?</div>
</div>
<p>&nbsp;</p>
<p>However, I&#8217;ve found debugging OpenCL kernels to be much easier thanks to the invention of a function I like to call &#8220;printf.&#8221; If you run your kernel on the CPU, then you can print out values just like in C. With debugging solved, I just needed to figure out how to find compiler errors without having to run my code.</p>
<p>Unlike Core Image, OpenCL actually requires you to compile your kernel code explicitly before using it. The API&#8217;s required to do this will return nice compiler error messages and everything. The obvious thing to do would be wrap up these API&#8217;s in a command line tool and invoke the tool from Xcode, which is what I did.</p>
<h2>The Code</h2>
<p>You can download the Xcode project for <a href="/blog/wp-content/uploads/2009/12/clc.zip" title="clc.zip">clc, the OpenCL Compiler here</a>. It requires Mac OS X 10.6 Snow Leopard and the associated Xcode tools. To install, just build the project and copy the resulting binary into your path somewhere. I used ~/bin.</p>
<p>Most of the code is self explanatory, with the possible exception of the output. Unlike most compilers, clc doesn&#8217;t output object code because the host and runtime CPU/GPU&#8217;s could easily be different. It could simply copy the source code as is for the output, but that was a little too obvious for my tastes. Instead clc compresses the source code and stuffs it into a binary plist. It has the slight benefit of potentially being smaller on disk, and making your source code negligibly less accessible to prying eyes, if you&#8217;re into that sort of thing.</p>
<p>There are definite possibilities for improvement. For example, there could be an option to encrypt the source code to be more &#8220;secure.&#8221; Or it could store the binary code generated by the host machine in the output on the chance that the host and runtime machines have the same hardware.</p>
<h2>Setting up Xcode</h2>
<p>To use clc, you&#8217;ll need to set up a build rule in your app&#8217;s target settings to run all OpenCL source files through it. Selecting the menu item Project &gt; Edit Active Target, and then selecting the Rules tab, should land you here:</p>
<p><img src="/blog/wp-content/uploads/2009/12/TargetSettings.png" alt="TargetSettings.png" border="0" width="577" height="306" /></p>
<p>Add another rule, set it to process OpenCL source files using a Custom script. For the custom script enter:</p>
<p><code>~/bin/clc ${INPUT_FILE_PATH} -o ${TARGET_BUILD_DIR}/${PRODUCT_NAME}.app/Contents/Resources/${INPUT_FILE_BASE}.clar</code></p>
<p>Finally, you&#8217;ll need to tell Xcode where you&#8217;re putting the output file, which is:</p>
<p><code>${TARGET_BUILD_DIR}/${PRODUCT_NAME}.app/Contents/Resources/${INPUT_FILE_BASE}.clar</code></p>
<p>By default, Xcode won&#8217;t put OpenCL source files (.cl) into the target, meaning they won&#8217;t get compiled. For each of your OpenCL source files<br />
you&#8217;ll need to explicitly add them to your target. There are several ways to do this, but the easiest is probably selecting all of them, and doing a Get Info (File &gt; Get Info). Switch to the Targets tab and check the target they should be compiled into.</p>
<h2>Runtime Code</h2>
<p>At runtime, it&#8217;s pretty easy to retrieve the original OpenCL source code using the <var>+ (NSString *) openCLSourceWithData:(NSData *)data error:(NSError **)error;</var> method on <var>ONOpenCLArchive</var>. However, there are a couple of convenience methods on NSBundle that makes retrieving the kernel easier. For example:</p>
<p><code></p>
<pre>
#import "ONOpenCLArchive.h"

NSString *kernelSource = [[NSBundle mainBundle] openCLResource:@"MyKernel"];
</pre>
<p></code></p>
<p>That&#8217;s pretty much all there is to it.</p>
<h2>Conclusion</h2>
<p>Although not ground breaking, this little tool has certainly been helpful to me by finding compiler errors at compile time instead of runtime. Hopefully it will be useful to other people as well.</p>
]]></content:encoded>
			<wfw:commentRss>http://losingfight.com/blog/2009/12/17/compiling-opencl-kernels-with-xcode/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>NSConference 2010 Quiz</title>
		<link>http://losingfight.com/blog/2009/12/15/nsconference-2010-quiz/</link>
		<comments>http://losingfight.com/blog/2009/12/15/nsconference-2010-quiz/#comments</comments>
		<pubDate>Wed, 16 Dec 2009 03:49:44 +0000</pubDate>
		<dc:creator>andy</dc:creator>
				<category><![CDATA[Macintosh]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[iPhone]]></category>

		<guid isPermaLink="false">http://losingfight.com/blog/?p=347</guid>
		<description><![CDATA[For the uninformed, NSConference is a Mac developer&#8217;s conference put on by Scotty &#8220;The Scottster&#8221; Scott and his faithful sidekick, Tim &#8220;The Faithful Sidekick&#8221; Isted. They&#8217;re kind of the Batman and Robin of the Mac programming conference world, but have a slightly lower probability of bat-gassing you than the real dynamic duo.
This year they aren&#8217;t [...]]]></description>
			<content:encoded><![CDATA[<p>For the uninformed, <a href="http://www.nsconference.com">NSConference</a> is a Mac developer&#8217;s conference put on by Scotty &#8220;The Scottster&#8221; Scott and his faithful sidekick, Tim &#8220;The Faithful Sidekick&#8221; Isted. They&#8217;re kind of the Batman and Robin of the Mac programming conference world, but have a slightly lower probability of bat-gassing you than the real dynamic duo.</p>
<p>This year they aren&#8217;t content with bringing conference justice to only the UK, so they&#8217;re branching out to the good &#8216;ole U.S. of A. by way of Atlanta, GA. To help you decide which conference you should attend, US or Europe, I&#8217;ve prepared the following quiz:</p>
<ol>
<li>
<p>What is your opinion of Seattle?</p>
<ol>
<li>It&#8217;s nice, but isn&#8217;t nearly rainy or dreary enough.</li>
<li>I like their coffee.</li>
<li>The residents have too many teeth.</li>
</ol>
</li>
<li>
<p>Describe your driving habits</p>
<ol>
<li>I like to drive on the left side of the road.</li>
<li>I like to drive on both sides of the road.</li>
<li>I like to merge right six lanes without signaling while going 147 mph on the off ramp and giving the finger with both hands in my black Camaro.</li>
</ol>
</li>
<li>
<p>The pinnacle of human achievement is&#8230;</p>
<ol>
<li><a href="http://en.wikipedia.org/wiki/Tea_(meal)">Afternoon tea</a></li>
<li>Sliced bread</li>
<li><a href="http://en.wikipedia.org/wiki/Hee_Haw">Hee Haw</a></li>
</ol>
</li>
</ol>
<p>Scoring: Give yourself -1 points for any <strong>1</strong> answer, 0 points for any <strong>2</strong> answer, and 1 point for any <strong>3</strong> answer.</p>
<p>If you scored is less than zero, you should attend NSConference Europe; if greater than zero, NSConference USA. If you scored exactly zero, you are truly a cultured individual and should attend both.</p>
<p>Personally, I&#8217;ll be attending both, and not just because of peer pressure and insightful quizzes. I&#8217;ll be presenting a talk on how to implement a watercolor brush using Core Image and OpenCL and maybe some duct tape. If you enjoy the graphics articles that I post here, you&#8217;ll probably enjoy my presentation. If not, I hear Steve &#8220;I&#8217;m Batman&#8221; Scott does a mean <a href="http://en.wikipedia.org/wiki/Adam_West">Adam West</a> impression.</p>
]]></content:encoded>
			<wfw:commentRss>http://losingfight.com/blog/2009/12/15/nsconference-2010-quiz/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Implementing AppleScript Recordability</title>
		<link>http://losingfight.com/blog/2009/11/02/implementing-applescript-recordability/</link>
		<comments>http://losingfight.com/blog/2009/11/02/implementing-applescript-recordability/#comments</comments>
		<pubDate>Tue, 03 Nov 2009 05:13:59 +0000</pubDate>
		<dc:creator>andy</dc:creator>
				<category><![CDATA[Macintosh]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://losingfight.com/blog/?p=340</guid>
		<description><![CDATA[For one of my side projects that I&#8217;m currently working on, I decided to implement an AppleScript interface. Designing and implementing one wasn&#8217;t that bad, although Apple&#8217;s AppleScript documentation was sometimes confusing. Fortunately, CocoaDev has a good overview on how to implement AppleScript support.
However, one of my frustrations with working with other app&#8217;s AppleScript interfaces [...]]]></description>
			<content:encoded><![CDATA[<p>For one of my side projects that I&#8217;m currently working on, I decided to implement an AppleScript interface. Designing and implementing one wasn&#8217;t <em>that</em> bad, although <a href="http://www.red-sweater.com/blog/195/we-need-a-hero">Apple&#8217;s AppleScript documentation was sometimes confusing</a>. Fortunately, CocoaDev has a good <a href="http://www.cocoadev.com/index.pl?HowToSupportAppleScript">overview on how to implement AppleScript support</a>.</p>
<p>However, one of my frustrations with working with other app&#8217;s AppleScript interfaces was trying to figure out how the interface was intended to be used. Sure, the AppleScript Editor would show me all the actions and classes, but it isn&#8217;t always obvious how things are supposed to fit together. Something that would help in these cases is AppleScript recording. I could record the app performing the actions I cared about, then examine how the app itself used the actions and classes. Unfortunately, it seems like only the Finder and <a href="http://www.barebones.com/products/bbedit/">BBEdit</a> ever got around to implementing AppleScript recording.</p>
<p>In the hopes of increasing the number of apps with AppleScript recordability, I&#8217;m going to document my approach to implementing it. For brevity, I&#8217;ll assume you&#8217;ve already an AppleScript interface implemented for your Cocoa app.</p>
<h2>Thinking Big Design Thoughts</h2>
<p>If you have an AppleScript interface for your application, you may think of your app architecture as something like this:</p>
<p><img src="/blog/wp-content/uploads/2009/11/TraditionalModel1.png" alt="Traditional Model for Implementing AppleScript" width="301" height="246" /></p>
<p>Here your AppleScript interface and graphical interface are independent peers, and both modify your model classes directly to accomplish your application&#8217;s tasks. Each interface is separate and largely ignorant of the other.</p>
<p>However, when implementing AppleScript recordability it is helpful to think about your app&#8217;s architecture in a different way:</p>
<p><img src="/blog/wp-content/uploads/2009/11/RecordableModel1.png" alt="Recordable Model for Implementing AppleScript" width="300" height="294" /></p>
<p>In this case the GUI is dependent on, and implemented in terms of, the AppleScript interface. The general guideline is that anything the GUI does that mutates or alters the model goes through the AppleScript interface. However, if the GUI simply needs to access or get information from the model, it would go directly to the model, not through the AppleScript interface. Accessing the model can happen at seemingly random times to the user, and spamming the AppleScript Editor with these accesses when recording only confuses the user.</p>
<p>Suppose an application has table view and a button that deletes the currently selected item in that table view. The table view data source would be implemented the standard way, simply going directly to the model, bypassing the AppleScript layer. However, the delete button, since it alters the model, would be implemented by invoking the AppleScript delete command on the object represented by the current table row.</p>
<p>This design has some other benefits besides recordability. Notably it helps test your AppleScript interface design and implementation. If you find that implementing a GUI feature in terms of your AppleScript interface is impossible or difficult, congratulations, you found a bug! Also, merely testing your GUI also exercises your AppleScript interface. It is not a replacement for testing your AppleScript interface explicitly, but it certainly helps.</p>
<h2>Implementation Hardships</h2>
<p>Everything I&#8217;ve talked about so far hasn&#8217;t been all that novel, and probably has been met with large bucket fulls of &#8220;well, duh&#8221;s by anyone who&#8217;s ever looked into implementing AppleScript recording. The problem isn&#8217;t thinking about how to design in recordability, but actually implementing recordability.</p>
<p>As things stand now, doing something as simple as &#8220;invoking the AppleScript delete command on the object represented by the current table row&#8221; is incredibly involved and painful. You have to manually build up the AppleEvent that represents the delete command and the target model object using functions like <var>AEBuildAppleEvent</var> or classes like <var>NSAppleEventDescriptor</var>. Then you have to remember to target your app by specifying the kCurrentProcess process serial number (specifying kCurrentProcess as the ProcessSerialNumber is currently the only way to enable recording. Bundle identifiers, urls, and pid_t&#8217;s do not work.), and parse the AppleEvent you get back into something useful. You&#8217;d have to do this for <strong>every</strong> property or method on your model object you want exposed for recordability.</p>
<h3>Dreaming of Ponies</h3>
<p>The thing is, the Cocoa runtime has a lot of the AppleScript information from your SDEF file at its disposal and could theoretically generate these interfaces for you. In the ideal hypothetical situation, invoking the delete action via AppleScript inside your app could be as simple as:</p>
<p><code></p>
<pre>
// Suppose ONEmployee is our model object, with the appropriate AppleScript interface implemented
ONEmployee *employee = [_employees objectAtIndex:0];
ONEmployeeASProxy *proxy = employee.appleScriptInterface;
[proxy delete];
</pre>
<p></code></p>
<p>Here, any object that implemented the <var>objectSpecifier</var> method for AppleScript support would automatically get an <var>appleScriptInterface</var> property. The object returned by <var>appleScriptInterface</var> would be a proxy object implementing the same methods and properties as the original object. The proxy object would implement these methods by building up the appropriate AppleEvents, sending them, and parsing the resulting event back into a usable object.</p>
<p>Apple actually gets tantalizingly close to this with the Scripting Bridge. Outside users of your app can run your SDEF file through the <a href="http://developer.apple.com/mac/library/documentation/Darwin/Reference/ManPages/man1/sdp.1.html">sdp</a> command line tool and get a nice Objective-C interface of proxy objects that build, send, and parse AppleEvents to and from your app. However, there is not currently a way tell these proxy objects to target kCurrentProcess, or to initialize one of the proxy objects by passing in a model object that implements <var>objectSpecifier</var>. (I&#8217;ve written this up as <a href="rdar://problem/7359646">rdar://problem/7359646</a>.)</p>
<h3>Harsh Reality</h3>
<p>Since I didn&#8217;t want to wait on Apple to extend the Scripting Bridge to make my life easier, I decided to write a couple of classes to help out. You can <a href="/blog/wp-content/uploads/2009/11/ASObject.zip" title="ASObject.zip">download the code here</a>. The code is under the <a href="/blog/2007/09/05/license-for-sample-code/">MIT license</a>.</p>
<p>The classes work similar to the <var>SBObject</var> and <var>SBElementArray</var> classes in the Scripting Bridge framework. Using these classes, you can invoke the delete AppleScript method like so:</p>
<p><code></p>
<pre>
// Suppose ONEmployee is our model object, with the appropriate AppleScript interface implemented
ONEmployee *employee = [_employees objectAtIndex:0];
[ASObj(employee) invokeCommand:@"delete"];
</pre>
<p></code></p>
<p>The <var>ASObj</var> function creates an <var>ASObject</var> proxy object for any NSObject that implements <var>objectSpecifier</var>. <var>invokeCommand</var> takes care of marshalling the parameters into an AppleEvent, sending it, and unmarshalling the return value into an NSObject. The name of the command is the name of the name used in AppleScript, not the Cocoa implementation.</p>
<p><var>invokeCommand</var> can take parameters, although it gets more tricky:</p>
<p><code></p>
<pre>
ONEmployee *employee = [_employees objectAtIndex:0];
[ASObj(employee) invokeCommand:@"giveRaise" with:[NSNumber numberWithInt:10], @"Percent", nil];
</pre>
<p></code></p>
<p>First, the parameters must be named (it&#8217;s not done by parameter order), and those names must match the Cocoa Key in the SDEF, not the user visible parameter name. Second, the marshalling code (from random NSObjects to AppleEvents) is a bit sparse. I&#8217;ve only added code for the types I needed for my project. If you use it, you may need to add support for other types. The same goes for return values; I only added support for the types that I use.</p>
<p><var>ASObject</var> also has support for properties. For example this marks an employee as exempt:</p>
<p><code></p>
<pre>
ONEmployee *employee = [_employees objectAtIndex:0];
[ASObj(employee) setObject:[NSNumber numberWithBool:YES] forProperty:@"isExempt"];
</pre>
<p></code></p>
<p>The same type restrictions for parameters apply to properties as well. The Cocoa Key for the property must be used here, the same as parameters.</p>
<p>Elements also have basic support, which is where the <var>ASElementArray</var> comes in. Right now the only interesting thing to do with an element array is retrieve a reference to a specific element:</p>
<p><code></p>
<pre>
ONEmployee *employee = [_employees objectAtIndex:0];
ASObject *dependent = [[ASObj(employee) elementForKey:@"dependents"] objectAtIndex:0];
[dependent setObject:[NSNumber numberWithBool:YES] forProperty:@"insured"];
</pre>
<p></code></p>
<p>Unlike other methods, <var>ASElementArray</var>&#8217;s <var>objectAtIndex</var> does not execute an AppleEvents or otherwise take any actions. Instead it constructs an object specifier (i.e. an ASObject) for the given element.</p>
<p>The code is still somewhat rough and incomplete, but should help with anyone wanting to implement AppleScript recording. If nothing else, it should serve as a starting point or sample code for anyone rolling their own solution.</p>
]]></content:encoded>
			<wfw:commentRss>http://losingfight.com/blog/2009/11/02/implementing-applescript-recordability/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<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>I support Stepwise</title>
		<link>http://losingfight.com/blog/2007/12/21/i-support-stepwise/</link>
		<comments>http://losingfight.com/blog/2007/12/21/i-support-stepwise/#comments</comments>
		<pubDate>Sat, 22 Dec 2007 06:19:50 +0000</pubDate>
		<dc:creator>andy</dc:creator>
				<category><![CDATA[Macintosh]]></category>
		<category><![CDATA[Personal]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://www.losingfight.com/blog/2007/12/21/i-support-stepwise/</guid>
		<description><![CDATA[It has come to my attention that Scott Anguish has made the decision to pull Stepwise offline. You can read all the details at Stepwise, but the basic gist of it is, someone took some of Scott&#8217;s articles, republished them without permission, and misrepresented Scott&#8217;s feelings about the direction of Mac OS X.
Although I&#8217;m not [...]]]></description>
			<content:encoded><![CDATA[<p>It has come to my attention that Scott Anguish has made the decision to pull <a href="http://www.stepwise.com">Stepwise</a> offline. You can read all the details at <a href="http://www.stepwise.com">Stepwise</a>, but the basic gist of it is, someone took some of Scott&#8217;s articles, republished them without permission, and misrepresented Scott&#8217;s feelings about the direction of Mac OS X.</p>
<p>Although I&#8217;m not a lawyer and can&#8217;t offer any legal advice or support, I can do something. First, I can express my displeasure at someone who claims to be part of the Mac developer community treating Stepwise, or any other developer for that matter, in this manner. I can ask Rixstep to remove the copyrighted material. Finally, I can inform the audience of this blog about what&#8217;s going on.</p>
<p>Scott, via Stepwise, has been instrumental in my journey with Mac OS X programming. When I was first getting acquainted with Cocoa in the OS X Beta days, I often found myself over at Stepwise, checking out the tutorials. When I began writing tutorials on this blog, Scott would link to some of them. I often used Stepwise as a barometer as to how relevant my posts were.</p>
<p>I hope that Rixstep will relent, and remove the offending material. It would be best for the Mac developer community if Stepwise came back.</p>
]]></content:encoded>
			<wfw:commentRss>http://losingfight.com/blog/2007/12/21/i-support-stepwise/feed/</wfw:commentRss>
		<slash:comments>2</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>Acorn 1.0 is out</title>
		<link>http://losingfight.com/blog/2007/09/10/acorn-10-is-out/</link>
		<comments>http://losingfight.com/blog/2007/09/10/acorn-10-is-out/#comments</comments>
		<pubDate>Mon, 10 Sep 2007 08:25:13 +0000</pubDate>
		<dc:creator>andy</dc:creator>
				<category><![CDATA[Macintosh]]></category>

		<guid isPermaLink="false">http://www.losingfight.com/blog/2007/09/10/acorn-10-is-out/</guid>
		<description><![CDATA[Gus just released a new bitmap editor, Acorn. You should go check it out.
I had the pleasure of using Acorn for the past couple of weeks, and I&#8217;m quite happy with the direction Gus is taking it. It is focused on staying simple, streamlined, and easy to use.
There are no revolutionary or experimental tools in [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://gusmueller.com/blog/">Gus</a> just released a new bitmap editor, <a href="http://flyingmeat.com/acorn/">Acorn</a>. You should go check it out.</p>
<p>I had the pleasure of using Acorn for the past couple of weeks, and I&#8217;m quite happy with the direction Gus is taking it. It is focused on staying simple, streamlined, and easy to use.</p>
<p>There are no revolutionary or experimental tools in Acorn, just streamlined versions of the ones you would expect to find in a bitmap editor. The focus is not on glitz and glamour and the application itself, but on staying out of user&#8217;s way so get he or she can get the job done. i.e. Acorn is trying to evolve the user interface for bitmap editors.</p>
<p><img src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/Picture 3.png" alt="Acorn 1.0" border="0" width="330" height="468" /></p>
<p>One of the interface features that I was first struck by was the all-in-one tools window. To be honest, my initial gut reaction was: &#8220;What was Gus thinking?&#8221; However, after using it for a while, I dig it. It keeps everything together in one place, so after selecting a tool I don&#8217;t have to move my mouse to the other side of the screen to configure it. Or to select the layer I want to work on. While the all-in-one window wouldn&#8217;t work in the 800lb gorilla with all its panels, it works beautifully in Acorn.</p>
<p>I&#8217;m not a designer, so I don&#8217;t feel qualified to give Acorn a full review. However, I&#8217;ve worked on graphics applications before, and I&#8217;ve tried Acorn, and I like it, for all that&#8217;s worth. Go take it for a spin.</p>
]]></content:encoded>
			<wfw:commentRss>http://losingfight.com/blog/2007/09/10/acorn-10-is-out/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>License for sample code</title>
		<link>http://losingfight.com/blog/2007/09/05/license-for-sample-code/</link>
		<comments>http://losingfight.com/blog/2007/09/05/license-for-sample-code/#comments</comments>
		<pubDate>Wed, 05 Sep 2007 19:10:18 +0000</pubDate>
		<dc:creator>andy</dc:creator>
				<category><![CDATA[Macintosh]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://www.losingfight.com/blog/2007/09/05/license-for-sample-code/</guid>
		<description><![CDATA[I&#8217;ve received a few inquires about the licensing for the sample code that I&#8217;ve presented in the &#8220;how to implement&#8230;&#8221; graphics articles. I thought it might be prudent to cover that here publicly.
The intent of the articles and sample code is to increase the public knowledge of how tools in bitmap editors work. It appeared [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve received a few inquires about the licensing for the sample code that I&#8217;ve presented in the &#8220;how to implement&#8230;&#8221; graphics articles. I thought it might be prudent to cover that here publicly.</p>
<p>The intent of the articles and sample code is to increase the public knowledge of how tools in bitmap editors work. It appeared to me that every author of a graphics editor had to go out and reinvent the wheel somewhat, simply because not all the basic tools were publicly documented. The sample code is intended not only to satisfy idle curiosity, but to improve graphics editors, specifically for the Mac. i.e. I&#8217;m hoping to see Mac graphics apps with these features, if they don&#8217;t have them already.</p>
<p>To that end, the license needs to be permissive, so that it can be used in a wide variety of applications.</p>
<p>Privately, I think of the sample code as public domain. Optimally, that&#8217;s how I&#8217;d want it. However, because of the copyright laws in the US, it&#8217;s very difficult to make it so.</p>
<p>Instead, I&#8217;ve chosen the <a href="http://en.wikipedia.org/wiki/MIT_License">MIT license</a>, which will hopefully be lenient enough:</p>
<blockquote><p>
Copyright (c) 2007 Andrew Finnell</p>
<p>Permission is hereby granted, free of charge, to any person<br />
obtaining a copy of this software and associated documentation<br />
files (the &#8220;Software&#8221;), to deal in the Software without<br />
restriction, including without limitation the rights to use,<br />
copy, modify, merge, publish, distribute, sublicense, and/or sell<br />
copies of the Software, and to permit persons to whom the<br />
Software is furnished to do so, subject to the following<br />
conditions:</p>
<p>The above copyright notice and this permission notice shall be<br />
included in all copies or substantial portions of the Software.</p>
<p>THE SOFTWARE IS PROVIDED &#8220;AS IS&#8221;, WITHOUT WARRANTY OF ANY KIND,<br />
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES<br />
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND<br />
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT<br />
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,<br />
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING<br />
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR<br />
OTHER DEALINGS IN THE SOFTWARE.
</p></blockquote>
<p>If you&#8217;d like to use another license, just let me know. In general I grant permission to use the code in any application, whether it be open source or closed.</p>
<p>One final note: since I&#8217;m writing the articles with the hope that some of the tools will wind up in real applications, I&#8217;d like to know if they do. If you use some of the code here, I would appreciate it if you would shoot me an email letting me know. This isn&#8217;t required, but I would be grateful.</p>
]]></content:encoded>
			<wfw:commentRss>http://losingfight.com/blog/2007/09/05/license-for-sample-code/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>How to implement smudge and stamp tools</title>
		<link>http://losingfight.com/blog/2007/09/04/how-to-implement-smudge-and-stamp-tools/</link>
		<comments>http://losingfight.com/blog/2007/09/04/how-to-implement-smudge-and-stamp-tools/#comments</comments>
		<pubDate>Wed, 05 Sep 2007 04:48:25 +0000</pubDate>
		<dc:creator>andy</dc:creator>
				<category><![CDATA[Macintosh]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://www.losingfight.com/blog/2007/09/04/how-to-implement-smudge-and-stamp-tools/</guid>
		<description><![CDATA[I like the smudge tool because, like the brush, it has a real world analog, which means it&#8217;s a bit easier for new users to figure out how it works. Just about everyone has played with finger paints before, and knows what happens when you drag your finger through paint.
I originally thought the smudge tool [...]]]></description>
			<content:encoded><![CDATA[<p>I like the smudge tool because, like the brush, it has a real world analog, which means it&#8217;s a bit easier for new users to figure out how it works. Just about everyone has played with finger paints before, and knows what happens when you drag your finger through paint.</p>
<p>I originally thought the smudge tool would be rather complex. I was pleasantly surprised that it was quite simple to implement. So, as a bonus, I went ahead and threw in a stamp tool implementation too.</p>
<h2>Overview</h2>
<p>Your basic smudge tools smears the paint as if you dragged a clean brush across it. For example if you start with simple image below:</p>
<p><img src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/blue_stripe.png" alt="Image that is blue on one side, white on the other" /></p>
<p>then drag the smudge tool from the left to the right (i.e. from the blue to the white), your get:</p>
<p><img src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/blue_stripe_smudge.png" alt="Image that is blue on one side, white on the other, with a smudge" /></p>
<p>How much of a smudge you get is determined by how much pressure is applied.</p>
<p>The idea behind a smudge tool is that as you drag a brush through paint, both the canvas and brush swap paint. The brush picks up paint from the canvas then deposits it elsewhere on the canvas. If you&#8217;re familiar with bitmap editors, that almost sounds like the stamp brush tool, which it is, so as a bonus we&#8217;ll cover how to implement a stamp brush tool.</p>
<h3>Algorithm</h3>
<p>When researching how to implement a smudge tool, I found that just about everyone has a different implementation. They range from the very simple, to the very complex which are trying to accurately simulate the physical characteristics of a brush dragged across paint. The algorithm that I&#8217;ll present here definitely falls on the simple side, but it is reasonably close to what popular graphics editors implement.</p>
<p>The first thing to remember is that the smudge tool is just a special kind of brush. As a result, it is implemented in a very similar fashion to bitmap brush. In fact, if you haven&#8217;t yet already read the article <a href="/blog/2007/08/18/how-to-implement-a-basic-bitmap-brush/">&#8220;How to implement a basic bitmap brush&#8221;</a>, you should do that now. The main difference between the simple bitmap brush and the smudge tool is how it renders a single stamp.</p>
<p>Instead of using a single color in the shape of the brush tip, the smudge tool will use a piece of the canvas, in the shape of the brush tip, as the stamp image. It locates the piece of canvas to be used as a brush image by looking at the last location it rendered a stamp. It grabs the canvas at that point and uses it as the brush tip.</p>
<p>The only other deviation from the usual stamping algorithm is the stamp spacing. For most brushes, the stamps are spaced 1/4 of brush tip wide. However, since the smudge tool takes its brush tip from the previous render location, it requires a spacing of one pixel. If we were to use the standard spacing, we&#8217;d end up with a choppy smudge, like so:</p>
<p><img src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/blue_strip_smudge_5px.png" alt="Image that is blue on one side, white on the other, with a choppy smudge" /></p>
<p>That&#8217;s it for our simple smudge algorithm. If we wanted something more complex, we could create a separate drawing context for the brush tip image, and have it actually accumulate paint from the canvas. We could also have the brush tip textured, so it picked up paint more in the raised areas of the brush.</p>
<h2>Code architecture</h2>
<p>As always, I have provided sample code to demonstrate the smudge tool. It is heavily based on the sample code from the <a href="/blog/2007/08/18/how-to-implement-a-basic-bitmap-brush/">brushes article</a>, so we will only cover the differences between the smudge tool and a normal brush. There are four classes in the sample code: <var>MyDocument</var>, <var>CanvasView</var>, <var>Canvas</var>, and <var>Smudge</var>.</p>
<p><var>MyDocument</var> is derived from NSDocument, and exists only to load up a user selected NSImage and hand it off to <var>CanvasView</var>. It is almost identical to the <var>MyDocument</var> used in the <a href="/blog/2007/08/28/how-to-implement-a-magic-wand-tool/">magic wand article</a>, plus it doesn&#8217;t do anything terribly exciting, so we&#8217;ll ignore it from here on out.</p>
<p><var>CanvasView</var> is a NSView derived class that renders the canvas to the screen, and catches mouse events to pass off to the <var>Smudge</var> class to handle. It also acts like a moderator between the <var>Smudge</var> and <var>Canvas</var> classes. It is nearly identical to the <var>CanvasView</var> class used in the <a href="/blog/2007/08/18/how-to-implement-a-basic-bitmap-brush/">brushes article</a>, so we&#8217;ll ignore it the rest of the article.</p>
<p>The <var>Canvas</var> class represents the canvas the user draws onto. It can render itself into a NSGraphicsContext, and provides drawing primitives to render a simple brush stamp and a line of stamps. Although the stamping algorithm hasn&#8217;t changed from the <a href="/blog/2007/08/18/how-to-implement-a-basic-bitmap-brush/">brushes article</a>, the <var>Canvas</var> class does use a different backing implementation and parameters, which warrants a quick revisit in this article.</p>
<p>The <var>Smudge</var> class represents a brush that can be used to smear paint across the canvas. Since it is a type of brush, it is heavily based on the <var>Brush</var> class used in the <a href="/blog/2007/08/18/how-to-implement-a-basic-bitmap-brush/">brushes article</a>. In this article, we will only cover what the differences are. In this code, the smudge tool not only processes the user&#8217;s mouse events, it is also responsible from rendering a single stamp onto the canvas.</p>
<p>We&#8217;ll tackle the changes in the <var>Canvas</var> class first, then dig into the <var>Smudge</var> class.</p>
<h2>Canvas</h2>
<p>Unlike its previous incarnation, the <var>Canvas</var> class here is backed by a CGLayerRef, instead of a CGBitmapContext. This allows both greater ease of use when stamping from the canvas onto itself, and better performance. There is no init method on this class because it requires an NSImage, which will be specified later. There is a dealloc method, which simply releases the CGLayerRef.</p>
<h3>Initialization</h3>
<p>The initialization happens when the setImage method is called by the <var>CanvasView</var> class. It is responsible for creating a CGLayerRef of the proper size and rendering the NSImage parameter into the layer:</p>
<p><code></p>
<pre>
- (void) setImage:(NSImage *)image view:(NSView *)view
{
	// First free up the previous layer, in case the new one is a new size
	CGLayerRelease(mLayer);
	mLayer = nil;

	// Next, create a new layer the size of the image. For performance reasons,
	//	we want to base our layer off of the window context.
	NSGraphicsContext* windowContext = [NSGraphicsContext graphicsContextWithWindow:[view window]];
	CGContextRef context = [windowContext graphicsPort];

	NSSize size = [image size];
	mLayer = CGLayerCreateWithContext(context, CGSizeMake(size.width, size.height), nil);
</pre>
<p></code></p>
<p>Although in the sample code we never call setImage more than once, we cover that possibility here by freeing up any previously existing layer. Note that we pass in the NSView we&#8217;ll eventually render into as a parameter. This is for the purpose of creating the CGLayerRef. The layer needs to be based on a CGContextRef similar to the one that it will eventually be rendered into, in order to get the best performance. To that end, we create grab the NSGraphicsContext for the window the view is inside. We then create the layer based on that and the size of the image.</p>
<p>Next, we need to render the NSImage we got as a parameter into our layer:</p>
<p><code></p>
<pre>
	// Pull out the NSGraphicsContext from the layer and focus it so we can
	//	use Cocoa class to draw into it.
	CGContextRef cgLayerContext = CGLayerGetContext(mLayer);
	NSGraphicsContext* layerContext = [NSGraphicsContext graphicsContextWithGraphicsPort:cgLayerContext flipped:YES];

	[NSGraphicsContext saveGraphicsState];
	[NSGraphicsContext setCurrentContext:layerContext];

	// Some images might have transparency, so fill the background with an opaque
	//	white. Real apps would probably do a checkerboard pattern.
	[[NSColor whiteColor] set];
	[NSBezierPath fillRect:NSMakeRect(0, 0, size.width, size.height)];

	// Draw the image, with no scaling
	[image drawAtPoint:NSMakePoint(0, 0) fromRect:NSMakeRect(0, 0, size.width, size.height) operation:NSCompositeSourceOver fraction:1.0];

	[NSGraphicsContext restoreGraphicsState];
}
</pre>
<p></code></p>
<p>We&#8217;re going to use Cocoa drawing classes to render the image, so we need to go from our CGLayerRef to a focused NSGraphicsContext. Fortunately, CGLayerRef has a method that will return a CGContextRef, and NSGraphicsContext has a constructor that will take a CGContextRef. The first bit of drawing we do is to render a white background. This is for the case of a transparent image. Finally, we render the image into the current NSGraphicsContext, which happens to our layer. Our <var>Canvas</var> object is now initialized and ready to be used.</p>
<h3>Drawing the canvas onto a view</h3>
<p>After initialization, the first thing we&#8217;ll be asked to do in the <var>Canvas</var> class is to render. This is covered in drawRect:</p>
<p><code></p>
<pre>
- (void)drawRect:(NSRect)rect inContext:(NSGraphicsContext*)context
{
	if ( mLayer == nil )
		return;

	// Very straight forward: just draw our layer in the given context at 0, 0
	CGContextRef cgContext = [context graphicsPort];
	CGContextDrawLayerAtPoint(cgContext, CGPointMake(0, 0), mLayer);
}
</pre>
<p></code></p>
<p>This is almost too trivial to cover. If we have created a layer, we render it into the NSGraphicsContext passed in. The End.</p>
<h3>Rendering a line of stamps</h3>
<p>As we covered earlier, the stamping algorithm hasn&#8217;t changed from the <a href="/blog/2007/08/18/how-to-implement-a-basic-bitmap-brush/">brushes article</a>, although one of the parameters has, and a constant derived from that parameter. Since the line stamping is rather long, we&#8217;ll only review the part that has changed, which, fortunately for us, is only the function signature and the first line of code:</p>
<p><code></p>
<pre>
- (float)stamp:(Smudge *)brush from:(NSPoint)startPoint to:(NSPoint)endPoint leftOverDistance:(float)leftOverDistance
{
	// Set the spacing between the stamps.
	float spacing = [brush spacing]; // Ask the brush
</pre>
<p></code></p>
<p>Instead of passing in an image to be used for stamping, we now pass in the smudge tool. This is because the <var>Smudge</var> class is now responsible for rendering a single stamp. Also, we want the spacing for the stamping to be configurable based on the brush. So instead of computing the stamp spacing here in the <var>Canvas</var> class, we ask the brush for it.</p>
<p>The rest of the method is the same as before.</p>
<h3>Rendering a single stamp</h3>
<p>The last part of the <var>Canvas</var> class to cover is the rendering of a single stamp, which as noted above, is no longer handled in the <var>Canvas</var> class:</p>
<p><code></p>
<pre>
- (void)stamp:(Smudge *)brush at:(NSPoint)point
{
	// Just pass through to the brush to ask it to render. Give it the layer
	//	that backs the canvas, and the point to render at.
	[brush render:mLayer at:point];
}
</pre>
<p></code></p>
<p>We just hand the rendering of a stamp off to our <var>Smudge</var> class. We provide it with the canvas layer, so that it can pull pixels from it and render into it.</p>
<p>As you can see, the main ideas of the <var>Canvas</var> class didn&#8217;t change, but a bit of the implementation did, in order to make it more flexible.</p>
<h2>Smudge</h2>
<p>The <var>Smudge</var> class contains most of the interesting code. Like its predecessor, <var>Brush</var>, it tells the <var>Canvas</var> class where to render lines and single points. However, it is also responsible for rendering a single stamp. Since <var>Smudge</var> and its predecessor have so much in common, we&#8217;ll only discuss where they differ.</p>
<h3>Parameters</h3>
<p>The smudge tool has some new parameters that weren&#8217;t in the basic brush class. They are initialized in init:</p>
<p><code></p>
<pre>
- (id) init
{
	self = [super init];

	if ( self ) {
		// Set the size of the brush. A radius of 10 means a 20 pixel wide brush
		mRadius = 10.0;

		// Create the shape of the tip of the brush. Code currently assumes the bounding
		//	box of the shape is square (height == width)
		mShape = CGPathCreateMutable();
		CGPathAddEllipseInRect(mShape, nil, CGRectMake(0, 0, 2 * mRadius, 2 * mRadius));
		//CGPathAddRect(mShape, nil, CGRectMake(0, 0, 2 * mRadius, 2 * mRadius));

		// Set the initial smudge color, that starts out on the brush. May be nil,
		//	if you don't want a smudge color.
#if 1
		mColor = nil;
#else
		CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
		float components[] = { 0.0, 0.0, 1.0, 1.0 }; // I like blue
		mColor = CGColorCreate(colorspace, components);
		CGColorSpaceRelease(colorspace);
#endif

		// The "softness" of the brush edges
		mSoftness = 0.5;

		// The pressure at which to smudge. The more pressure, the more of a smudge
		//	will result.
		mPressure = 1.0;

		// Initialize variables that will be used during tracking
		mMask = nil;
		mLastPoint = NSZeroPoint;
		mLeftOverDistance = 0.0;
		mLastRenderPoint = NSZeroPoint;
	}

	return self;
}
</pre>
<p></code></p>
<p>There are two new parameters: <var>mPressure</var> and <var>mColor</var>. Although <var>mColor</var> was also in the brush class, its function is different for the smudge tool.</p>
<ul>
<li><var>mPressure</var> Pressure mimics how hard the user is pressing down on the canvas, and as a result, how much the paint is smeared. It has a range of 0.0 to 1.0, where 0.0 means the paint doesn&#8217;t smudge at all, and 1.0 means it smudges a lot.
<p>		Examples:</p>
<ul>
<li>mPressure = 0.1, <img style="vertical-align: bottom;" src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/blue_strip_pressure_1.png" alt="Blue stripe, smudged, 1% pressure" /></li>
<li>mPressure = 0.5, <img style="vertical-align: bottom;" src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/blue_stripe_pressure_50.png" alt="Blue stripe, smudged, 50% pressure" /></li>
<li>mPressure = 1.0, <img style="vertical-align: bottom;" src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/blue_stripe_pressure_100.png" alt="Blue stripe, smudged, 100% pressure" /></li>
</ul>
</li>
<li><var>mColor</var> Color is used to determine if the brush used for smudging is dirty. i.e. Does the brush start out with some paint already on it? If so, it leaves an initial smudge of the specified color. If <var>mColor</var> is nil, then the smudge tool simulates a clean brush.
<p>		Examples:</p>
<ul>
<li>mColor = [1.0, 0.0, 0.0], <img style="vertical-align: bottom;" src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/blue_stripe_smudge_red.png" alt="Blue stripe, smudged, red color" /></li>
<li> mColor = [0.0, 1.0, 0.0], <img style="vertical-align: bottom;" src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/blue_stripe_smudge_green.png" alt="Blue stripe, smudged, green color" /></li>
<li> mColor = [0.0, 0.0, 1.0], <img style="vertical-align: bottom;" src="http://www.losingfight.com/blog/wp-content/uploads/2007/09/blue_stripe_smudge_blue.png" alt="Blue stripe, smudged, blue color" /></li>
</ul>
</li>
</ul>
<p>The rest of the parameters are identical to those in the regular brush class.</p>
<h3>Creating the brush tip</h3>
<p>The job of creating the brush tip is a bit different in the smudge tool than the original brush tool. In the original brush we created a full ARGB image in the brush color with the correct transparency. Since our brush image is going to be pixels from the canvas, we just want the brush tip, <var>mMask</var>, to reflect the shape, or transparency, of the brush. So instead of a full color image, we create a grayscale image with no alpha that will act as a mask for our actual brush image. This allows us to impose our brush shape onto any random image.</p>
<p>The new createBrushTip method looks like:</p>
<p><code></p>
<pre>
- (CGImageRef) createShapeImage
{
	// Create a bitmap context to hold our brush image
	CGContextRef bitmapContext = [self createBitmapContext];

	CGContextSetGrayFillColor(bitmapContext, 1.0, 1.0);

	// The way we acheive "softness" on the edges of the brush is to draw
	//	the shape full size with some transparency, then keep drawing the shape
	//	at smaller sizes with the same transparency level. Thus, the center
	//	builds up and is darker, while edges remain partially transparent.

	// First, based on the softness setting, determine the radius of the fully
	//	opaque pixels.
	int innerRadius = (int)ceil(mSoftness * (0.5 - mRadius) + mRadius);
	int outerRadius = (int)ceil(mRadius);
	int i = 0;

	// The alpha level is always proportial to the difference between the inner, opaque
	//	radius and the outer, transparent radius.
	float alphaStep = 1.0 / (outerRadius - innerRadius + 1);

	// Since we're drawing shape on top of shape, we only need to set the alpha once
	CGContextSetAlpha(bitmapContext, alphaStep);

	for (i = outerRadius; i >= innerRadius; --i) {
		CGContextSaveGState(bitmapContext);

		// First, center the shape onto the context.
		CGContextTranslateCTM(bitmapContext, outerRadius - i, outerRadius - i);

		// Second, scale the the brush shape, such that each successive iteration
		//	is two pixels smaller in width and height than the previous iteration.
		float scale = (2.0 * (float)i) / (2.0 * (float)outerRadius);
		CGContextScaleCTM(bitmapContext, scale, scale);

		// Finally, actually add the path and fill it
		CGContextAddPath(bitmapContext, mShape);
		CGContextEOFillPath(bitmapContext);

		CGContextRestoreGState(bitmapContext);
	}

	// Create the brush tip image from our bitmap context
	CGImageRef image = CGBitmapContextCreateImage(bitmapContext);

	// Free up the offscreen bitmap
	[self disposeBitmapContext:bitmapContext];

	return image;
}
</pre>
<p></code></p>
<p>Note that createBitmapContext actually creates a grayscale bitmap context with no alpha that is filled to completely black. The only real difference in createBrushTip is that we set the color to white instead of a user specified color. In an image that is used as a mask, white means that a pixel is full opaque, while black means the pixel will be fully transparent.</p>
<h3>Rendering a single stamp</h3>
<p>The real meat of <var>Smudge</var> is where we render a single stamp of the brush into the canvas&#8217;s CGLayerRef. The render function starts out like the old single stamp method on <var>Canvas</var>:</p>
<p><code></p>
<pre>
- (void) render:(CGLayerRef)canvas at:(NSPoint)point
{
	// Grab the context for the canvas. No matter what, we're going to determine
	//	where the current brush stamp should go, then translate the context
	//	to that position.
	CGContextRef canvasContext = CGLayerGetContext(canvas);
	CGContextSaveGState(canvasContext);

	// So we can position the image correct, compute where the bottom left
	//	of the image should go, and modify the CTM so that 0, 0 is there.
	CGPoint bottomLeft = CGPointMake( point.x - CGImageGetWidth(mMask) * 0.5,
									  point.y - CGImageGetHeight(mMask) * 0.5 );
	CGContextTranslateCTM(canvasContext, bottomLeft.x, bottomLeft.y);
</pre>
<p></code></p>
<p>We grab the context off the layer, which is really our canvas, and apply an affine transform to it such that the origin is where we want to draw the bottom left of our brush image.</p>
<p>Next, we want to force the shape of our brush onto our brush image:</p>
<p><code></p>
<pre>
	// 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.
	CGContextClipToMask(canvasContext, CGRectMake(0, 0, CGImageGetWidth(mMask), CGImageGetHeight(mMask)), mMask);
</pre>
<p></code></p>
<p>We simply apply the image we created up in createBrushTip to the context as a clipping mask.</p>
<p>The last thing we need to do before actually rendering the brush image, is to deal with the <var>mPressure</var> parameter:</p>
<p><code></p>
<pre>
	// The pressure of the smudge is a one to one correspondance with the amount
	//	transparency of the brush stamp.
	CGContextSetAlpha(canvasContext, mPressure);
</pre>
<p></code></p>
<p>As you can see, the pressure is directly related to how transparent with render the brush image. If the user is applying a lot of pressure, then we render the brush image fully opaque. If the user applies less, then the brush image is rendered more transparent.</p>
<p>Now we&#8217;re ready to render the brush image:</p>
<p><code></p>
<pre>
	// If this is our first stamp, then we normally don't want to lay down any
	//	ink. That's because if we're smudging with a clean brush, we don't have
	//	any ink on the brush to lay down. Only after the initial stamp will we
	//	have the ink from the canvas to lay down.
	if ( !NSEqualPoints(mLastRenderPoint, NSZeroPoint) ) {
		// Based on the last render point that we keep track of, determine the
		//	source bounds.
		CGPoint sourceBottomLeft = CGPointMake( mLastRenderPoint.x - CGImageGetWidth(mMask) * 0.5,
												mLastRenderPoint.y - CGImageGetHeight(mMask) * 0.5 );

		// We pull straight from the canvas, and render directly onto the canvas. CGLayerRefs
		//	make this easy.
		CGContextDrawLayerAtPoint(canvasContext, CGPointMake(-sourceBottomLeft.x, -sourceBottomLeft.y), canvas);
	}
</pre>
<p></code></p>
<p>Notice that we check to see if this our first rendering the brush image in the tracking loop. This if statement works because both the mouse down and mouse up handlers reset the <var>mLastRenderPoint</var> member to NSZeroPoint.</p>
<p>If this is our first time, then there is no previous render point to use as our source image, so we don&#8217;t do anything. This matches the real world analog: until we move the brush, no smudging occurs.</p>
<p>If this is not our first time trying to stamp the brush image, then we actually stamp the image. We determine the bottom left coordinate of the previously rendered stamp, then use that as our source in the CGLayerRef. Since we have already applied our mask, the pixels from the layer are shaped just like our brush. As you may note, CGLayerRef makes it nice and easy to draw back into itself.</p>
<p>However, if this is our first time trying to render, that doesn&#8217;t necessarily mean we have nothing to do:</p>
<p><code></p>
<pre>
 	 else if ( mColor ) {
		// If this is our first stamp, and we have an initial color specified (i.e. the brush
		//	was dirty), we have to render the brush with that color only on the first
		//	stamp. The initial color might be carried to other pixels depending on
		//	how strong the pressure is.
		CGContextSetFillColorWithColor(canvasContext, mColor);
		CGContextFillRect(canvasContext, CGRectMake(0, 0, CGImageGetWidth(mMask), CGImageGetHeight(mMask)));
	}
</pre>
<p></code></p>
<p>This is the else clause for the if shown above. If this our first time rendering and we have a color specified, then we need to render it. This actually works the same as a normal brush would. We set the fill color to the same as the initial color and fill the entire rect. Since we have a mask applied, only the pixels in the brush shape are drawn.</p>
<p>Note that we only render the initial color on the first stamp. It will get smudged across the canvas on subsequent stamps, since the stamps are only one pixel apart.</p>
<p>The final part of rendering a single stamp is cleaning up, and remembering where we just stamped:</p>
<p><code></p>
<pre>
	CGContextRestoreGState(canvasContext);

	// Remember our last render point, so we know where to pull from
	mLastRenderPoint = point;
}
</pre>
<p></code></p>
<p>We update <var>mLastRenderPoint</var> so we can use it the next time we stamp.</p>
<p>The last little bit of the <var>Smudge</var> class that we need to examine is the spacing method:</p>
<p><code></p>
<pre>
- (float) spacing
{
	// Smudge has to be spaced by 1 pixel or we get jaggies
	return 1.0;
}
</pre>
<p></code></p>
<p>If you recall, the <var>Canvas</var> class calls back into the smudge tool to determine how far apart to space the stamps. In order to avoid a really choppy smudge, we return a spacing of 1 pixel.</p>
<h2>Stamp tool</h2>
<p>We haven&#8217;t really spoken about a stamp tool, but if you know what one is, you probably realized that it sounds a bit like the smudge tool in terms of implementation. Briefly, a stamp tool takes a location on the canvas and copies it to another point on the canvas. It is used to copy parts of an image exactly. The source pixels are determined by the user specifying an offset from the cursor for the stamp tool to pull from.</p>
<p>It will probably make more sense when we look at an example implementation. It is exactly the same as the <var>Smudge</var> class except that the spacing and render functions change.</p>
<p>First, the render function for a stamp tool:</p>
<p><code></p>
<pre>
- (void) render:(CGLayerRef)canvas at:(NSPoint)point
{
	// Grab the context for the canvas. No matter what, we're going to determine
	//	where the current brush stamp should go, then translate the context
	//	to that position.
	CGContextRef canvasContext = CGLayerGetContext(canvas);
	CGContextSaveGState(canvasContext);

	// So we can position the image correct, compute where the bottom left
	//	of the image should go, and modify the CTM so that 0, 0 is there.
	CGPoint bottomLeft = CGPointMake( point.x - CGImageGetWidth(mMask) * 0.5,
									  point.y - CGImageGetHeight(mMask) * 0.5 );
	CGContextTranslateCTM(canvasContext, bottomLeft.x, bottomLeft.y);

	// 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.
	CGContextClipToMask(canvasContext, CGRectMake(0, 0, CGImageGetWidth(mMask), CGImageGetHeight(mMask)), mMask);

	// Based on the user specified offset, determine the source bounds.
	CGPoint sourcePoint = CGPointMake(point.x + mOffset.x, point.y + mOffset.y);
	CGPoint sourceBottomLeft = CGPointMake( sourcePoint.x - CGImageGetWidth(mMask) * 0.5,
											sourcePoint.y - CGImageGetHeight(mMask) * 0.5 );

	// We pull straight from the canvas, and render directly onto the canvas. CGLayerRefs
	//	make this easy.
	CGContextDrawLayerAtPoint(canvasContext, CGPointMake(-sourceBottomLeft.x, -sourceBottomLeft.y), canvas);

	CGContextRestoreGState(canvasContext);
}
</pre>
<p></code></p>
<p>This should look really familiar. We set up the layer&#8217;s context exactly the same as the smudge tool, up to and including setting the brush tip as a mask. A stamp tool doesn&#8217;t have a pressure parameter, so we eliminate the line that monkeys with the alpha. Since we&#8217;re doing a straight replicate with the stamp tool, we always draw from the canvas layer as our source. The only difference is we don&#8217;t pull from our last rendered point. Instead we take our current point and apply the user specified offset to determine what we should use as our brush image.</p>
<p>The only other modification we have to make is to the spacing function:</p>
<p><code></p>
<pre>
- (float) spacing
{
	// Standard brush spacing
	return CGImageGetWidth(mMask) * 0.25;
}
</pre>
<p></code></p>
<p>All we&#8217;re doing here is returning the stamp spacing to the normal spacing. We don&#8217;t need the one pixel spacing of the smudge tool to avoid a choppy render, and a wider spacing gives us better performance.</p>
<p>The code for the stamp tool is not included in the downloadable sample code, but you should be able to copy paste the methods provided here into the <var>Smudge</var> class and have them work.</p>
<h2>Conclusion</h2>
<p>The smudge tool was surprisingly simple to implement. I had done a stamp tool previously, but not presented it, because I felt it was too simple to stand on its own. However, given that its implementation is similar to the smudge tool, I jumped at the chance to present it here.</p>
<p>There&#8217;s still plenty that could be improved on here. The smudge tool is begging for pressure sensitivity from a tablet. It could also be made more realistic by accumulating paint and using a textured brush.</p>
<p>Happy smudging!</p>
<p><a href="http://www.losingfight.com/blog/wp-content/uploads/2007/09/Smudge.zip" title="Smudge sample code">Download the sample code</a></p>
]]></content:encoded>
			<wfw:commentRss>http://losingfight.com/blog/2007/09/04/how-to-implement-smudge-and-stamp-tools/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
	</channel>
</rss>
