KCMS CMM Developer's Guide

Chapter 7 KcsXform Derivative

In This Chapter

This chapter discusses the following topics to help you create a KcsXform class derivative that is dynamically loadable at runtime:

Figure 7-1 shows the relationship of the KcsXform class to the parent classes in the KCMS class hierarchy. See Figure 1-2 for an illustration of all the relevant KCMS classes.

Figure 7-1 KcsXform Derivative

Graphic

External Entry Points

The KCMS framework uses external entry points to load your derivative as an executable. The mandatory and optional entry points are described.

Mandatory

When you derive from a KcsXform class, the mandatory external entry points are:

extern

long KcsDLOpenXfrmCount; KcsXform * KcsCreateXfrm(KcsStatus *aStat, 

	KcsChunkSet *aChunkSet, KcsChunkId aChunkId,  	KcsAttributeSet

*aAttrSet);

KcsCreateXfrm()() creates an instance of a KcsXform derivative.

Optional

When you derive from a KcsXform class, the optional entry points are:

KcsStatus

KcsInitXfrm(); KcsStatus

KcsCleanupXfrm();

Example

The following example shows you how to use the entry points when creating a KcsXform derivative.


Example 7-1 KcsXform Class Entry Points Example

extern long

KcsDLOpenXfrmCount = 0;  /* Global initialization */ KcsStatus

KcsInitXfrm(long libMajor, long libMinor, long *myMajor, long *myMinor) { 	//

Set up the return values 	*myMajor = KCS_MAJOR_VERSION; 	*myMinor =

KCS_MINOR_VERSION;  	//Check the major version 	if (libMajor !=

KCS_MAJOR_VERSION) 		return (KCS_CMM_MAJOR_VERSION_MISMATCH);  	//Currently,

if minor version of library is less than the KCMS  	// minor version, return

an error. 	if (libMinor < KCS_MINOR_VERSION) 		return

(KCS_CMM_MINOR_VERSION_MISMATCH);  	//Library guarantees if your minor version

number is greater than 	//KCMS minor version number, it will handle it. No

more init. 	return(KCS_SUCCESS); }  KcsXform * KcsCreateXfrm(KcsStatus *aStat,

KcsChunkSet *aCS,  	KcsChunkId aChunkId, KcsAttributeSet *aAttrSet) {

	//Create the new derivative 	return(new KcsTechUCP(aStat, KcsLoadAllow, aCS,

aChunkId,  		aAttrSet)); }  /* Global clean up */ KcsStatus KcsCleanupXfrm() {

	KcsStatus sStat; 	return(KCS_SUCCESS);

}

Member Function Override Rules

The following table tells you which KcsXform member functions you must override and can override when deriving from this class. The member functions indicated with an "X" in the Must column are required to successfully derive from this base class. All of these member functions are defined in the kcsxform.h header file and the KCMS CMM Reference Manual.

Table 7-1 KcsXform Member Function Override Rules

Member Function 

Override Rules 

Must 

Can 

compose()()

 

connect()()

 

connectSink()()

 

connectSource()()

 

connectXform()()

 

convertXform()()

 

eval()()

 

getAttrSet()()

 

getLoadOrder()()

 

getSaveOrder()()

 

KcsXform()()

 

~KcsXform()()

 

loadU()()

 

numberOfCallbacks()()

 

optimize()()

 

saveU()()

 

setAttrSet()()

 

setCallbackInterval()()

 

setComponentDepth()()

 

setDefaultAttributes()()

 

setNumComponents()()

 

validateLayouts()()

 

Technology

In the KCMS framework environment, the term technology means algorithms, code, and data used to implement a specific method of color transformations. All supported technologies must supply certain uniform functionality. You can do this in C++ by having a KcsXform base class with pure virtual methods. Each technology is implemented in a derived class that must implement the required virtual methods.

With transformation conversion, a technology or base class can default to a specific derivative with the functionality that best meets that technology. For example, the KcsXform base class is aware of only one type of KcsXform derivative that can save universally. Therefore, the default saveU()() method converts whatever technology it has into a KcsTechUCP. Then it asks the converted KcsXform to do the saveU()().

KcsXform Attributes

KcsXforms contain their own KcsAttributeSets. They are passed in through all constructors and default to NULL. The KcsAttributeSets are copied and are not shared by default: they are set by their constructor callers. All derivative constructors are updated.

