xref: /aosp_15_r20/external/libmonet/quantize/QuantizerWu.java (revision 970e10460f970939fd510dd6ad3e0d65908272e3)
1*970e1046SAndroid Build Coastguard Worker /*
2*970e1046SAndroid Build Coastguard Worker  * Copyright 2021 Google LLC
3*970e1046SAndroid Build Coastguard Worker  *
4*970e1046SAndroid Build Coastguard Worker  * Licensed under the Apache License, Version 2.0 (the "License");
5*970e1046SAndroid Build Coastguard Worker  * you may not use this file except in compliance with the License.
6*970e1046SAndroid Build Coastguard Worker  * You may obtain a copy of the License at
7*970e1046SAndroid Build Coastguard Worker  *
8*970e1046SAndroid Build Coastguard Worker  *      http://www.apache.org/licenses/LICENSE-2.0
9*970e1046SAndroid Build Coastguard Worker  *
10*970e1046SAndroid Build Coastguard Worker  * Unless required by applicable law or agreed to in writing, software
11*970e1046SAndroid Build Coastguard Worker  * distributed under the License is distributed on an "AS IS" BASIS,
12*970e1046SAndroid Build Coastguard Worker  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*970e1046SAndroid Build Coastguard Worker  * See the License for the specific language governing permissions and
14*970e1046SAndroid Build Coastguard Worker  * limitations under the License.
15*970e1046SAndroid Build Coastguard Worker  */
16*970e1046SAndroid Build Coastguard Worker 
17*970e1046SAndroid Build Coastguard Worker package com.google.ux.material.libmonet.quantize;
18*970e1046SAndroid Build Coastguard Worker 
19*970e1046SAndroid Build Coastguard Worker import com.google.ux.material.libmonet.utils.ColorUtils;
20*970e1046SAndroid Build Coastguard Worker import java.util.ArrayList;
21*970e1046SAndroid Build Coastguard Worker import java.util.LinkedHashMap;
22*970e1046SAndroid Build Coastguard Worker import java.util.List;
23*970e1046SAndroid Build Coastguard Worker import java.util.Map;
24*970e1046SAndroid Build Coastguard Worker 
25*970e1046SAndroid Build Coastguard Worker /**
26*970e1046SAndroid Build Coastguard Worker  * An image quantizer that divides the image's pixels into clusters by recursively cutting an RGB
27*970e1046SAndroid Build Coastguard Worker  * cube, based on the weight of pixels in each area of the cube.
28*970e1046SAndroid Build Coastguard Worker  *
29*970e1046SAndroid Build Coastguard Worker  * <p>The algorithm was described by Xiaolin Wu in Graphic Gems II, published in 1991.
30*970e1046SAndroid Build Coastguard Worker  */
31*970e1046SAndroid Build Coastguard Worker public final class QuantizerWu implements Quantizer {
32*970e1046SAndroid Build Coastguard Worker   int[] weights;
33*970e1046SAndroid Build Coastguard Worker   int[] momentsR;
34*970e1046SAndroid Build Coastguard Worker   int[] momentsG;
35*970e1046SAndroid Build Coastguard Worker   int[] momentsB;
36*970e1046SAndroid Build Coastguard Worker   double[] moments;
37*970e1046SAndroid Build Coastguard Worker   Box[] cubes;
38*970e1046SAndroid Build Coastguard Worker 
39*970e1046SAndroid Build Coastguard Worker   // A histogram of all the input colors is constructed. It has the shape of a
40*970e1046SAndroid Build Coastguard Worker   // cube. The cube would be too large if it contained all 16 million colors:
41*970e1046SAndroid Build Coastguard Worker   // historical best practice is to use 5 bits  of the 8 in each channel,
42*970e1046SAndroid Build Coastguard Worker   // reducing the histogram to a volume of ~32,000.
43*970e1046SAndroid Build Coastguard Worker   private static final int INDEX_BITS = 5;
44*970e1046SAndroid Build Coastguard Worker   private static final int INDEX_COUNT = 33; // ((1 << INDEX_BITS) + 1)
45*970e1046SAndroid Build Coastguard Worker   private static final int TOTAL_SIZE = 35937; // INDEX_COUNT * INDEX_COUNT * INDEX_COUNT
46*970e1046SAndroid Build Coastguard Worker 
47*970e1046SAndroid Build Coastguard Worker   @Override
quantize(int[] pixels, int colorCount)48*970e1046SAndroid Build Coastguard Worker   public QuantizerResult quantize(int[] pixels, int colorCount) {
49*970e1046SAndroid Build Coastguard Worker     QuantizerResult mapResult = new QuantizerMap().quantize(pixels, colorCount);
50*970e1046SAndroid Build Coastguard Worker     constructHistogram(mapResult.colorToCount);
51*970e1046SAndroid Build Coastguard Worker     createMoments();
52*970e1046SAndroid Build Coastguard Worker     CreateBoxesResult createBoxesResult = createBoxes(colorCount);
53*970e1046SAndroid Build Coastguard Worker     List<Integer> colors = createResult(createBoxesResult.resultCount);
54*970e1046SAndroid Build Coastguard Worker     Map<Integer, Integer> resultMap = new LinkedHashMap<>();
55*970e1046SAndroid Build Coastguard Worker     for (int color : colors) {
56*970e1046SAndroid Build Coastguard Worker       resultMap.put(color, 0);
57*970e1046SAndroid Build Coastguard Worker     }
58*970e1046SAndroid Build Coastguard Worker     return new QuantizerResult(resultMap);
59*970e1046SAndroid Build Coastguard Worker   }
60*970e1046SAndroid Build Coastguard Worker 
getIndex(int r, int g, int b)61*970e1046SAndroid Build Coastguard Worker   static int getIndex(int r, int g, int b) {
62*970e1046SAndroid Build Coastguard Worker     return (r << (INDEX_BITS * 2)) + (r << (INDEX_BITS + 1)) + r + (g << INDEX_BITS) + g + b;
63*970e1046SAndroid Build Coastguard Worker   }
64*970e1046SAndroid Build Coastguard Worker 
constructHistogram(Map<Integer, Integer> pixels)65*970e1046SAndroid Build Coastguard Worker   void constructHistogram(Map<Integer, Integer> pixels) {
66*970e1046SAndroid Build Coastguard Worker     weights = new int[TOTAL_SIZE];
67*970e1046SAndroid Build Coastguard Worker     momentsR = new int[TOTAL_SIZE];
68*970e1046SAndroid Build Coastguard Worker     momentsG = new int[TOTAL_SIZE];
69*970e1046SAndroid Build Coastguard Worker     momentsB = new int[TOTAL_SIZE];
70*970e1046SAndroid Build Coastguard Worker     moments = new double[TOTAL_SIZE];
71*970e1046SAndroid Build Coastguard Worker 
72*970e1046SAndroid Build Coastguard Worker     for (Map.Entry<Integer, Integer> pair : pixels.entrySet()) {
73*970e1046SAndroid Build Coastguard Worker       int pixel = pair.getKey();
74*970e1046SAndroid Build Coastguard Worker       int count = pair.getValue();
75*970e1046SAndroid Build Coastguard Worker       int red = ColorUtils.redFromArgb(pixel);
76*970e1046SAndroid Build Coastguard Worker       int green = ColorUtils.greenFromArgb(pixel);
77*970e1046SAndroid Build Coastguard Worker       int blue = ColorUtils.blueFromArgb(pixel);
78*970e1046SAndroid Build Coastguard Worker       int bitsToRemove = 8 - INDEX_BITS;
79*970e1046SAndroid Build Coastguard Worker       int iR = (red >> bitsToRemove) + 1;
80*970e1046SAndroid Build Coastguard Worker       int iG = (green >> bitsToRemove) + 1;
81*970e1046SAndroid Build Coastguard Worker       int iB = (blue >> bitsToRemove) + 1;
82*970e1046SAndroid Build Coastguard Worker       int index = getIndex(iR, iG, iB);
83*970e1046SAndroid Build Coastguard Worker       weights[index] += count;
84*970e1046SAndroid Build Coastguard Worker       momentsR[index] += (red * count);
85*970e1046SAndroid Build Coastguard Worker       momentsG[index] += (green * count);
86*970e1046SAndroid Build Coastguard Worker       momentsB[index] += (blue * count);
87*970e1046SAndroid Build Coastguard Worker       moments[index] += (count * ((red * red) + (green * green) + (blue * blue)));
88*970e1046SAndroid Build Coastguard Worker     }
89*970e1046SAndroid Build Coastguard Worker   }
90*970e1046SAndroid Build Coastguard Worker 
createMoments()91*970e1046SAndroid Build Coastguard Worker   void createMoments() {
92*970e1046SAndroid Build Coastguard Worker     for (int r = 1; r < INDEX_COUNT; ++r) {
93*970e1046SAndroid Build Coastguard Worker       int[] area = new int[INDEX_COUNT];
94*970e1046SAndroid Build Coastguard Worker       int[] areaR = new int[INDEX_COUNT];
95*970e1046SAndroid Build Coastguard Worker       int[] areaG = new int[INDEX_COUNT];
96*970e1046SAndroid Build Coastguard Worker       int[] areaB = new int[INDEX_COUNT];
97*970e1046SAndroid Build Coastguard Worker       double[] area2 = new double[INDEX_COUNT];
98*970e1046SAndroid Build Coastguard Worker 
99*970e1046SAndroid Build Coastguard Worker       for (int g = 1; g < INDEX_COUNT; ++g) {
100*970e1046SAndroid Build Coastguard Worker         int line = 0;
101*970e1046SAndroid Build Coastguard Worker         int lineR = 0;
102*970e1046SAndroid Build Coastguard Worker         int lineG = 0;
103*970e1046SAndroid Build Coastguard Worker         int lineB = 0;
104*970e1046SAndroid Build Coastguard Worker         double line2 = 0.0;
105*970e1046SAndroid Build Coastguard Worker         for (int b = 1; b < INDEX_COUNT; ++b) {
106*970e1046SAndroid Build Coastguard Worker           int index = getIndex(r, g, b);
107*970e1046SAndroid Build Coastguard Worker           line += weights[index];
108*970e1046SAndroid Build Coastguard Worker           lineR += momentsR[index];
109*970e1046SAndroid Build Coastguard Worker           lineG += momentsG[index];
110*970e1046SAndroid Build Coastguard Worker           lineB += momentsB[index];
111*970e1046SAndroid Build Coastguard Worker           line2 += moments[index];
112*970e1046SAndroid Build Coastguard Worker 
113*970e1046SAndroid Build Coastguard Worker           area[b] += line;
114*970e1046SAndroid Build Coastguard Worker           areaR[b] += lineR;
115*970e1046SAndroid Build Coastguard Worker           areaG[b] += lineG;
116*970e1046SAndroid Build Coastguard Worker           areaB[b] += lineB;
117*970e1046SAndroid Build Coastguard Worker           area2[b] += line2;
118*970e1046SAndroid Build Coastguard Worker 
119*970e1046SAndroid Build Coastguard Worker           int previousIndex = getIndex(r - 1, g, b);
120*970e1046SAndroid Build Coastguard Worker           weights[index] = weights[previousIndex] + area[b];
121*970e1046SAndroid Build Coastguard Worker           momentsR[index] = momentsR[previousIndex] + areaR[b];
122*970e1046SAndroid Build Coastguard Worker           momentsG[index] = momentsG[previousIndex] + areaG[b];
123*970e1046SAndroid Build Coastguard Worker           momentsB[index] = momentsB[previousIndex] + areaB[b];
124*970e1046SAndroid Build Coastguard Worker           moments[index] = moments[previousIndex] + area2[b];
125*970e1046SAndroid Build Coastguard Worker         }
126*970e1046SAndroid Build Coastguard Worker       }
127*970e1046SAndroid Build Coastguard Worker     }
128*970e1046SAndroid Build Coastguard Worker   }
129*970e1046SAndroid Build Coastguard Worker 
createBoxes(int maxColorCount)130*970e1046SAndroid Build Coastguard Worker   CreateBoxesResult createBoxes(int maxColorCount) {
131*970e1046SAndroid Build Coastguard Worker     cubes = new Box[maxColorCount];
132*970e1046SAndroid Build Coastguard Worker     for (int i = 0; i < maxColorCount; i++) {
133*970e1046SAndroid Build Coastguard Worker       cubes[i] = new Box();
134*970e1046SAndroid Build Coastguard Worker     }
135*970e1046SAndroid Build Coastguard Worker     double[] volumeVariance = new double[maxColorCount];
136*970e1046SAndroid Build Coastguard Worker     Box firstBox = cubes[0];
137*970e1046SAndroid Build Coastguard Worker     firstBox.r1 = INDEX_COUNT - 1;
138*970e1046SAndroid Build Coastguard Worker     firstBox.g1 = INDEX_COUNT - 1;
139*970e1046SAndroid Build Coastguard Worker     firstBox.b1 = INDEX_COUNT - 1;
140*970e1046SAndroid Build Coastguard Worker 
141*970e1046SAndroid Build Coastguard Worker     int generatedColorCount = maxColorCount;
142*970e1046SAndroid Build Coastguard Worker     int next = 0;
143*970e1046SAndroid Build Coastguard Worker     for (int i = 1; i < maxColorCount; i++) {
144*970e1046SAndroid Build Coastguard Worker       if (cut(cubes[next], cubes[i])) {
145*970e1046SAndroid Build Coastguard Worker         volumeVariance[next] = (cubes[next].vol > 1) ? variance(cubes[next]) : 0.0;
146*970e1046SAndroid Build Coastguard Worker         volumeVariance[i] = (cubes[i].vol > 1) ? variance(cubes[i]) : 0.0;
147*970e1046SAndroid Build Coastguard Worker       } else {
148*970e1046SAndroid Build Coastguard Worker         volumeVariance[next] = 0.0;
149*970e1046SAndroid Build Coastguard Worker         i--;
150*970e1046SAndroid Build Coastguard Worker       }
151*970e1046SAndroid Build Coastguard Worker 
152*970e1046SAndroid Build Coastguard Worker       next = 0;
153*970e1046SAndroid Build Coastguard Worker 
154*970e1046SAndroid Build Coastguard Worker       double temp = volumeVariance[0];
155*970e1046SAndroid Build Coastguard Worker       for (int j = 1; j <= i; j++) {
156*970e1046SAndroid Build Coastguard Worker         if (volumeVariance[j] > temp) {
157*970e1046SAndroid Build Coastguard Worker           temp = volumeVariance[j];
158*970e1046SAndroid Build Coastguard Worker           next = j;
159*970e1046SAndroid Build Coastguard Worker         }
160*970e1046SAndroid Build Coastguard Worker       }
161*970e1046SAndroid Build Coastguard Worker       if (temp <= 0.0) {
162*970e1046SAndroid Build Coastguard Worker         generatedColorCount = i + 1;
163*970e1046SAndroid Build Coastguard Worker         break;
164*970e1046SAndroid Build Coastguard Worker       }
165*970e1046SAndroid Build Coastguard Worker     }
166*970e1046SAndroid Build Coastguard Worker 
167*970e1046SAndroid Build Coastguard Worker     return new CreateBoxesResult(maxColorCount, generatedColorCount);
168*970e1046SAndroid Build Coastguard Worker   }
169*970e1046SAndroid Build Coastguard Worker 
createResult(int colorCount)170*970e1046SAndroid Build Coastguard Worker   List<Integer> createResult(int colorCount) {
171*970e1046SAndroid Build Coastguard Worker     List<Integer> colors = new ArrayList<>();
172*970e1046SAndroid Build Coastguard Worker     for (int i = 0; i < colorCount; ++i) {
173*970e1046SAndroid Build Coastguard Worker       Box cube = cubes[i];
174*970e1046SAndroid Build Coastguard Worker       int weight = volume(cube, weights);
175*970e1046SAndroid Build Coastguard Worker       if (weight > 0) {
176*970e1046SAndroid Build Coastguard Worker         int r = volume(cube, momentsR) / weight;
177*970e1046SAndroid Build Coastguard Worker         int g = volume(cube, momentsG) / weight;
178*970e1046SAndroid Build Coastguard Worker         int b = volume(cube, momentsB) / weight;
179*970e1046SAndroid Build Coastguard Worker         int color = (255 << 24) | ((r & 0x0ff) << 16) | ((g & 0x0ff) << 8) | (b & 0x0ff);
180*970e1046SAndroid Build Coastguard Worker         colors.add(color);
181*970e1046SAndroid Build Coastguard Worker       }
182*970e1046SAndroid Build Coastguard Worker     }
183*970e1046SAndroid Build Coastguard Worker     return colors;
184*970e1046SAndroid Build Coastguard Worker   }
185*970e1046SAndroid Build Coastguard Worker 
variance(Box cube)186*970e1046SAndroid Build Coastguard Worker   double variance(Box cube) {
187*970e1046SAndroid Build Coastguard Worker     int dr = volume(cube, momentsR);
188*970e1046SAndroid Build Coastguard Worker     int dg = volume(cube, momentsG);
189*970e1046SAndroid Build Coastguard Worker     int db = volume(cube, momentsB);
190*970e1046SAndroid Build Coastguard Worker     double xx =
191*970e1046SAndroid Build Coastguard Worker         moments[getIndex(cube.r1, cube.g1, cube.b1)]
192*970e1046SAndroid Build Coastguard Worker             - moments[getIndex(cube.r1, cube.g1, cube.b0)]
193*970e1046SAndroid Build Coastguard Worker             - moments[getIndex(cube.r1, cube.g0, cube.b1)]
194*970e1046SAndroid Build Coastguard Worker             + moments[getIndex(cube.r1, cube.g0, cube.b0)]
195*970e1046SAndroid Build Coastguard Worker             - moments[getIndex(cube.r0, cube.g1, cube.b1)]
196*970e1046SAndroid Build Coastguard Worker             + moments[getIndex(cube.r0, cube.g1, cube.b0)]
197*970e1046SAndroid Build Coastguard Worker             + moments[getIndex(cube.r0, cube.g0, cube.b1)]
198*970e1046SAndroid Build Coastguard Worker             - moments[getIndex(cube.r0, cube.g0, cube.b0)];
199*970e1046SAndroid Build Coastguard Worker 
200*970e1046SAndroid Build Coastguard Worker     int hypotenuse = dr * dr + dg * dg + db * db;
201*970e1046SAndroid Build Coastguard Worker     int volume = volume(cube, weights);
202*970e1046SAndroid Build Coastguard Worker     return xx - hypotenuse / ((double) volume);
203*970e1046SAndroid Build Coastguard Worker   }
204*970e1046SAndroid Build Coastguard Worker 
cut(Box one, Box two)205*970e1046SAndroid Build Coastguard Worker   Boolean cut(Box one, Box two) {
206*970e1046SAndroid Build Coastguard Worker     int wholeR = volume(one, momentsR);
207*970e1046SAndroid Build Coastguard Worker     int wholeG = volume(one, momentsG);
208*970e1046SAndroid Build Coastguard Worker     int wholeB = volume(one, momentsB);
209*970e1046SAndroid Build Coastguard Worker     int wholeW = volume(one, weights);
210*970e1046SAndroid Build Coastguard Worker 
211*970e1046SAndroid Build Coastguard Worker     MaximizeResult maxRResult =
212*970e1046SAndroid Build Coastguard Worker         maximize(one, Direction.RED, one.r0 + 1, one.r1, wholeR, wholeG, wholeB, wholeW);
213*970e1046SAndroid Build Coastguard Worker     MaximizeResult maxGResult =
214*970e1046SAndroid Build Coastguard Worker         maximize(one, Direction.GREEN, one.g0 + 1, one.g1, wholeR, wholeG, wholeB, wholeW);
215*970e1046SAndroid Build Coastguard Worker     MaximizeResult maxBResult =
216*970e1046SAndroid Build Coastguard Worker         maximize(one, Direction.BLUE, one.b0 + 1, one.b1, wholeR, wholeG, wholeB, wholeW);
217*970e1046SAndroid Build Coastguard Worker     Direction cutDirection;
218*970e1046SAndroid Build Coastguard Worker     double maxR = maxRResult.maximum;
219*970e1046SAndroid Build Coastguard Worker     double maxG = maxGResult.maximum;
220*970e1046SAndroid Build Coastguard Worker     double maxB = maxBResult.maximum;
221*970e1046SAndroid Build Coastguard Worker     if (maxR >= maxG && maxR >= maxB) {
222*970e1046SAndroid Build Coastguard Worker       if (maxRResult.cutLocation < 0) {
223*970e1046SAndroid Build Coastguard Worker         return false;
224*970e1046SAndroid Build Coastguard Worker       }
225*970e1046SAndroid Build Coastguard Worker       cutDirection = Direction.RED;
226*970e1046SAndroid Build Coastguard Worker     } else if (maxG >= maxR && maxG >= maxB) {
227*970e1046SAndroid Build Coastguard Worker       cutDirection = Direction.GREEN;
228*970e1046SAndroid Build Coastguard Worker     } else {
229*970e1046SAndroid Build Coastguard Worker       cutDirection = Direction.BLUE;
230*970e1046SAndroid Build Coastguard Worker     }
231*970e1046SAndroid Build Coastguard Worker 
232*970e1046SAndroid Build Coastguard Worker     two.r1 = one.r1;
233*970e1046SAndroid Build Coastguard Worker     two.g1 = one.g1;
234*970e1046SAndroid Build Coastguard Worker     two.b1 = one.b1;
235*970e1046SAndroid Build Coastguard Worker 
236*970e1046SAndroid Build Coastguard Worker     switch (cutDirection) {
237*970e1046SAndroid Build Coastguard Worker       case RED:
238*970e1046SAndroid Build Coastguard Worker         one.r1 = maxRResult.cutLocation;
239*970e1046SAndroid Build Coastguard Worker         two.r0 = one.r1;
240*970e1046SAndroid Build Coastguard Worker         two.g0 = one.g0;
241*970e1046SAndroid Build Coastguard Worker         two.b0 = one.b0;
242*970e1046SAndroid Build Coastguard Worker         break;
243*970e1046SAndroid Build Coastguard Worker       case GREEN:
244*970e1046SAndroid Build Coastguard Worker         one.g1 = maxGResult.cutLocation;
245*970e1046SAndroid Build Coastguard Worker         two.r0 = one.r0;
246*970e1046SAndroid Build Coastguard Worker         two.g0 = one.g1;
247*970e1046SAndroid Build Coastguard Worker         two.b0 = one.b0;
248*970e1046SAndroid Build Coastguard Worker         break;
249*970e1046SAndroid Build Coastguard Worker       case BLUE:
250*970e1046SAndroid Build Coastguard Worker         one.b1 = maxBResult.cutLocation;
251*970e1046SAndroid Build Coastguard Worker         two.r0 = one.r0;
252*970e1046SAndroid Build Coastguard Worker         two.g0 = one.g0;
253*970e1046SAndroid Build Coastguard Worker         two.b0 = one.b1;
254*970e1046SAndroid Build Coastguard Worker         break;
255*970e1046SAndroid Build Coastguard Worker     }
256*970e1046SAndroid Build Coastguard Worker 
257*970e1046SAndroid Build Coastguard Worker     one.vol = (one.r1 - one.r0) * (one.g1 - one.g0) * (one.b1 - one.b0);
258*970e1046SAndroid Build Coastguard Worker     two.vol = (two.r1 - two.r0) * (two.g1 - two.g0) * (two.b1 - two.b0);
259*970e1046SAndroid Build Coastguard Worker 
260*970e1046SAndroid Build Coastguard Worker     return true;
261*970e1046SAndroid Build Coastguard Worker   }
262*970e1046SAndroid Build Coastguard Worker 
maximize( Box cube, Direction direction, int first, int last, int wholeR, int wholeG, int wholeB, int wholeW)263*970e1046SAndroid Build Coastguard Worker   MaximizeResult maximize(
264*970e1046SAndroid Build Coastguard Worker       Box cube,
265*970e1046SAndroid Build Coastguard Worker       Direction direction,
266*970e1046SAndroid Build Coastguard Worker       int first,
267*970e1046SAndroid Build Coastguard Worker       int last,
268*970e1046SAndroid Build Coastguard Worker       int wholeR,
269*970e1046SAndroid Build Coastguard Worker       int wholeG,
270*970e1046SAndroid Build Coastguard Worker       int wholeB,
271*970e1046SAndroid Build Coastguard Worker       int wholeW) {
272*970e1046SAndroid Build Coastguard Worker     int bottomR = bottom(cube, direction, momentsR);
273*970e1046SAndroid Build Coastguard Worker     int bottomG = bottom(cube, direction, momentsG);
274*970e1046SAndroid Build Coastguard Worker     int bottomB = bottom(cube, direction, momentsB);
275*970e1046SAndroid Build Coastguard Worker     int bottomW = bottom(cube, direction, weights);
276*970e1046SAndroid Build Coastguard Worker 
277*970e1046SAndroid Build Coastguard Worker     double max = 0.0;
278*970e1046SAndroid Build Coastguard Worker     int cut = -1;
279*970e1046SAndroid Build Coastguard Worker 
280*970e1046SAndroid Build Coastguard Worker     int halfR = 0;
281*970e1046SAndroid Build Coastguard Worker     int halfG = 0;
282*970e1046SAndroid Build Coastguard Worker     int halfB = 0;
283*970e1046SAndroid Build Coastguard Worker     int halfW = 0;
284*970e1046SAndroid Build Coastguard Worker     for (int i = first; i < last; i++) {
285*970e1046SAndroid Build Coastguard Worker       halfR = bottomR + top(cube, direction, i, momentsR);
286*970e1046SAndroid Build Coastguard Worker       halfG = bottomG + top(cube, direction, i, momentsG);
287*970e1046SAndroid Build Coastguard Worker       halfB = bottomB + top(cube, direction, i, momentsB);
288*970e1046SAndroid Build Coastguard Worker       halfW = bottomW + top(cube, direction, i, weights);
289*970e1046SAndroid Build Coastguard Worker       if (halfW == 0) {
290*970e1046SAndroid Build Coastguard Worker         continue;
291*970e1046SAndroid Build Coastguard Worker       }
292*970e1046SAndroid Build Coastguard Worker 
293*970e1046SAndroid Build Coastguard Worker       double tempNumerator = halfR * halfR + halfG * halfG + halfB * halfB;
294*970e1046SAndroid Build Coastguard Worker       double tempDenominator = halfW;
295*970e1046SAndroid Build Coastguard Worker       double temp = tempNumerator / tempDenominator;
296*970e1046SAndroid Build Coastguard Worker 
297*970e1046SAndroid Build Coastguard Worker       halfR = wholeR - halfR;
298*970e1046SAndroid Build Coastguard Worker       halfG = wholeG - halfG;
299*970e1046SAndroid Build Coastguard Worker       halfB = wholeB - halfB;
300*970e1046SAndroid Build Coastguard Worker       halfW = wholeW - halfW;
301*970e1046SAndroid Build Coastguard Worker       if (halfW == 0) {
302*970e1046SAndroid Build Coastguard Worker         continue;
303*970e1046SAndroid Build Coastguard Worker       }
304*970e1046SAndroid Build Coastguard Worker 
305*970e1046SAndroid Build Coastguard Worker       tempNumerator = halfR * halfR + halfG * halfG + halfB * halfB;
306*970e1046SAndroid Build Coastguard Worker       tempDenominator = halfW;
307*970e1046SAndroid Build Coastguard Worker       temp += (tempNumerator / tempDenominator);
308*970e1046SAndroid Build Coastguard Worker 
309*970e1046SAndroid Build Coastguard Worker       if (temp > max) {
310*970e1046SAndroid Build Coastguard Worker         max = temp;
311*970e1046SAndroid Build Coastguard Worker         cut = i;
312*970e1046SAndroid Build Coastguard Worker       }
313*970e1046SAndroid Build Coastguard Worker     }
314*970e1046SAndroid Build Coastguard Worker     return new MaximizeResult(cut, max);
315*970e1046SAndroid Build Coastguard Worker   }
316*970e1046SAndroid Build Coastguard Worker 
volume(Box cube, int[] moment)317*970e1046SAndroid Build Coastguard Worker   static int volume(Box cube, int[] moment) {
318*970e1046SAndroid Build Coastguard Worker     return (moment[getIndex(cube.r1, cube.g1, cube.b1)]
319*970e1046SAndroid Build Coastguard Worker         - moment[getIndex(cube.r1, cube.g1, cube.b0)]
320*970e1046SAndroid Build Coastguard Worker         - moment[getIndex(cube.r1, cube.g0, cube.b1)]
321*970e1046SAndroid Build Coastguard Worker         + moment[getIndex(cube.r1, cube.g0, cube.b0)]
322*970e1046SAndroid Build Coastguard Worker         - moment[getIndex(cube.r0, cube.g1, cube.b1)]
323*970e1046SAndroid Build Coastguard Worker         + moment[getIndex(cube.r0, cube.g1, cube.b0)]
324*970e1046SAndroid Build Coastguard Worker         + moment[getIndex(cube.r0, cube.g0, cube.b1)]
325*970e1046SAndroid Build Coastguard Worker         - moment[getIndex(cube.r0, cube.g0, cube.b0)]);
326*970e1046SAndroid Build Coastguard Worker   }
327*970e1046SAndroid Build Coastguard Worker 
bottom(Box cube, Direction direction, int[] moment)328*970e1046SAndroid Build Coastguard Worker   static int bottom(Box cube, Direction direction, int[] moment) {
329*970e1046SAndroid Build Coastguard Worker     switch (direction) {
330*970e1046SAndroid Build Coastguard Worker       case RED:
331*970e1046SAndroid Build Coastguard Worker         return -moment[getIndex(cube.r0, cube.g1, cube.b1)]
332*970e1046SAndroid Build Coastguard Worker             + moment[getIndex(cube.r0, cube.g1, cube.b0)]
333*970e1046SAndroid Build Coastguard Worker             + moment[getIndex(cube.r0, cube.g0, cube.b1)]
334*970e1046SAndroid Build Coastguard Worker             - moment[getIndex(cube.r0, cube.g0, cube.b0)];
335*970e1046SAndroid Build Coastguard Worker       case GREEN:
336*970e1046SAndroid Build Coastguard Worker         return -moment[getIndex(cube.r1, cube.g0, cube.b1)]
337*970e1046SAndroid Build Coastguard Worker             + moment[getIndex(cube.r1, cube.g0, cube.b0)]
338*970e1046SAndroid Build Coastguard Worker             + moment[getIndex(cube.r0, cube.g0, cube.b1)]
339*970e1046SAndroid Build Coastguard Worker             - moment[getIndex(cube.r0, cube.g0, cube.b0)];
340*970e1046SAndroid Build Coastguard Worker       case BLUE:
341*970e1046SAndroid Build Coastguard Worker         return -moment[getIndex(cube.r1, cube.g1, cube.b0)]
342*970e1046SAndroid Build Coastguard Worker             + moment[getIndex(cube.r1, cube.g0, cube.b0)]
343*970e1046SAndroid Build Coastguard Worker             + moment[getIndex(cube.r0, cube.g1, cube.b0)]
344*970e1046SAndroid Build Coastguard Worker             - moment[getIndex(cube.r0, cube.g0, cube.b0)];
345*970e1046SAndroid Build Coastguard Worker     }
346*970e1046SAndroid Build Coastguard Worker     throw new IllegalArgumentException("unexpected direction " + direction);
347*970e1046SAndroid Build Coastguard Worker   }
348*970e1046SAndroid Build Coastguard Worker 
top(Box cube, Direction direction, int position, int[] moment)349*970e1046SAndroid Build Coastguard Worker   static int top(Box cube, Direction direction, int position, int[] moment) {
350*970e1046SAndroid Build Coastguard Worker     switch (direction) {
351*970e1046SAndroid Build Coastguard Worker       case RED:
352*970e1046SAndroid Build Coastguard Worker         return (moment[getIndex(position, cube.g1, cube.b1)]
353*970e1046SAndroid Build Coastguard Worker             - moment[getIndex(position, cube.g1, cube.b0)]
354*970e1046SAndroid Build Coastguard Worker             - moment[getIndex(position, cube.g0, cube.b1)]
355*970e1046SAndroid Build Coastguard Worker             + moment[getIndex(position, cube.g0, cube.b0)]);
356*970e1046SAndroid Build Coastguard Worker       case GREEN:
357*970e1046SAndroid Build Coastguard Worker         return (moment[getIndex(cube.r1, position, cube.b1)]
358*970e1046SAndroid Build Coastguard Worker             - moment[getIndex(cube.r1, position, cube.b0)]
359*970e1046SAndroid Build Coastguard Worker             - moment[getIndex(cube.r0, position, cube.b1)]
360*970e1046SAndroid Build Coastguard Worker             + moment[getIndex(cube.r0, position, cube.b0)]);
361*970e1046SAndroid Build Coastguard Worker       case BLUE:
362*970e1046SAndroid Build Coastguard Worker         return (moment[getIndex(cube.r1, cube.g1, position)]
363*970e1046SAndroid Build Coastguard Worker             - moment[getIndex(cube.r1, cube.g0, position)]
364*970e1046SAndroid Build Coastguard Worker             - moment[getIndex(cube.r0, cube.g1, position)]
365*970e1046SAndroid Build Coastguard Worker             + moment[getIndex(cube.r0, cube.g0, position)]);
366*970e1046SAndroid Build Coastguard Worker     }
367*970e1046SAndroid Build Coastguard Worker     throw new IllegalArgumentException("unexpected direction " + direction);
368*970e1046SAndroid Build Coastguard Worker   }
369*970e1046SAndroid Build Coastguard Worker 
370*970e1046SAndroid Build Coastguard Worker   private static enum Direction {
371*970e1046SAndroid Build Coastguard Worker     RED,
372*970e1046SAndroid Build Coastguard Worker     GREEN,
373*970e1046SAndroid Build Coastguard Worker     BLUE
374*970e1046SAndroid Build Coastguard Worker   }
375*970e1046SAndroid Build Coastguard Worker 
376*970e1046SAndroid Build Coastguard Worker   private static final class MaximizeResult {
377*970e1046SAndroid Build Coastguard Worker     // < 0 if cut impossible
378*970e1046SAndroid Build Coastguard Worker     int cutLocation;
379*970e1046SAndroid Build Coastguard Worker     double maximum;
380*970e1046SAndroid Build Coastguard Worker 
MaximizeResult(int cut, double max)381*970e1046SAndroid Build Coastguard Worker     MaximizeResult(int cut, double max) {
382*970e1046SAndroid Build Coastguard Worker       this.cutLocation = cut;
383*970e1046SAndroid Build Coastguard Worker       this.maximum = max;
384*970e1046SAndroid Build Coastguard Worker     }
385*970e1046SAndroid Build Coastguard Worker   }
386*970e1046SAndroid Build Coastguard Worker 
387*970e1046SAndroid Build Coastguard Worker   private static final class CreateBoxesResult {
388*970e1046SAndroid Build Coastguard Worker     int requestedCount;
389*970e1046SAndroid Build Coastguard Worker     int resultCount;
390*970e1046SAndroid Build Coastguard Worker 
CreateBoxesResult(int requestedCount, int resultCount)391*970e1046SAndroid Build Coastguard Worker     CreateBoxesResult(int requestedCount, int resultCount) {
392*970e1046SAndroid Build Coastguard Worker       this.requestedCount = requestedCount;
393*970e1046SAndroid Build Coastguard Worker       this.resultCount = resultCount;
394*970e1046SAndroid Build Coastguard Worker     }
395*970e1046SAndroid Build Coastguard Worker   }
396*970e1046SAndroid Build Coastguard Worker 
397*970e1046SAndroid Build Coastguard Worker   private static final class Box {
398*970e1046SAndroid Build Coastguard Worker     int r0 = 0;
399*970e1046SAndroid Build Coastguard Worker     int r1 = 0;
400*970e1046SAndroid Build Coastguard Worker     int g0 = 0;
401*970e1046SAndroid Build Coastguard Worker     int g1 = 0;
402*970e1046SAndroid Build Coastguard Worker     int b0 = 0;
403*970e1046SAndroid Build Coastguard Worker     int b1 = 0;
404*970e1046SAndroid Build Coastguard Worker     int vol = 0;
405*970e1046SAndroid Build Coastguard Worker   }
406*970e1046SAndroid Build Coastguard Worker }
407