I was wrong; it only took three operations:
Code: Select all
/* non-zero result if colors differ considerably */
unsigned diff( unsigned x, unsigned y )
{
unsigned offset = (0x440 << 21) + (0x207 << 11) + 0x407;
unsigned mask = (0x380 << 21) + (0x1F0 << 11) + 0x3F0;
return (x - y + offset) & mask;
}
It requires that the RGB to YUV table be in a slightly different format, but this shouldn't matter.
Technique: You can manipulate multiple signed values in a 32-bit integer, as long as you convert to offset-binary when examining the result. When one of the lower values goes negative, it will effectively subtract one from the next component to the left. Adding offset later will cancel this. Consider a decimal example where the input values range from 0 to 10. The difference of any two values is in the range -10 to 10. If you add 100 to the result, it will always be in the range 90 to 110. Since that range has no negative values, it could be used in the low bits of a number without affecting the upper bits.
Technique: You can check whether a value is outside the range -n to (n - 1), where n is a power of two, by adding n and masking with ~(n * 2 - 1). For example, to find if a value is outside the range -16 to 15, add 16 and mask with ~31. The original range will be converted to 0 to 31. A value below zero will set all the bits above bit 4, and a value above 31 will set at least one bit above bit 4. This technique allows multiple range checks on values packed into a single integer.
Putting these together yields the solution. The function in pure form is
Code: Select all
abs( y1 - y2 ) > 0x30 || abs( u1 - u2 ) > 7 || abs( v1 - v2 ) > 6
The first step is to rescale the components so that the ranges are a power of two. So rescale Y by 0x3F / 0x30 and V by 7 / 6. Ideally you'd do the scaling to the Y and V values while they're still floating-point, otherwise you'll get some rounding errors. The thresholds are probably not absolutely critical, so these small errors should be acceptable.
Next, pack the Y U V values in the table with some padding between them. Y and V will have a slightly higher range, so give them each 11 bits, and U the remaining 10 bits.
Code: Select all
packed = (Y << 21) | (U << 11) | V;
That covers table initialization. The final function finds the difference between the two Y/U/V vectors and adds an offset to convert to offset binary. The offset also incorporates the value for the range check technique. The only step left is to mask off the upper bits of each component (except the top bit, which is normally set, rather than clear) and return this. If any of the retained bits are non-zero, one or more of the Y, U, or V component differences were beyond the threshold.
You'll note that the top bit of each component wasn't used. If they covered a larger range (or you wanted more accuracy), this might be necessary, in which case you'd include it in the final mask, then XOR with a value having the top bit set for each component.