The KcsProfile base class copies some standard attributes to the appropriate KcsXform. Access is through methods that set and get the attribute set; therefore, all access to these attributes are equal to the interface to KcsAttributeSets.

Optimization

Transformation optimization includes one or more compositions, but this is not always the case. That is why optimize()() is separate from composition. Generally stated, optimizing an object makes it smaller, faster, more precise, or some combination of the above. It is up to the derivative to figure out what is best for its situation. For example, if your derivative contains a resource such as extra tables for quality purposes and the derivative is requested to optimize for space and speed, it may very well throw away those extra tables.

During a save, this same derivative may not want to get rid of these extra tables. Instead, it either can use the hierarchical method described in save or reread the tables back into memory and save them again. It is up to the derivative. The choice might depend on the size of the table or some error constraint on the transform. See "KcsXformSeq Derivatives" for information on how a derivative always composes and keeps that transformation for evaluation. Also note that it keeps the original transformations in the list unless it is also told to optimize for size, after which it will get rid of them.

If your derivative discards a resource in the process of optimizing and subsequently attempts to retrieve it, that resource may no longer be available. Ultimately it is up to the derivative to decide when and how to make that determination. It may (and probably will) change between releases of that derivative as well.

Optimization must be defined by the derivative if that functionality is needed. Only the derivative instances understand how best to optimize. The derivative can refuse any optimization request. It also can prioritize the types of optimization if more than one bit is set. For example, if the instance is told to optimize for space and speed and speed means to add space, then if you consider it appropriate, have your derivative add the space to support the speed increase.

Loading

Defer some loading functionality to other objects in the KCMS framework, because the objects can minimize and load more efficiently. With the KcsXform class, the object does not need to implement the load in all derivatives for the first time. This means that the profile instance has the objects (in this case the KcsXform derivatives) load and unload themselves, but it still has to load and minimize objects through construction and destruction to make up for those KcsXform derivatives that do not load and minimize.

The KcsXform base class provides the default load virtual methods that return the KCS_NOT_RUNTIME_LOADABLE error. This error allows the KcsProfile class, or any other KcsXform container, to check for this error condition and to use another approach if necessary.


Note -

Currently, not all technologies provide their own loading mechanism; use the base class functionality.


Save Types

Since there is more than one way to save, derivatives can specify the order in which its pieces get saved. The save types consist of bit sets and are:

These choices are available with an extensible protocol in which:

Universal

The KcsXform base class supports saving in the universal format. The save()() method converts the object to ICC 3.0 to icLutX form. You need to provide allocation of the *aLut argument. When complete, the converted date is copied to the *aLut variable. This method is used by other objects during save()(). The ICC 3.2 profile format derivative calls KcsXform during its save to convert the KcsXform object into the appropriate ICC transformation attribute. If not overridden, the KcsXform base class converts the transformation into a KcsXform derivative that supports the save()() method and returns its conversion. If a derivative needs more control over this type of save, then it must override this method.

Private

Private saving uses the chunk set and chunk Id associated with the instance to save. The derivative only needs to package all of its data into a contiguous piece of memory and pass the address and its chunk Id to the object's chunk set. If this is too limiting, you can split the derivative's pieces into different chunks, each with its own chunk Id. The only caveat is that the instance must then place all of those chunk Ids into one chunk, which is ultimately saved as the top of the object.

This approach is appropriate when the object has many data structures that it does not want to store into one contiguous memory block. It also helps with loading if all the pieces are not needed all the time. This is the overall approach taken by the KCMS framework where the KcsProfile class has a table of chunkIds, one of which is the attribute chunkId for this profile. When loading attributes only, it is faster to use getchunk()() and load just the attribute object than it is to use getchunk()() and load the entire set of objects that represent a profile.

Example

ICC has both universal and private places for transformation data. The InterColorProfileFormat asks for the load order and gives a list of universal plus private. The Universal Color Processor (UCP) derivative responds with universalAsPrivate. Since the derivative knows that UCPs can do this, it asks any KcsXform derivative that does not save in the universal format to convert itself into a UCP. This follows the second way to break an obligation, since the InterColorProfileFormat actually converts the transformation to another kind and saves the converted one. It never saves the original.

The typdefs are as follows:

typedef

long KcsLoadSaveSet; #define KcsNoParts           

