/*
 * Copyright (c) 2001, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#include "dither.h"

sgn_ordered_dither_array std_img_oda_red;
sgn_ordered_dither_array std_img_oda_green;
sgn_ordered_dither_array std_img_oda_blue;
int std_odas_computed = 0;

void initInverseGrayLut(int* prgb, int rgbsize, ColorData *cData) {
    int *inverse;
    int lastindex, lastgray, missing, i;

    if (!cData) {
        return;
    }

    inverse = calloc(256, sizeof(int));
    if (!inverse) {
        return;
    }
    cData->pGrayInverseLutData = inverse;

    for (i = 0; i < 256; i++) {
        inverse[i] = -1;
    }

    /* First, fill the gray values */
    for (i = 0; i < rgbsize; i++) {
        int r, g, b, rgb = prgb[i];
        if (rgb == 0x0) {
            /* ignore transparent black */
            continue;
        }
        r = (rgb >> 16) & 0xff;
        g = (rgb >> 8 ) & 0xff;
        b = rgb & 0xff;
        if (b == r && b == g) {
            inverse[b] = i;
        }
    }

    /* fill the missing gaps by taking the valid values
     * on either side and filling them halfway into the gap
     */
    lastindex = -1;
    lastgray = -1;
    missing = 0;
    for (i = 0; i < 256; i++) {
        if (inverse[i] < 0) {
            inverse[i] = lastgray;
            missing = 1;
        } else {
            lastgray = inverse[i];
            if (missing) {
                lastindex = lastindex < 0 ? 0 : (i+lastindex)/2;
                while (lastindex < i) {
                    inverse[lastindex++] = lastgray;
                }
            }
            lastindex = i;
            missing = 0;
        }
    }
}

void freeICMColorData(ColorData *pData) {
    if (CANFREE(pData)) {
        if (pData->img_clr_tbl) {
            free(pData->img_clr_tbl);
        }
        if (pData->pGrayInverseLutData) {
            free(pData->pGrayInverseLutData);
        }
        free(pData);
    }
}

/* REMIND: does not deal well with bifurcation which happens when two
 * palette entries map to the same cube vertex
 */

static int
recurseLevel(CubeStateInfo *priorState) {
    int i;
    CubeStateInfo currentState;
    memcpy(&currentState, priorState, sizeof(CubeStateInfo));


    currentState.rgb = (unsigned short *)malloc(6
                                                * sizeof(unsigned short)
                                                * priorState->activeEntries);
    if (currentState.rgb == NULL) {
        return 0;
    }

    currentState.indices = (unsigned char *)malloc(6
                                                * sizeof(unsigned char)
                                                * priorState->activeEntries);

    if (currentState.indices == NULL) {
        free(currentState.rgb);
        return 0;
    }

    currentState.depth++;
    if (currentState.depth > priorState->maxDepth) {
        priorState->maxDepth = currentState.depth;
    }
    currentState.activeEntries = 0;
    for (i=priorState->activeEntries - 1; i >= 0; i--) {
        unsigned short rgb = priorState->rgb[i];
        unsigned char  index = priorState->indices[i];
        ACTIVATE(rgb, 0x7c00, 0x0400, currentState, index);
        ACTIVATE(rgb, 0x03e0, 0x0020, currentState, index);
        ACTIVATE(rgb, 0x001f, 0x0001, currentState, index);
    }
    if (currentState.activeEntries) {
        if (!recurseLevel(&currentState)) {
            free(currentState.rgb);
            free(currentState.indices);
            return 0;
        }
    }
    if (currentState.maxDepth > priorState->maxDepth) {
        priorState->maxDepth = currentState.maxDepth;
    }

    free(currentState.rgb);
    free(currentState.indices);
    return  1;
}

/*
 * REMIND: take core inversedLUT calculation to the shared tree and
 * recode the functions (Win32)awt_Image:initCubemap(),
 * (Win32)awt_Image:make_cubemap(), (Win32)AwtToolkit::GenerateInverseLUT(),
 * (Solaris)color:initCubemap() to call the shared codes.
 */
