The Basics of Custom Patches

A Quartz Composer patch is similar to a routine in a traditional programming language—a patch is a base processing unit that executes and produces a result. A custom patch is one that you write by subclassing the QCPlugIn class. To make a custom patch available in the Quartz Composer patch library, you need to package it as a plug-in—an NSBundle object—and install in the appropriate directory. If you’d like, you can package more than one custom patch in a plug-in. You don’t need to create a user interface for a custom patch. When Quartz Composer loads your plug-in, it creates the user interface automatically for each custom patch in the plug-in so that custom patches have the same look in the workspace as the patches that are built in to Quartz Composer.

This chapter provides a discussion of custom patches and gives an overview of the tasks you need to perform to create a custom patch. You’ll see which aspects of your code give rise to the user interface for a custom patch. By the end of the chapter you should have a fairly good idea of how to get started writing your own custom patches. Then you can read Writing Processor Patches and Writing Consumer Patches to find out how to write specific kinds of patches.

A Close Look at the Patch User Interface

Patches in Quartz Composer all have the same basic look and feel, as you can see in Figure 1-1. The patch name is at the top of the patch, in the patch title bar. Input ports are on the left side of the patch and output ports are on the right side. Many provider patches don’t have input ports because they get data from an outside source, such as a mouse, keyboard, tablet, or MIDI controller. Consumer patches don’t have output ports because they render data to a destination. Processor patches have one or more input ports and one or more output ports.

Figure 1-1  A variety of Quartz Composer patches
A variety of Quartz Composer patches

The inspector provides access to the patch parameters. The Input Parameters pane contains the same parameters represented by the input ports. It provides the option to manually adjust input values instead of supplying values through the input ports. Compare the Teapot patch shown in Figure 1-1 with Figure 1-2. You’ll see that the ports match up with the input parameters.

Figure 1-2  The Input Parameters pane for the Teapot patch
The Input Parameters pane for the Teapot patch

Some patches also have a Settings pane, as shown in Figure 1-3, that provides support for configuring settings whose values either can’t be represented by one of the standard port data types (see Table 1-1) or that control advanced options.

Figure 1-3  The Settings pane for the Date Formatter patch
The Settings pane for the Date Formatter patch

From Custom Patch Code to Patch User Interface

A custom patch is a subclass of the QCPlugIn class. Quartz Composer creates the input and output ports on a custom patch from dynamic Objective-C properties of the subclass. Properties whose name begins with input (and are one of the supported types) will appear as an input port on the patch. Properties whose name begins with output (and are one of the supported types) will appear as an output port on the patch. Listing 1-1 shows code that creates a subclass of QCPlugIn and declares one input and one output parameter. As you read this section you’ll see how that code relates to the resulting custom patch in Quartz Composer.

Listing 1-1  The subclass and properties for a custom string-processing patch

@interface iPatchPlugIn : QCPlugIn
{
}
 
@property(assign) NSString* inputString;
@property(assign) NSString* outputString;
 
@end

You can implement the attributesForPropertyPortWithKey: method to map the name of each property (such as inputString or outputString) to a key-value pair for the corresponding patch parameter. Listing 1-2 shows an implementation of this method for the properties declared in Listing 1-1. For each property, the method returns a dictionary that contains the port name and its default value, if any. This example returns the port name Name for the property that’s named inputString. It returns iName for the property that’s named outputString.

Listing 1-2  A routine that returns attributes for property ports

+ (NSDictionary*) attributesForPropertyPortWithKey:(NSString*)key
{
    if([key isEqualToString:@"inputString"])
        return [NSDictionary dictionaryWithObjectsAndKeys:
                @"Name", QCPortAttributeNameKey,
                @"Pod", QCPortAttributeDefaultValueKey,
                nil];
    if([key isEqualToString:@"outputString"])
        return [NSDictionary dictionaryWithObjectsAndKeys:
                @"iName", QCPortAttributeNameKey,
                nil];
 
    return nil;
}

Figure 1-4 shows the patch that Quartz Composer creates as a result of the property declarations and the attributesForPropertyPortWithKey: method. Note that input values are read only—your custom patch code reads the input values and processes them in some way. Your custom patch code can write to output ports as well as read their current values.