((KcsLoadSaveSet)0x00000000) #define KcsPrivatePart       

((KcsLoadSaveSet)0x00000001) #define KcsUniversalPart     

((KcsLoadSaveSet)0x00000002)
 #define KcsUniversalisPrivate 

	((KcsLoadSaveSet)((0x80000000)|KcsUniversalPart|KcsPrivatePart))

Composition

Some technologies convert from another technology (Xform *). For example, CS1.0 logTech can generate an instance of itself from any other (KcsXform *) derivative. It does this by calling the compose()() method, which takes a (KcsXform *) and returns a (KcsXform **). To use this technique, you should supply a callback function because it can be a slow operation.

The KCMS framework uses this protocol to implement a sequence KcsXform derivative that can take many transformations and treat them as one by sequentially evaluating the chain. Since the KcsXformSeq class is a KcsXform derivative, one LogTech can be generated that represents the complete connection. This has tremendous speed and quality advantages.

The KcsXform base class performs composition using the default CMM.

Evaluation

When a KcsXform is instantiated, it is ready to transform n->m component data (unless it is in the process of being built). Since it can handle many different data formats, the KCMS framework encapsulates the description of the data to be transformed into a data structure called KcsPixelLayout. This structure is an array of component descriptions. See the KCMS Application Developer's Guide for more information on KcsPixelLayout.

The KcsPixelLayout structure is used by the eval()() methods to describe the information to be transformed. When using an eval()method(), supply a source PixelLayout, a destination PixelLayout, and a callback function. The eval()()method takes the data described by the source layout, transforms it, and puts it into the buffer described by the destination layout. If the evaluation is going to take a long time, the callback function is repeatedly called until evaluation is complete.

The layouts can describe the same buffer (a technique called in-place transformation). In this case, the eval()() method detects it is the same buffer and optimizes for performance. The layouts can also specify different buffers, in which case the data is moved as well as transformed. You can even supply two layouts which differ in composition (for example, planar RGB and chunky CMYK), and the data is moved and transformed from RGB to CMYK as well as has its composition transformed from planar to chunky. The evaluation methods accomplish this with minimal steps. Evaluation is most efficient when given large buffers of data to transform.

If the transformation is not compatible with the layout(s), it returns an error. For example, if an RGB->CMYK KcsXform * is given two 3-component descriptions, it would return an error if the destination is expecting 4-component data.

If your data can be represented in different formats, get the value of the attribute KcsAttrPixelLayoutSupported to see what the most efficient pixel layout is for that KcsXform derivative.

Evaluation Helper Methods

KcsXform includes only one pure virtual eval()() method. It is the one with two pixel layouts and a callback as arguments. Other eval()() methods are overloaded in the base KcsXform class to allow other data types to fit a long on each side of the xform, for example,

(long *) -> (long

*)

and

aRGB->aR'G'B'

The base class takes all the overloaded methods and creates a pixel layout for each method. Then it calls the pure virtual method. Therefore, derivatives only need to implement one eval()() method.

When starting an evaluation, the derivative can use any of the helper functions provided for pixel layout usage. The convertLayouts()() method takes any layout and transforms it into any other. If a derivative can only handle chunky, the derivative may want to convert a non-chunky derivative to chunky before the evaluation is started.

KcsXformSeq Derivatives

The KcsXformSeq class is a KcsXform derivative that allows a list or concatenation of transformations to act as one KcsXform. It is an alias to an ordered transformation collection that allows all normal list management in addition to all of the required KcsXform protocols. It also allows a hierarchy of KcsXform instances by providing the ability to sequence the list. Evaluating through a sequence of KcsXforms is like serially running each transform, with successive transformations taking input from the output of its predecessor and ending with the last one putting its output into the destination location.

Constructs and Destructors

You can construct a KcsXformSeq with any of the following:

Saving

Saving trickles down throughout the whole connected hierarchy. Any change to any transformation in the sequence is saved when the sequence is saved. This happens because the sequence shares the transformations passed to it. The instance also gets the chunk Ids from each transformation in the list. It then packs these and other state information into a memory block and does a setChunk()() to allow lookup of this transformation list upon a load request to the sequence.

When requested to save in universal format (see "Universal" for details), the sequence does a composition that generates one transformation that is saved in this format.

Loading and Constructing the List

