A general (and backward compatible) way to manage colors in ImageAPI

Motivation

There are numerous ways to represent RGB colors today. ImageAPI pretty much leaves the parsing of colors to the underlying toolkit and this leads to situations where e.g. imagemagick will accept one (maybe user entered) color, whereas gd will not.

The proposal

This patch is the first step in an attempt to uniform color management in ImageAPI by introduction to a new function imageapi_color_convert(). This accepts RGB(A) colors in several formats and return the same color in another. So far, accepted colors are 24 bit integers, arrays with rgba values and strings with hex representations just like imageapi_hex2rgba(). The intended use is whenever a toolkit needs to use colors entered by users or modules and the toolkits needs this in a different formats.

The algorithm is simple: Split the color into each separate channel and output the color in the desired format. It's a generalization of imageapi_hex2rgba().

Discussion, comments

The values for the parameter $format probably need work. If it should be possible to output colors as a CSS rgb() function we have a collision, or one could add the formats 'css_rgb'.

Another question is whether it should be possible to enter HSL (hue-saturation-lightness) color values. This might add the need for another parameter, specifying if it is a RGB or HSL color we got.

This implementation triggers a bug in imageapi_gd due to the fact that alpha channels in GD are from 0 (full opacity) to 127 (full transparency). The norm elsewhere is that 0 is full transparency and MAX (1.0, 100%, 255) is full opacity. So is the case with this implementation. Depending on the outcome of this patch, another issue will address this bug.

Colors specified as integers only accept the 24 least significant bits and leaves no room for alpha channel. If you use 32 bits and specify a integer literal above 0x7fffffff, PHP treats this as a double. This leaves no room for four 8-bit channels in an integer.

I still haven't looked into simpletest, but here is a snippet that tests a series of different colors:

function _imageapi_color_convert_test()
{
  $colors = array(
     0xFFFFFF, 0xFF00FF, 0xB0A090, -1,
     0xABC,    0xAABBCC, 0xABCD,   0x112233,
     0x003,    0x000033, 0x003f,   0x0033ff,

     array(255,255,255), array(-1, 300, -200, 5000),
     array(255,255,0), array(0xaa, 0xaa, 0, 0x33),
     array(0x80,0x7f,0x0f), array(0xa, 0xa, 0xaa, 0),
     array('255','255','0'), array('aa', 'aa', '0', '33'),

      'abc',   'aabbcc',   'abcd',   'aabbccdd',
     '#abc',  '#aabbcc',  '#abcd',  '#aabbccdd',
    '0xabc', '0xaabbcc', '0xabcd', '0xaabbccdd',
      '123',   '112233',   '1234',   '11223344',
     '#123',  '#112233',  '#1234',  '#11223344',
    '0x123', '0x112233', '0x1234', '0x11223344',
  );
  $ret = "<pre>\n";
  $ret .= "src         web     array                  rgb    rgba    \n";
  $ret .= "==========================================================\n";
  foreach($colors as $color) {
    $web = imageapi_color_convert($color, 'web');
    $array = imageapi_color_convert($color, 'array');
    $rgb   = imageapi_color_convert($color, 'rgb');
    $rgba  = imageapi_color_convert($color, 'rgba');
    $ret .= sprintf("%-10s: %7s array(%3d,%3d,%3d,%3d) %s %s\n", $color, $web, $array[0], $array[1], $array[2], $array[3], $rgb, $rgba);
  }
  return $ret ."</pre>\n";
}

Please review.

CommentFileSizeAuthor
imageapi_color_convert.patch3.61 KBkaare
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

drewish’s picture

interesting... i did a lot of similar work for pear's image_color2 package: http://pear.php.net/package/Image_Color2

dman’s picture

As per #471816: API suggestion - use keyed color names, not ints in imageapi_hex2rgba() - I'd prefer to see named array keys in the internal array() $color['alpha'] instead of $color[4]
Drupal internals have no problem with verbosity - witness the migration from url() arguments to keyed arrays. And - as a user-facing API util function, it's that much more readable - for only a little more typing.

Other than that - and again reflecting the way Drupal APIs have tended to mature - This same task could maybe be done as a small set of functions rather than one super-function plus key switch? - witness the deprecation of hook_nodeapi, hook_form_alter, hook_block super-functions into smaller ones. The same arguments may not apply, as this util is a pretty self-contained task, not an API. I just note it as a strong trend in Drupal preferred code style over the last 5 years.

But as to the general idea of improving / centralizing color mechanisms, hell yeah! I've had to re-invent this several too many times in imagecache_actions. Imageapi is the place for it.

kaare’s picture

One could turn colors in drupal into objects and add all kinds of methods for notation and manipulation, but this may be overkill for the simple color handling imageapi requires. And it isn't backward compatible with imageapi-6.x. So I agree with going for an array with named keys for colors.

The color API will then be two-fold: Parsing (also insecure user input) and notation. How about this approach?

/**
 * Parse a color and return an array representation of it
 *
 * @param $color
 *   A color in any of these formats:
 *   ...
 *
 * @return
 *   An array with named keys for each of the red, green, blue and alpha channels.
 */ 
imageapi_parse_color($color)

/**
 * Return a color in web notation (#RRGGBB)
 *
 * @param $color
 *   An internal imageapi color
 */
imageapi_color_web($color)

/**
 * 0xRRGGBB
 */
imageapi_color_hex($color)

/**
 * rbg(red,green,blue)
 */
imageapi_color_css_rgb($color)

etc.

Then there is the question of alpha channel representation. gd's alpha channel is a value between 0 - 127 where 0 is full opacity and 127 is full transparency. For 32 bits signed integer color values, this approach makes sense. ImageMagick has 0.0 as full transparency and 1.0 as full opacity. The wikipedia entry RGBA color space states that the alpha channel is normally used as an opacity channel with 0% (0.0 or 0) as full transparency and 100% (1.0 or 255) as full opacity.

It feels more right to use the alpha channel as opacity percentage, and thereby having 0.0 as transparent and 1.0 as fully opaque.

What do you think?

dman’s picture

GD has got in wrong in this case, yeah, 0-1 transparent-opaque feels better. Matches image application UIs better.
Small issue is that we have to ensure that 'undefined' alpha consistently defaults to 1 not zero. Can be solved in code of course, but just need a tiny bit more care with the empty() or bool checks.