/*
 * @(#)Colors.java  
 *
 * Copyright (c) 2005-2012 Werner Randelshofer, Immensee, Switzerland.
 * All rights reserved.
 *
 * You may not use, copy or modify this file, except in compliance with the
 * license agreement you entered into with Werner Randelshofer.
 * For details see accompanying license terms.
 */
package org.monte.media.image;

import java.awt.*;
import java.awt.image.*;
import java.util.*;
import static java.lang.Math.*;

/**
 * Utility methods for ColorModels.
 *
 * @author  Werner Randelshofer
 * @version $Id: ColorModels.java 161 2012-01-31 12:51:31Z werner $
 */
public class ColorModels {

    /**
     * Prevent instance creation.
     */
    private ColorModels() {
    }

    /**
     * Returns a descriptive string for the provided color model.
     */
    public static String toString(ColorModel cm) {
        StringBuffer buf = new StringBuffer();
        if (cm instanceof DirectColorModel) {
            DirectColorModel dcm = (DirectColorModel) cm;
            buf.append("Direct Color Model ");

            int[] masks = dcm.getMasks();
            int totalBits = 0;
            MaskEntry[] entries = new MaskEntry[masks.length];
            for (int i = 0; i < masks.length; i++) {
                switch (i) {
                    case 0:
                        entries[i] = new MaskEntry(masks[i], "R");
                        break;
                    case 1:
                        entries[i] = new MaskEntry(masks[i], "G");
                        break;
                    case 2:
                        entries[i] = new MaskEntry(masks[i], "B");
                        break;
                    case 3:
                        entries[i] = new MaskEntry(masks[i], "A");
                        break;
                }
                totalBits += entries[i].getBits();
            }
            buf.append(totalBits);
            buf.append(" Bit ");
            Arrays.sort(entries);
            for (int i = 0; i < entries.length; i++) {
                buf.append(entries[i]);
            }
        } else if (cm instanceof IndexColorModel) {
            buf.append("Index Color Model ");
            IndexColorModel icm = (IndexColorModel) cm;
            int mapSize = icm.getMapSize();
            buf.append(icm.getMapSize());
            buf.append(" Colors");
        } else {
            buf.append(cm.toString());
        }
        switch (cm.getTransparency()) {
            case Transparency.OPAQUE:
                break;
            case Transparency.BITMASK:
                buf.append(" with Alpha Bitmask");
                break;
            case Transparency.TRANSLUCENT:
                buf.append(" with Alpha Translucency");
                break;
        }
        return buf.toString();
    }

    private static class MaskEntry implements Comparable {

        private int mask;
        private int bits;
        private String name;

        public MaskEntry(int mask, String name) {
            this.mask = mask;
            this.name = name;

            for (int i = 0; i < 32; i++) {
                if (((mask >>> i) & 1) == 1) {
                    bits++;
                }
            }
        }

        public int getBits() {
            return bits;
        }

        public String toString() {
            return name;
        }

        public int compareTo(Object o) {
            MaskEntry that = (MaskEntry) o;
            return that.mask - this.mask;
        }
    }

    /** RGB in the range [0,1] to YCC in the range Y=[0,1], Cb=[-0.5,0.5],
     * Cr=[-0.5,0.5]
     */
    public static void RGBtoYCC(float[] rgb, float[] ycc) {
        float R = max(0f, min(1f, rgb[0]));
        float G = max(0f, min(1f, rgb[1]));
        float B = max(0f, min(1f, rgb[2]));
        float Y = 0.3f * R + 0.6f * G + 0.1f * B;
        float V = R - Y;
        float U = B - Y;
        float Cb = (U / 2f) /*+ 0.5f*/;
        float Cr = (V / 1.6f) /*+ 0.5f*/;
        ycc[0] = Y;
        ycc[1] = Cb;
        ycc[2] = Cr;
    }

    /** YCC in the range Y=[0,1], Cb=[-0.5,0.5], Cr=[-0.5,0.5] 
     * to RGB in the range [0,1] */
    public static void YCCtoRGB(float[] ycc, float[] rgb) {
        float Y = max(0f, min(1f, ycc[0]));
        float Cb = max(-0.5f, min(0.5f, ycc[1]));
        float Cr = max(-0.5f, min(0.5f, ycc[2]));
        float U = (Cb /*- 0.5f*/) * 2f;
        float V = (Cr /*- 0.5f*/) * 1.6f;
        float R = V + Y;
        float B = U + Y;
        float G = (Y - 0.3f * R - 0.1f * B) / 0.6f;
        rgb[0] = max(0f, min(1f, R));
        rgb[1] = max(0f, min(1f, G));
        rgb[2] = max(0f, min(1f, B));
    }

    /** RGB 8-bit per channel to YCC 16-bit per channel. */
    public static void RGBtoYCC(int[] rgb, int[] ycc) {
        int R = rgb[0];
        int G = rgb[1];
        int B = rgb[2];
        int Y = 77 * R + 153 * G + 26 * B;
        int V = R * 256 - Y;
        int U = B * 256 - Y;
        int Cb = (U / 2) + 128 * 256;
        int Cr = (V * 5 / 8) + 128 * 256;
        ycc[0] = Y;
        ycc[1] = Cb;
        ycc[2] = Cr;
    }