unsigned char*
initCubemap(int* cmap,
            int  cmap_len,
            int  cube_dim) {
    int i;
    CubeStateInfo currentState;
    int cubesize = cube_dim * cube_dim * cube_dim;
    unsigned char *useFlags;
    unsigned char *newILut = (unsigned char*)malloc(cubesize);
    int cmap_mid = (cmap_len >> 1) + (cmap_len & 0x1);
    if (newILut) {

      useFlags = (unsigned char *)calloc(cubesize, 1);

      if (useFlags == 0) {
          free(newILut);
#ifdef DEBUG
        fprintf(stderr, "Out of memory in color:initCubemap()1\n");
#endif
          return NULL;
      }

        currentState.depth          = 0;
        currentState.maxDepth       = 0;
        currentState.usedFlags      = useFlags;
        currentState.activeEntries  = 0;
        currentState.iLUT           = newILut;

        currentState.rgb = (unsigned short *)
                                malloc(cmap_len * sizeof(unsigned short));
        if (currentState.rgb == NULL) {
            free(newILut);
            free(useFlags);
#ifdef DEBUG
        fprintf(stderr, "Out of memory in color:initCubemap()2\n");
#endif
            return NULL;
        }

        currentState.indices = (unsigned char *)
                                malloc(cmap_len * sizeof(unsigned char));
        if (currentState.indices == NULL) {
            free(currentState.rgb);
            free(newILut);
            free(useFlags);
#ifdef DEBUG
        fprintf(stderr, "Out of memory in color:initCubemap()3\n");
#endif
            return NULL;
        }

        for (i = 0; i < cmap_mid; i++) {
            unsigned short rgb;
            int pixel = cmap[i];
            rgb = (pixel & 0x00f80000) >> 9;
            rgb |= (pixel & 0x0000f800) >> 6;
            rgb |=  (pixel & 0xf8) >> 3;
            INSERTNEW(currentState, rgb, i);
            pixel = cmap[cmap_len - i - 1];
            rgb = (pixel & 0x00f80000) >> 9;
            rgb |= (pixel & 0x0000f800) >> 6;
            rgb |=  (pixel & 0xf8) >> 3;
            INSERTNEW(currentState, rgb, cmap_len - i - 1);
        }

        if (!recurseLevel(&currentState)) {
            free(newILut);
            free(useFlags);
            free(currentState.rgb);
            free(currentState.indices);
#ifdef DEBUG
        fprintf(stderr, "Out of memory in color:initCubemap()4\n");
#endif
            return NULL;
        }

        free(useFlags);
        free(currentState.rgb);
        free(currentState.indices);

        return newILut;
    }

#ifdef DEBUG
        fprintf(stderr, "Out of memory in color:initCubemap()5\n");
#endif
    return NULL;
}

void
initDitherTables(ColorData* cData) {


    if(std_odas_computed) {
        cData->img_oda_red   = &(std_img_oda_red[0][0]);
        cData->img_oda_green = &(std_img_oda_green[0][0]);
        cData->img_oda_blue  = &(std_img_oda_blue[0][0]);
    } else {
        cData->img_oda_red   = &(std_img_oda_red[0][0]);
        cData->img_oda_green = &(std_img_oda_green[0][0]);
        cData->img_oda_blue  = &(std_img_oda_blue[0][0]);
        make_dither_arrays(256, cData);
        std_odas_computed = 1;
    }

}

void make_dither_arrays(int cmapsize, ColorData *cData) {
    int i, j, k;

    /*
     * Initialize the per-component ordered dithering arrays
     * Choose a size based on how far between elements in the
     * virtual cube.  Assume the cube has cuberoot(cmapsize)
     * elements per axis and those elements are distributed
     * over 256 colors.
     * The calculation should really divide by (#comp/axis - 1)
     * since the first and last elements are at the extremes of
     * the 256 levels, but in a practical sense this formula
     * produces a smaller error array which results in smoother
     * images that have slightly less color fidelity but much
     * less dithering noise, especially for grayscale images.
     */
    i = (int) (256 / pow(cmapsize, 1.0/3.0));
    make_sgn_ordered_dither_array(cData->img_oda_red, -i / 2, i / 2);
    make_sgn_ordered_dither_array(cData->img_oda_green, -i / 2, i / 2);
    make_sgn_ordered_dither_array(cData->img_oda_blue, -i / 2, i / 2);

    /*
     * Flip green horizontally and blue vertically so that
     * the errors don't line up in the 3 primary components.
     */
    for (i = 0; i < 8; i++) {
        for (j = 0; j < 4; j++) {
            k = cData->img_oda_green[(i<<3)+j];
            cData->img_oda_green[(i<<3)+j] = cData->img_oda_green[(i<<3)+7 - j];
            cData->img_oda_green[(i<<3) + 7 - j] = k;
            k = cData->img_oda_blue[(j<<3)+i];
            cData->img_oda_blue[(j<<3)+i] = cData->img_oda_blue[((7 - j)<<3)+i];
            cData->img_oda_blue[((7 - j)<<3) + i] = k;
        }
    }
}
