| Home | Press | Mailing Lists | Documentation | Screenshots | People | SourceForge | Downloads |
by Robin Rowe
CinePaint currently supports 8-bit unsigned, 16-bit unsigned, 16-bit RnH chopped float (HDR), and 32-bit IEEE float (HDR) channel types. Stefan Klein has a patch to add 16-bit signed binary fixed point (HDR 0-2.0). These types are implemented in the paint core and rather messy. An idea advanced by Rhythm & Hues is to have the paint core work in float32 solely and to convert to other types as import/export operations. However, that approach lacks flexibility for future channel type extensions. And, it can lose precision with some types such as 32-bit unsigned and 32-bit binary fixed point. Moreover, 16-bit Half float conversion is an expensive process, and highly undesirable if we move in the anticipated direction of supporting Half float computation as GPU processing.
The appeal of a monlithic channel architecture like RnH proposes is to reduce complexity. Besides the paint core complexity, supporting a large number of channel types across a set of plug-ins is too much work.
How can we keep channel combinatorics (coding conversions between all the different channel types) from being overwhelming?
Imagine a future set of channel plug-ins like this: u8.dll, u16.dll, u32.dll, bfp16.dll, bfp32.dll, ubfp16.dll, ubfp32.dll, cf16.dll, hf16.dll, f32.dll and d64.dll That's unsigned 8/16/32, signed binary fixed point 16/32, ditto unsigned, chopped float 16 (RnH), Half float 16 (ILM), IEEE float 32, and IEEE double 64. (I've listed them with the Windows extension of dll, but plug-in extension would of course depend upon the platform.) Each plug-in implements the usual paint functions that we have in the core now, but not the combinatoric conversions between every type like what we have in the core now.
When a plug-in is loaded it populates an array of stuctures of per-type function pointers. Those are called instead of the hardwired functions we have in the core now for paint ops. For example, each channel plug-in implement 'x_add_row' -- that there not be a name-mangled channel-specific function named 'x_add_row_bfp'. Across channel type plug-ins the same function would have the same name and same types. That creates a little problem because they of course aren't the same channel types. That channel data would pass through void* and be cast appropriately inside the function. (That's pretty much how they work now.)
Functions that are bit-depth specific would go in channel plug-ins. Otherwise, the function should be implemented one time in the core. Code would need to be touched in the same number of places regardless. That it is a plug-in doesn't change that. The only difference is that some of that code would reside in plug-ins instead of the core. The only extra effort involved for plug-ins would be some logistics, that they have to load and be build separately. This architecture change would not create a lot more new code. That still leaves the issue of conversions, where the combinatorics of converting between every supported channel type would seem to be overwhelming. For that I propose a technique called "vaulting".
For vaulting conversions, a set of 32-bit converter functions would be added (as vaulting32.dll). To do a channel conversion from u8 to u16 (say in a filter that is dealing with 8-bit and 16-bit layers) the code would try to look-up for a function in the PDB named "u8_u16_line" (with variants perhaps per channel, pixel, line, raster). If it finds that converter, fine. However, if only 32-bit vaulting converters are available it isn't going to find that direct-route converter. Consequently, it would vault through u8_u32 then down again via u32_u16, take the hit of doing two conversions.
Every channel type plug-in will include its one best vaulting converter, which will convert to the closest 32-bit type (both directions). That would mean one 32-bit converter required for each channel type plug-in. The other converter each channel type that would always provide is u8, to optimize screen display. Other converters would be optional.
In addition to vaulting, the bridge conversions would be implemented. This is done once, not part of the channel plug-ins. Implemented in a bridge32.dll plug-in would be the 32-bit channel bridge conversions to go between unsigned, bfp, ubfp, and float. These are inherently lossy conversions.
Using the 32-bit bridge and vaulting converters in conjunction with the optimal 32-bit converter included with each channel type plug-in, it will be possible to convert anything to anything in the most lossless manner possible in a maximum of three hops (an up-conversion, a bridge, a down-conversion). Note for the odd case that the "up-conversion" on d64 would be to f32.
When conversion performance is an issue it would be an option to install more converter plug-ins with hand optimized direct-route conversions. Conversion routines should always check for a direct-route conversion before calling the vaulting function.