Figure 1-4  The resulting patch
The resulting patch

If you don’t use properties, or if your custom patch needs to change the number of ports at runtime, you can use the QCPlugIn methods to dynamically add and remove input and output ports, and the associated methods to set and retrieve values. These methods are described in QCPlugIn Class Reference:

You can define a string that specifies a practical name for the custom patch. This is the name that appears as the patch title. You should also define a description string that briefly tells what the custom patch does. For example:

#define    kQCPlugIn_Name @"iPatch"
#define    kQCPlugIn_Description @"Converts any name to an \"iName\""

Then you need to implement the attributes method so that your custom patch returns a dictionary that contains the name and description. Listing 1-3 shows the attributes method for the iPatch custom patch.

Listing 1-3  A routine that returns the custom patch name and description

+ (NSDictionary*) attributes
{
    return [NSDictionary dictionaryWithObjectsAndKeys:
                 @"Name", QCPlugInAttributeNameKey,
                @"Convert any name to an \"iName\"", QCPlugInAttributeDescriptionKey,
                nil];
}

The description appears in the Quartz Composer when the patch is selected in the Patch Creator and when the user hovers the pointer over the patch title bar in the workspace.

You must establish an execution mode for the custom patch by implementing the executionMode method. The method returns the appropriate execution mode constant, which represents a Quartz Composer patch type—provider, processor, or consumer.

A custom patch must establish a time dependency by implementing the timeMode method. The method returns one of the following time mode constants:

If a custom patch uses the time base mode, the patch will have an option that allows the user to set the time base to parent, local, or external, as shown in Figure 1-5.

Figure 1-5  The time base setting for the Interpolation patch
The time base setting for the Interpolation patch

Property and Port Data Types

Objective-C 2.0 properties must be one of the data types listed in Table 1-1. Quartz Composer maps the property data type to the appropriate port type. The type constants for ports that your custom patch creates at runtime are listed in the Custom Port Type column of the table, next to the Objective-C class associated with the port value. If your custom patch requires data that can’t be captured by one of the data types below, see Internal Settings.

Table 1-1  Data type mappings

Port

Objective-C 2.0 property type

Custom port type

Objective-C class

Boolean

BOOL

QCPortTypeBoolean

NSNumber

Index

NSUInteger

QCPortTypeIndex

NSNumber

Number

double

QCPortTypeNumber

NSNumber

String

NSString *

QCPortTypeString

NSString

Color

CGColorRef

QCPortTypeColor

CGColorRef

Structure

NSDictionary *

QCPortTypeStructure

NSDictionary

Image (input)

id<QCPlugInInputImageSource>

QCPortTypeImage

(id)<QCPlugInInputImageSource>

Image (output)

id <QCPlugInOutputImageProvider>

QCPortTypeImage

(id)<QCPlugInOutputImageProvider>

Images in Quartz Composer are opaque objects that conform to protocols. Using protocols avoids the restrictions of a particular image type as well as type mismatches. It also gets the best performance because Quartz Composer defers pixel computation until it is needed.

The supported pixel formats for images are:

Input images are opaque source objects that conform to the QCPlugInInputImageSource protocol. To use an image as an input parameter to your custom patch, declare it as a property:

@property(assign) id<QCPlugInInputImageSource> inputImage;

Output images are opaque provider objects that conform to the QCPlugInOutputImageProvider protocol. To use an image as an output parameter for your custom patch, declare it as a property:

@property(assign) id<QCPlugInOutputImageProvider> outputImage;

Writing Image Processing Patches shows how to define the methods of the QCPlugInInputImageSource and QCPlugInOutputImageProvider protocols. See also QCPlugInInputImageSource Protocol Reference and QCPlugInOutputImageProvider Protocol Reference.

Internal Settings

Custom patch parameters that are not suitable as input ports can be added to the Settings pane of the inspector for the patch. A few reasons why you might want to use internal settings are:

Figure 1-6  Two sample Settings panes
Two sample Settings panesTwo sample Settings panes

