Line data Source code
1 : /*
2 : * Copyright 2016 Google Inc.
3 : *
4 : * Use of this source code is governed by a BSD-style license that can be
5 : * found in the LICENSE file.
6 : */
7 :
8 : #include "SkColorLookUpTable.h"
9 : #include "SkColorSpaceXformPriv.h"
10 : #include "SkFloatingPoint.h"
11 :
12 0 : void SkColorLookUpTable::interp(float* dst, const float* src) const {
13 0 : if (fInputChannels == 3) {
14 0 : interp3D(dst, src);
15 : } else {
16 0 : SkASSERT(dst != src);
17 : // index gets initialized as the algorithm proceeds by interpDimension.
18 : // It's just there to store the choice of low/high so far.
19 : int index[kMaxColorChannels];
20 0 : for (uint8_t outputDimension = 0; outputDimension < kOutputChannels; ++outputDimension) {
21 0 : dst[outputDimension] = interpDimension(src, fInputChannels - 1, outputDimension,
22 : index);
23 : }
24 : }
25 0 : }
26 :
27 0 : void SkColorLookUpTable::interp3D(float* dst, const float* src) const {
28 : SkASSERT(3 == kOutputChannels);
29 : // Call the src components x, y, and z.
30 0 : const uint8_t maxX = fGridPoints[0] - 1;
31 0 : const uint8_t maxY = fGridPoints[1] - 1;
32 0 : const uint8_t maxZ = fGridPoints[2] - 1;
33 :
34 : // An approximate index into each of the three dimensions of the table.
35 0 : const float x = src[0] * maxX;
36 0 : const float y = src[1] * maxY;
37 0 : const float z = src[2] * maxZ;
38 :
39 : // This gives us the low index for our interpolation.
40 0 : int ix = sk_float_floor2int(x);
41 0 : int iy = sk_float_floor2int(y);
42 0 : int iz = sk_float_floor2int(z);
43 :
44 : // Make sure the low index is not also the max index.
45 0 : ix = (maxX == ix) ? ix - 1 : ix;
46 0 : iy = (maxY == iy) ? iy - 1 : iy;
47 0 : iz = (maxZ == iz) ? iz - 1 : iz;
48 :
49 : // Weighting factors for the interpolation.
50 0 : const float diffX = x - ix;
51 0 : const float diffY = y - iy;
52 0 : const float diffZ = z - iz;
53 :
54 : // Constants to help us navigate the 3D table.
55 : // Ex: Assume x = a, y = b, z = c.
56 : // table[a * n001 + b * n010 + c * n100] logically equals table[a][b][c].
57 0 : const int n000 = 0;
58 0 : const int n001 = 3 * fGridPoints[1] * fGridPoints[2];
59 0 : const int n010 = 3 * fGridPoints[2];
60 0 : const int n011 = n001 + n010;
61 0 : const int n100 = 3;
62 0 : const int n101 = n100 + n001;
63 0 : const int n110 = n100 + n010;
64 0 : const int n111 = n110 + n001;
65 :
66 : // Base ptr into the table.
67 0 : const float* ptr = &(table()[ix*n001 + iy*n010 + iz*n100]);
68 :
69 : // The code below performs a tetrahedral interpolation for each of the three
70 : // dst components. Once the tetrahedron containing the interpolation point is
71 : // identified, the interpolation is a weighted sum of grid values at the
72 : // vertices of the tetrahedron. The claim is that tetrahedral interpolation
73 : // provides a more accurate color conversion.
74 : // blogs.mathworks.com/steve/2006/11/24/tetrahedral-interpolation-for-colorspace-conversion/
75 : //
76 : // I have one test image, and visually I can't tell the difference between
77 : // tetrahedral and trilinear interpolation. In terms of computation, the
78 : // tetrahedral code requires more branches but less computation. The
79 : // SampleICC library provides an option for the client to choose either
80 : // tetrahedral or trilinear.
81 0 : for (int i = 0; i < 3; i++) {
82 0 : if (diffZ < diffY) {
83 0 : if (diffZ > diffX) {
84 0 : dst[i] = (ptr[n000] + diffZ * (ptr[n110] - ptr[n010]) +
85 0 : diffY * (ptr[n010] - ptr[n000]) +
86 0 : diffX * (ptr[n111] - ptr[n110]));
87 0 : } else if (diffY < diffX) {
88 0 : dst[i] = (ptr[n000] + diffZ * (ptr[n111] - ptr[n011]) +
89 0 : diffY * (ptr[n011] - ptr[n001]) +
90 0 : diffX * (ptr[n001] - ptr[n000]));
91 : } else {
92 0 : dst[i] = (ptr[n000] + diffZ * (ptr[n111] - ptr[n011]) +
93 0 : diffY * (ptr[n010] - ptr[n000]) +
94 0 : diffX * (ptr[n011] - ptr[n010]));
95 : }
96 : } else {
97 0 : if (diffZ < diffX) {
98 0 : dst[i] = (ptr[n000] + diffZ * (ptr[n101] - ptr[n001]) +
99 0 : diffY * (ptr[n111] - ptr[n101]) +
100 0 : diffX * (ptr[n001] - ptr[n000]));
101 0 : } else if (diffY < diffX) {
102 0 : dst[i] = (ptr[n000] + diffZ * (ptr[n100] - ptr[n000]) +
103 0 : diffY * (ptr[n111] - ptr[n101]) +
104 0 : diffX * (ptr[n101] - ptr[n100]));
105 : } else {
106 0 : dst[i] = (ptr[n000] + diffZ * (ptr[n100] - ptr[n000]) +
107 0 : diffY * (ptr[n110] - ptr[n100]) +
108 0 : diffX * (ptr[n111] - ptr[n110]));
109 : }
110 : }
111 :
112 : // |src| is guaranteed to be in the 0-1 range as are all entries
113 : // in the table. For "increasing" tables, outputs will also be
114 : // in the 0-1 range. While this property is logical for color
115 : // look up tables, we don't check for it.
116 : // And for arbitrary, non-increasing tables, it is easy to see how
117 : // the output might not be 0-1. So we clamp here.
118 0 : dst[i] = clamp_0_1(dst[i]);
119 :
120 : // Increment the table ptr in order to handle the next component.
121 : // Note that this is the how table is designed: all of nXXX
122 : // variables are multiples of 3 because there are 3 output
123 : // components.
124 0 : ptr++;
125 : }
126 0 : }
127 :
128 0 : float SkColorLookUpTable::interpDimension(const float* src, int inputDimension,
129 : int outputDimension,
130 : int index[kMaxColorChannels]) const {
131 : // Base case. We've already decided whether to use the low or high point for each dimension
132 : // which is stored inside of index[] where index[i] gives the point in the CLUT to use for
133 : // input dimension i.
134 0 : if (inputDimension < 0) {
135 : // compute index into CLUT and look up the colour
136 0 : int outputIndex = outputDimension;
137 0 : int indexMultiplier = kOutputChannels;
138 0 : for (int i = fInputChannels - 1; i >= 0; --i) {
139 0 : outputIndex += index[i] * indexMultiplier;
140 0 : indexMultiplier *= fGridPoints[i];
141 : }
142 0 : return table()[outputIndex];
143 : }
144 : // for each dimension (input channel), try both the low and high point for it
145 : // and then do the same recursively for the later dimensions.
146 : // Finally, we need to LERP the results. ie LERP X then LERP Y then LERP Z.
147 0 : const float x = src[inputDimension] * (fGridPoints[inputDimension] - 1);
148 : // try the low point for this dimension
149 0 : index[inputDimension] = sk_float_floor2int(x);
150 0 : const float diff = x - index[inputDimension];
151 : // and recursively LERP all sub-dimensions with the current dimension fixed to the low point
152 0 : const float lo = interpDimension(src, inputDimension - 1, outputDimension, index);
153 : // now try the high point for this dimension
154 0 : index[inputDimension] = sk_float_ceil2int(x);
155 : // and recursively LERP all sub-dimensions with the current dimension fixed to the high point
156 0 : const float hi = interpDimension(src, inputDimension - 1, outputDimension, index);
157 : // then LERP the results based on the current dimension
158 0 : return clamp_0_1((1 - diff) * lo + diff * hi);
159 : }
|