/*
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */

package org.apache.commons.imaging.formats.jpeg.decoder;

final class YCbCrConverter {
    private static final int[] REDS = new int[256 * 256];
    private static final int[] BLUES = new int[256 * 256];
    private static final int[] GREENS1 = new int[256 * 256];
    private static final int[] GREENS2 = new int[256 * 512];

    static {
        /*
         * Why use (Cr << 8) | Y and not (Y << 8) | Cr as the index? Y changes
         * often, while Cb and Cr is usually subsampled less often and repeats
         * itself between adjacent pixels, so using it as the high order byte
         * gives higher locality of reference.
         */
        for (int Y = 0; Y < 256; Y++) {
            for (int Cr = 0; Cr < 256; Cr++) {
                int r = Y + fastRound(1.402f * (Cr - 128));
                if (r < 0) {
                    r = 0;
                }
                if (r > 255) {
                    r = 255;
                }
                REDS[(Cr << 8) | Y] = r << 16;
            }
        }
        for (int Y = 0; Y < 256; Y++) {
            for (int Cb = 0; Cb < 256; Cb++) {
                int b = Y + fastRound(1.772f * (Cb - 128));
                if (b < 0) {
                    b = 0;
                }
                if (b > 255) {
                    b = 255;
                }
                BLUES[(Cb << 8) | Y] = b;
            }
        }
        // green is the hardest
        // Math.round((float) (Y - 0.34414*(Cb-128) - 0.71414*(Cr-128)))
        // but Y is integral
        // = Y - Math.round((float) (0.34414*(Cb-128) + 0.71414*(Cr-128)))
        // = Y - Math.round(f(Cb, Cr))
        // where
        // f(Cb, Cr) = 0.34414*(Cb-128) + 0.71414*(Cr-128)
        // Cb and Cr terms each vary from 255-128 = 127 to 0-128 = -128
        // Linear function, so only examine endpoints:
        // Cb term Cr term Result
        // 127 127 134.4
        // -128 -128 -135.4
        // 127 -128 -47.7
        // -128 127 46.6
        // Thus with -135 being the minimum and 134 the maximum,
        // there is a range of 269 values,
        // and 135 needs to be added to make it zero-based.

        // As for Y - f(Cb, Cr)
        // the range becomes:
        // Y f(Cb, Cr)
        // 255 -135
        // 255 134
        // 0 -135
        // 0 134
        // thus the range is [-134,390] and has 524 values
        // but is clamped to [0, 255]
        for (int Cb = 0; Cb < 256; Cb++) {
            for (int Cr = 0; Cr < 256; Cr++) {
                final int value = fastRound(0.34414f * (Cb - 128) + 0.71414f
                        * (Cr - 128));
                GREENS1[(Cb << 8) | Cr] = value + 135;
            }
        }
        for (int Y = 0; Y < 256; Y++) {
            for (int value = 0; value < 270; value++) {
                int green = Y - (value - 135);
                if (green < 0) {
                    green = 0;
                } else if (green > 255) {
                    green = 255;
                }
                GREENS2[(value << 8) | Y] = green << 8;
            }
        }
    }

    private YCbCrConverter() {
    }

    private static int fastRound(final float x) {
        // Math.round() is very slow
        return (int) (x + 0.5f);
    }

    public static int convertYCbCrToRGB(final int Y, final int Cb, final int Cr) {
        final int r = REDS[(Cr << 8) | Y];
        final int g1 = GREENS1[(Cb << 8) | Cr];
        final int g = GREENS2[(g1 << 8) | Y];
        final int b = BLUES[(Cb << 8) | Y];
        return r | g | b;
    }
}