For such cases, your custom patch needs to provide a user interface for editing these values. Quartz Composer inserts your custom user interface into the Settings pane of the inspector for the patch. Internal settings are accessible through key-value coding. The simplest way to implement them are as Objective-C 2.0 properties. Listing 1-4 shows two typical declarations for property instance variables.

Listing 1-4  Code that declares property instance variables

@property(copy) NSColor* systemColor;
@property(copy) MyConfiguration* systemConfiguration;

Your QCPlugIn subclass must implement the plugInKeys method so that it returns a list of keys for the internal settings. The plugInKeys method for the systemColor and systemConfiguration properties is shown in Listing 1-5. Make sure to terminate the list with nil. (See Key-Value Coding Programming Guide.)

Listing 1-5  An implementation of the plugInKeys method

+ (NSArray*) plugInKeys
{
    return [NSArray arrayWithObjects: @“systemColor”,
                @“systemConfiguration”,
                nil]
}

You use Interface Builder to create the user interface for editing internal settings. The interface is a view object (NSView or NSView subclass) that is managed through an instance of QCPlugInViewController. This instance acts as a controller between the custom patch instance and the view that contains the controls. In order for the nib to load properly, your plug-in class must implement the createViewController method of QCPlugIn and return an instance of QCPlugInViewController initialized with the correct nib name. In this example shown in Listing 1-6, the name is MyPlugIn.

Listing 1-6  Code that implements a view controller

- (QCPlugInViewController*) createViewController
{
  return [[QCPlugInViewController alloc] initWithPlugIn:self
         viewNibName:@”MyPlugIn”];
}

Using Cocoa bindings for the controls in your custom interface requires no code. Just use plugIn.XXX as the model key path, where XXX is the corresponding key for the internal setting. If you prefer, you can subclass QCPlugInViewController to implement the usual target-action communication model. (You also need to make sure that you do not autorelease the controller. Then you need to connect the controls in the nib to the owner controller.)

When you read or write a composition file that uses the custom patch, the internal settings are serialized. Serialization is automatic for any setting whose class conforms to the NSCoding protocol, such as NSColor. For example, the systemColor property defined in Listing 1-4 does not require any action on your part; the system serializes it automatically.

For settings that don’t conform to the NSCoding protocol, you need to override the following methods:

For example, the systemConfiguration property defined in Listing 1-4 is serialized as shown in Listing 1-7. A serialized value must be a property list class such as NSString, NSNumber, NSDate, NSArray, or NSDictionary or nil.

Listing 1-7  Code that overrides serialization methods for system configuration data

- (id) serializedValueForKey:(NSString*)key
 {
    if([key isEqualToString:@"systemConfiguration"])
        return [self.systemConfiguration data];
    // Ensure this has a data method
    return [super serializedValueForKey:key];
}
 
- (void) setSerializedValue:(id)serializedValue
                     forKey:(NSString*)key
{
    // System config is subclass of NSObject.
    // It's up to you to keep track of the version.
    if([key isEqualToString:@"systemConfiguration"])
        self.systemConfiguration
           = [MyConfiguration configurationWithData:serializedValue];
    else
        [super setSerializedValue:serializedValue forKey:key];
}

Custom Patch Execution

Quartz Composer assumes that the following statements are true for your QCPlugIn subclass:

The execute:atTime:withArguments: method of QCPlugIn is at the heart of the custom patch. The method typically:

Your execute:atTime:withArguments: method should access its property ports only when necessary. When you can, cache values in loops rather than repeatedly read them from the port.

When the Quartz Composer engine renders, it calls methods related to executing the custom patch. Quartz Composer passes an opaque object—that is, an execution context that conforms to the QCPlugInContext protocol—to these methods. You should neither retain the context nor use it outside of the execution methods. Make sure you don’t write values to the input ports (input ports are read only).

The QCPlugInContext protocol contains a number of useful methods for getting information about the rendering destination, including:

The protocol also provides utilities for logging messages and getting a dictionary of user information. The user information dictionary is shared with all instances of the custom patch in the same plug-in context environment. The dictionary was designed that way to allowing sharing information between custom patch instances. For more information on these methods, see QCPlugInContext Protocol Reference.

The QCPlugIn Template in Xcode

