blob: 8a550182f7881beffeb765458cf572e39f05dc80 [file] [log] [blame]
/*
* Copyright 2016 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkColorLookUpTable.h"
#include "SkFloatingPoint.h"
void SkColorLookUpTable::interp(float* dst, const float* src) const {
if (fInputChannels == 3) {
interp3D(dst, src);
} else {
SkASSERT(dst != src);
// index gets initialized as the algorithm proceeds by interpDimension.
// It's just there to store the choice of low/high so far.
int index[kMaxColorChannels];
for (uint8_t outputDimension = 0; outputDimension < kOutputChannels; ++outputDimension) {
dst[outputDimension] = interpDimension(src, fInputChannels - 1, outputDimension,
index);
}
}
}
void SkColorLookUpTable::interp3D(float* dst, const float* src) const {
SkASSERT(3 == kOutputChannels);
// Call the src components x, y, and z.
const uint8_t maxX = fGridPoints[0] - 1;
const uint8_t maxY = fGridPoints[1] - 1;
const uint8_t maxZ = fGridPoints[2] - 1;
// An approximate index into each of the three dimensions of the table.
const float x = src[0] * maxX;
const float y = src[1] * maxY;
const float z = src[2] * maxZ;
// This gives us the low index for our interpolation.
int ix = sk_float_floor2int(x);
int iy = sk_float_floor2int(y);
int iz = sk_float_floor2int(z);
// Make sure the low index is not also the max index.
ix = (maxX == ix) ? ix - 1 : ix;
iy = (maxY == iy) ? iy - 1 : iy;
iz = (maxZ == iz) ? iz - 1 : iz;
// Weighting factors for the interpolation.
const float diffX = x - ix;
const float diffY = y - iy;
const float diffZ = z - iz;
// Constants to help us navigate the 3D table.
// Ex: Assume x = a, y = b, z = c.
// table[a * n001 + b * n010 + c * n100] logically equals table[a][b][c].
const int n000 = 0;
const int n001 = 3 * fGridPoints[1] * fGridPoints[2];
const int n010 = 3 * fGridPoints[2];
const int n011 = n001 + n010;
const int n100 = 3;
const int n101 = n100 + n001;
const int n110 = n100 + n010;
const int n111 = n110 + n001;
// Base ptr into the table.
const float* ptr = &(table()[ix*n001 + iy*n010 + iz*n100]);
// The code below performs a tetrahedral interpolation for each of the three
// dst components. Once the tetrahedron containing the interpolation point is
// identified, the interpolation is a weighted sum of grid values at the
// vertices of the tetrahedron. The claim is that tetrahedral interpolation
// provides a more accurate color conversion.
// blogs.mathworks.com/steve/2006/11/24/tetrahedral-interpolation-for-colorspace-conversion/
//
// I have one test image, and visually I can't tell the difference between
// tetrahedral and trilinear interpolation. In terms of computation, the
// tetrahedral code requires more branches but less computation. The
// SampleICC library provides an option for the client to choose either
// tetrahedral or trilinear.
for (int i = 0; i < 3; i++) {
if (diffZ < diffY) {
if (diffZ > diffX) {
dst[i] = (ptr[n000] + diffZ * (ptr[n110] - ptr[n010]) +
diffY * (ptr[n010] - ptr[n000]) +
diffX * (ptr[n111] - ptr[n110]));
} else if (diffY < diffX) {
dst[i] = (ptr[n000] + diffZ * (ptr[n111] - ptr[n011]) +
diffY * (ptr[n011] - ptr[n001]) +
diffX * (ptr[n001] - ptr[n000]));
} else {
dst[i] = (ptr[n000] + diffZ * (ptr[n111] - ptr[n011]) +
diffY * (ptr[n010] - ptr[n000]) +
diffX * (ptr[n011] - ptr[n010]));
}
} else {
if (diffZ < diffX) {
dst[i] = (ptr[n000] + diffZ * (ptr[n101] - ptr[n001]) +
diffY * (ptr[n111] - ptr[n101]) +
diffX * (ptr[n001] - ptr[n000]));
} else if (diffY < diffX) {
dst[i] = (ptr[n000] + diffZ * (ptr[n100] - ptr[n000]) +
diffY * (ptr[n111] - ptr[n101]) +
diffX * (ptr[n101] - ptr[n100]));
} else {
dst[i] = (ptr[n000] + diffZ * (ptr[n100] - ptr[n000]) +
diffY * (ptr[n110] - ptr[n100]) +
diffX * (ptr[n111] - ptr[n110]));
}
}
// |src| is guaranteed to be in the 0-1 range as are all entries
// in the table. For "increasing" tables, outputs will also be
// in the 0-1 range. While this property is logical for color
// look up tables, we don't check for it.
// And for arbitrary, non-increasing tables, it is easy to see how
// the output might not be 0-1. So we clamp here.
if (dst[i] > 1.f) {
dst[i] = 1.f;
} else if (dst[i] < 0.f) {
dst[i] = 0.f;
}
// Increment the table ptr in order to handle the next component.
// Note that this is the how table is designed: all of nXXX
// variables are multiples of 3 because there are 3 output
// components.
ptr++;
}
}
float SkColorLookUpTable::interpDimension(const float* src, int inputDimension,
int outputDimension,
int index[kMaxColorChannels]) const {
// Base case. We've already decided whether to use the low or high point for each dimension
// which is stored inside of index[] where index[i] gives the point in the CLUT to use for
// input dimension i.
if (inputDimension < 0) {
// compute index into CLUT and look up the colour
int outputIndex = outputDimension;
int indexMultiplier = kOutputChannels;
for (int i = fInputChannels - 1; i >= 0; --i) {
outputIndex += index[i] * indexMultiplier;
indexMultiplier *= fGridPoints[i];
}
return table()[outputIndex];
}
// for each dimension (input channel), try both the low and high point for it
// and then do the same recursively for the later dimensions.
// Finally, we need to LERP the results. ie LERP X then LERP Y then LERP Z.
const float x = src[inputDimension] * (fGridPoints[inputDimension] - 1);
// try the low point for this dimension
index[inputDimension] = sk_float_floor2int(x);
const float diff = x - index[inputDimension];
// and recursively LERP all sub-dimensions with the current dimension fixed to the low point
const float lo = interpDimension(src, inputDimension - 1, outputDimension, index);
// now try the high point for this dimension
index[inputDimension] = sk_float_ceil2int(x);
// and recursively LERP all sub-dimensions with the current dimension fixed to the high point
const float hi = interpDimension(src, inputDimension - 1, outputDimension, index);
// then LERP the results based on the current dimension
return (1 - diff) * lo + diff * hi;
}