A KcsXformSeq instance saves its transformations as a list of chunk Ids to later instantiate when needed. For every chunk Id in its own chunk, getChunkXform()() takes the current chunk set and chunk Ids and, through the chunk set protocol of createXform()(), allocates the transformation represented by that unique combination.

Connections

KcsXformSeq is the only class in the KCMS framework that supports connection (and connection is the only reason the KcsXformSeq class exists). The base KcsXform class uses a sequence derivative in its connect()() method.

To make a connection, you can call either of two KcsXformSeq constructors (or use a combination of the two): one constructor takes a list of transformation pointers; the other creates a sequence of 0s. Then edit the transformations list with the list()() methods. See "Validation" for additional information on the connection method.

Optimization

When a sequence is told to optimize itself, first it optimizes each transformation in the chain individually. Then it composes all the transformations into one KcsTechUCP transformation. Finally it uses that composed KcsTechUCP to do future evaluations. Overall optimization is provided with optimization and composition of the individual transformations in the list.

The KcsXformSeq class performs composition by asking each transformation in the list to compose. If none comply, it uses the base class method to compose. It attempts to compose from the rightmost to leftmost. By doing so, the harder-to-model devices (typically printers, which are on the right) get composed first.

If you request to optimize for size, KcsXformSeq detaches all of the original list. After optimizing for size, the only way to regenerate the original list is to build it again.

Composition

The KcsXformSeq class uses the compose()() method to implement optimization. Since the KcsXformSeq class is a KcsXform derivative, you can generate one KcsTechUCP that represents the complete connection. This offers performance and quality advantages.

Evaluation

Evaluation of a KcsXformSeq instance is done with either the optimized or non-optimized technique.

Optimized evaluation uses the composed transformation it constructed when told to optimize. It keeps a pointer to that optimized transformation in its private section. When asked to evaluate, it passes the information down to the optimized transformation.

Unoptimized evaluation is used when the sequence is not optimized. This implementation evaluates the data through the list of transformations sequentially. Between transformations, a buffer is used to hold the temporary calculations. The first step evaluates from the source buffer, while the last step evaluates into the destination buffer.

Up to two different extra buffers are used between non-endpoint transformations, depending on the layout of the data. They are swapped between eval()()s. If the composition of the transformations is different (for example, chunky and planar), two buffers are needed. If the implementation did not use this technique, the data from one complete pixel (or component set) overrides a different (set of) pixel. The eval()() method always alternates between two buffer pointers. Both buffer pointers point to the same buffer if an output buffer for a transformation is compatible with the input buffor for the next transformation. This can be optimized further if all buffer layouts describe a buffer that is compatible with the destination buffer supplied by the caller. In this case, the buffer pointers point to the destination buffer described. And if the caller is using the same buffer for source and destination, everything ultimately uses one buffer. Such buffers are KcsMemoryBlocks that can be resized.

Validation

Each time a connection is made, it is validated against a set of rules defined in thisKcsXformSeq class. The rules use the current set of attributes as well as the current state of all of the transformations in the connection.

If the sequence rules pass, the sequence passes itself down to all the validation methods of each KcsXform in the list. In this way, all KcsXforms are allowed to determine if a connection can be made. If an error occurs in any single KcsXform, the connection is refused.

The List

The list of transformations is represented by a memory block of pointers to KcsXforms. The size of the block is incremented by a constant each time the current block fills with pointers. A few methods access and edit the list.

Note that a NULL parent starts the list based on this sequence. You must pass the last parent found into the next call to getNextXform()() and use the same object for invocations of this method. getNextXform()() returns KCS_END_OF_XFORMS when it reaches the end of the transformations in the sequence. All getNextXform()() calls are sequential. Any sharing of an object must take this into account. Otherwise, if the calls to getNextXform()() are not synchronized, two different results may occur. getNextXform()() works correctly when called on a sequence that is a part of another sequence: it runs through that subsequence only.

For example, given sequence A (a->B->e) and sequence B (c->d) where a, c, d, and e are primitive transformation types: A->GetNext()(). If GetNextXform()() is called (starting with a NULL parent **) until it returns KCS_END_OF_SEQUENCE, it returns transformations in the following order: a, c, d, e, B->GetNext()(). If called (starting with a NULL parent **) until it returns KCS_END_OF_SEQUENCE, it returns transformations in the following order: c, d. It also skips over all sequences of 0 transformations as if they are not even there.