    /** RGB 8-bit per channel to YCC 16-bit per channel. */
    public static void RGBtoYCC(int rgb, int[] ycc) {
        int R = (rgb & 0xff0000) >>> 16;
        int G = (rgb & 0xff00) >>> 8;
        int B = rgb & 0xff;
        int Y = 77 * R + 153 * G + 26 * B;
        int V = R * 256 - Y;
        int U = B * 256 - Y;
        int Cb = (U / 2) + 128 * 256;
        int Cr = (V * 5 / 8) + 128 * 256;
        ycc[0] = Y;
        ycc[1] = Cb;
        ycc[2] = Cr;
    }

    /** RGB in the range [0,1] to YUV in the range Y=[0,1], U=[-0.5,0.5],
     * V=[-0.5,0.5]
     */
    public static void RGBtoYUV(float[] rgb, float[] yuv) {
        float R = max(0f, min(1f, rgb[0]));
        float G = max(0f, min(1f, rgb[1]));
        float B = max(0f, min(1f, rgb[2]));
        float Y = 0.3f * R + 0.6f * G + 0.1f * B;
        yuv[0] = 0.299f * R + 0.587f * G + 0.114f * B;
        yuv[1] = -0.14713f * R - 0.28886f * G + 0.436f * B;
        yuv[2] = 0.615f * R - 0.51499f * G - 0.10001f * B;
    }

    /** YUV in the range Y=[0,1], U=[-0.5,0.5], V=[-0.5,0.5] 
     * to RGB in the range [0,1] */
    public static void YUVtoRGB(float[] yuv, float[] rgb) {
        float Y = max(0f, min(1f, yuv[0]));
        float U = max(-0.5f, min(0.5f, yuv[1]));
        float V = max(-0.5f, min(0.5f, yuv[2]));
        float R = 1 * Y + 0 * U + 1.13983f * V;
        float G = 1 * Y - 0.39465f * U - 0.58060f * V;
        float B = 1 * Y + 2.03211f * U + 0 * V;
        rgb[0]=min(1,max(0,R));
        rgb[1]=min(1,max(0,G));
        rgb[2]=min(1,max(0,B));
    }

    /** YCC 16-bit per channel to RGB 8-bit per channel. */
    public static void YCCtoRGB(int[] ycc, int[] rgb) {
        int Y = ycc[0];
        int Cb = ycc[1];
        int Cr = ycc[2];
        int U = (Cb - 128 * 256) * 2;
        int V = (Cr - 128 * 256) * 8 / 5;
        int R = min(255, max(0, (V + Y) / 256));
        int B = min(255, max(0, (U + Y) / 256));
        int G = min(255, max(0, (Y - 77 * R - 26 * B) / 153));
        rgb[0] = R;
        rgb[1] = G;
        rgb[2] = B;
    }

    /** YCC 16-bit per channel to RGB 8-bit per channel. */
    public static int YCCtoRGB(int[] ycc) {
        int Y = ycc[0];
        int Cb = ycc[1];
        int Cr = ycc[2];
        int U = (Cb - 128 * 256) * 2;
        int V = (Cr - 128 * 256) * 8 / 5;
        int R = min(255, max(0, (V + Y) / 256));
        int B = min(255, max(0, (U + Y) / 256));
        int G = min(255, max(0, (Y - 77 * R - 26 * B) / 153));
        return R << 16 | G << 8 | B;
    }

    /** RGB in the range [0,1] to YIQ in the range Y in [0,1],
     * I in [-0.5957,0.5957], Q in [-0.5226,0.5226].
     * <p>
     * http://en.wikipedia.org/wiki/YIQ
     */
    public static void RGBtoYIQ(float[] rgb, float[] yiq) {
        float R = max(0f, min(1f, rgb[0]));
        float G = max(0f, min(1f, rgb[1]));
        float B = max(0f, min(1f, rgb[2]));
        float Y = 0.299f * R + 0.587f * G + 0.114f * B;
        float I = 0.595716f * R + -0.274453f * G + -0.321263f * B;
        float Q = 0.211456f * R + -0.522591f * G + 0.311135f * B;
        yiq[0] = Y;
        yiq[1] = I;
        yiq[2] = Q;
    }

    /** YIQ in the range Y in [0,1], I in [-0.5957,0.5957], Q in [-0.5226,0.5226] 
     * to RGB in the range [0,1] 
     * <p>
     * http://en.wikipedia.org/wiki/YIQ
     */
    public static void YIQtoRGB(float[] yiq, float[] rgb) {
        float Y = max(0f, min(1f, yiq[0]));
        float I = max(-0.5957f, min(0.5957f, yiq[1]));
        float Q = max(-0.5226f, min(0.5226f, yiq[2]));
        float R = Y + 0.9563f * I + 0.6210f * Q;
        float G = Y + -0.2721f * I + -0.6474f * Q;
        float B = Y + -1.1070f * I + 1.7046f * Q;
        rgb[0] = max(0f, min(1f, R));
        rgb[1] = max(0f, min(1f, G));
        rgb[2] = max(0f, min(1f, B));
    }
}