Xcode provides two templates that makes it straightforward to write and package custom patches. One template is for custom patches that do not require a Settings pane. The other template includes a nib file for a Setting pane. For each template, Xcode provides the skeletal files and methods that are needed and names the files appropriately. Figure 1-7 shows the files automatically created by Xcode for a Quartz Composer plug-in project named iPatch.

Figure 1-7  The files in a Quartz Composer plug-in project
The files in a Quartz Composer plug-in projectThe files in a Quartz Composer plug-in project

Xcode automatically names files using the project name that you supply. These are the default files provided by Xcode.

The interface file declares a subclass of QCPlugIn. Xcode automatically names the subclass <ProjectName>PlugIn. For example, if you supply Number2Color as the project name, the interface file uses Number2ColorPlugIn as the subclass name.

The implementation file contains these methods of the QCPlugIn class that you need to modify for your purposes:

There are other methods of QCPlugIn provided in the template that you can implement if appropriate. (See also QCPlugIn Class Reference.)

The implementation file provided by Xcode contains two important statements that you should not modify and one that you need to modify. This statement should not be modified or deleted:

#import <OpenGL/CGLMacro.h>

Using CGLMacro.h improves performance. The inclusion of this statement allows Quartz Composer to optimize its use of OpenGL by providing a local context variable and caching the current renderer in that variable. You should keep this statement even if your custom patch does not contain OpenGL code itself. If your custom patch contains OpenGL code, you need to declare a local variable and set the current context to it. For example:

#import <OpenGL/CGLMacro.h>

CGLContext cgl_ctx = myContext;

where myContext is the current context.

See Using OpenGL in a Custom Patch and OpenGL Programming Guide for Mac for more information.

The statement that defines the custom patch name is automatically filled-in by Xcode based on the project name that you supply. To help the user distinguish patches, it’s best if the patch name is unique in the context of the Quartz Composer Patch Creator. If the name isn’t unique, your patch will be difficult to use. So, change this statement.

#define kQCPlugIn_Name @"<ProjectName>"

Xcode provides a default definition for the custom patch description:

#define kQCPlugIn_Description @"Converts any name to an \"iName\""

You need to modify this string so that it describes the custom patch. If the patch name is not unique, it’s best to describe how your patch differs from another patch of the same name. You’ll also want to provide localized strings. (For more information on localization, see Strings Files.)

Packaging, Installing, and Testing Custom Patches

You need to package a Quartz Composer custom patch as a standard Cocoa bundle. You can package more than one custom patch in the bundle. The QCPlugIn template makes it trivial to package custom patches; see The QCPlugIn Template in Xcode.

The bundle Info.plist file must have an entry for each QCPlugIn subclass that’s in the bundle. Listing 1-8 shows a property list entry for a bundle that contains two custom patches: MyColorGenerator and MyNumberCruncher.

Listing 1-8  Entries in the Info.plist file

<key>QCPlugInClasses</key>
 <array>
  <string>MyColorGenerator</string>
  <string>MyNumberCruncher</string>
 </array>

When you build the bundles, you should target 32-bit, 64-bit, PowerPC, and Intel architectures.

You can make a custom patch available to any application that uses Quartz Composer by installing the plug-in that contains the custom patch in /Library/Graphics/Quartz Composer Plug-Ins or ~/Library/Graphics/Quartz Composer Plug-Ins. When Quartz Composer launches, it automatically loads the plug-in so that all the custom patches contained in the plug-in show up in the Patch Creator.

You can choose instead to include custom patch code in an application bundle. You might want to do this either to control the use of the custom patches or because they are useful only to the application you are embedding them in. To make a custom patch available to the application, register the subclass of the QCPlugIn class using the registerPlugInClass: method. If you want to restrict access to a plug-in that contains one or more custom patches, you can load the plug-in from any location by calling the method loadPlugInAtPath:.

You should make sure that your custom patch works properly by using it in a Quartz Composer application. If you use the Build & Copy target, Xcode automatically copies the built plug-in to ~/Library/Graphics/Quartz Composer Plug-Ins and then launches Quartz Composer.

If you do any of the following, the custom patches in your plug-in will not appear as patches in the Patch Creator:

You can check the system log in Console for error messages from Quartz Composer.