/*
 * The Python Imaging Library
 * $Id$
 *
 * point (pixel) translation
 *
 * history:
 * 1995-11-27 fl   Created
 * 1996-03-31 fl   Fixed colour support
 * 1996-08-13 fl   Support 8-bit to "1" thresholding
 * 1997-05-31 fl   Added floating point transform
 * 1998-07-02 fl   Added integer point transform
 * 1998-07-17 fl   Support L to anything lookup
 * 2004-12-18 fl   Refactored; added I to L lookup
 *
 * Copyright (c) 1997-2004 by Secret Labs AB.
 * Copyright (c) 1995-2004 by Fredrik Lundh.
 *
 * See the README file for information on usage and redistribution.
 */


#include "Imaging.h"

typedef struct {
    const void* table;
} im_point_context;

static void
im_point_8_8(Imaging imOut, Imaging imIn, im_point_context* context)
{
    int x, y;
    /* 8-bit source, 8-bit destination */
    UINT8* table = (UINT8*) context->table;
    for (y = 0; y < imIn->ysize; y++) {
        UINT8* in = imIn->image8[y];
        UINT8* out = imOut->image8[y];
        for (x = 0; x < imIn->xsize; x++)
            out[x] = table[in[x]];
    }
}

static void
im_point_2x8_2x8(Imaging imOut, Imaging imIn, im_point_context* context)
{
    int x, y;
    /* 2x8-bit source, 2x8-bit destination */
    UINT8* table = (UINT8*) context->table;
    for (y = 0; y < imIn->ysize; y++) {
        UINT8* in = (UINT8*) imIn->image[y];
        UINT8* out = (UINT8*) imOut->image[y];
        for (x = 0; x < imIn->xsize; x++) {
            out[0] = table[in[0]];
            out[3] = table[in[3]+256];
            in += 4; out += 4;
        }
    }
}

static void
im_point_3x8_3x8(Imaging imOut, Imaging imIn, im_point_context* context)
{
    int x, y;
    /* 3x8-bit source, 3x8-bit destination */
    UINT8* table = (UINT8*) context->table;
    for (y = 0; y < imIn->ysize; y++) {
        UINT8* in = (UINT8*) imIn->image[y];
        UINT8* out = (UINT8*) imOut->image[y];
        for (x = 0; x < imIn->xsize; x++) {
            out[0] = table[in[0]];
            out[1] = table[in[1]+256];
            out[2] = table[in[2]+512];
            in += 4; out += 4;
        }
    }
}

static void
im_point_4x8_4x8(Imaging imOut, Imaging imIn, im_point_context* context)
{
    int x, y;
    /* 4x8-bit source, 4x8-bit destination */
    UINT8* table = (UINT8*) context->table;
    for (y = 0; y < imIn->ysize; y++) {
        UINT8* in = (UINT8*) imIn->image[y];
        UINT8* out = (UINT8*) imOut->image[y];
        for (x = 0; x < imIn->xsize; x++) {
            out[0] = table[in[0]];
            out[1] = table[in[1]+256];
            out[2] = table[in[2]+512];
            out[3] = table[in[3]+768];
            in += 4; out += 4;
        }
    }
}

static void
im_point_8_32(Imaging imOut, Imaging imIn, im_point_context* context)
{
    int x, y;
    /* 8-bit source, 32-bit destination */
    char* table = (char*) context->table;
    for (y = 0; y < imIn->ysize; y++) {
        UINT8* in = imIn->image8[y];
        INT32* out = imOut->image32[y];
        for (x = 0; x < imIn->xsize; x++)
            memcpy(out + x, table + in[x] * sizeof(INT32), sizeof(INT32));
    }
}

static void
im_point_32_8(Imaging imOut, Imaging imIn, im_point_context* context)
{
    int x, y;
    /* 32-bit source, 8-bit destination */
    UINT8* table = (UINT8*) context->table;
    for (y = 0; y < imIn->ysize; y++) {
        INT32* in = imIn->image32[y];
        UINT8* out = imOut->image8[y];
        for (x = 0; x < imIn->xsize; x++) {
            int v = in[x];
            if (v < 0)
                v = 0;
            else if (v > 65535)
                v = 65535;
            out[x] = table[v];
        }
    }
}

Imaging
ImagingPoint(Imaging imIn, const char* mode, const void* table)
{
    /* lookup table transform */

    ImagingSectionCookie cookie;
    Imaging imOut;
    im_point_context context;
    void (*point)(Imaging imIn, Imaging imOut, im_point_context* context);

    if (!imIn)
	return (Imaging) ImagingError_ModeError();

    if (!mode)
        mode = imIn->mode;

    if (imIn->type != IMAGING_TYPE_UINT8) {
        if (imIn->type != IMAGING_TYPE_INT32 || strcmp(mode, "L") != 0)
            goto mode_mismatch;
    } else if (!imIn->image8 && strcmp(imIn->mode, mode) != 0)
        goto mode_mismatch;

    imOut = ImagingNew(mode, imIn->xsize, imIn->ysize);
    if (!imOut)
	return NULL;

    /* find appropriate handler */
    if (imIn->type == IMAGING_TYPE_UINT8) {
        if (imIn->bands == imOut->bands && imIn->type == imOut->type) {
            switch (imIn->bands) {
            case 1:
                point = im_point_8_8;
                break;
            case 2:
                point = im_point_2x8_2x8;
                break;
            case 3:
                point = im_point_3x8_3x8;
                break;
            case 4:
                point = im_point_4x8_4x8;
                break;
            default:
                /* this cannot really happen */
                point = im_point_8_8;
                break;
            }
        } else
            point = im_point_8_32;
    } else
        point = im_point_32_8;

    ImagingCopyPalette(imOut, imIn);

    ImagingSectionEnter(&cookie);

    context.table = table;
    point(imOut, imIn, &context);

    ImagingSectionLeave(&cookie);

    return imOut;

  mode_mismatch:
    return (Imaging) ImagingError_ValueError(
        "point operation not supported for this mode"
        );
}


Imaging
ImagingPointTransform(Imaging imIn, double scale, double offset)
{
    /* scale/offset transform */

    ImagingSectionCookie cookie;
    Imaging imOut;
    int x, y;

    if (!imIn || (strcmp(imIn->mode, "I") != 0 &&
                  strcmp(imIn->mode, "I;16") != 0 &&
                  strcmp(imIn->mode, "F") != 0))
	return (Imaging) ImagingError_ModeError();

    imOut = ImagingNew(imIn->mode, imIn->xsize, imIn->ysize);
    if (!imOut)
	return NULL;

    switch (imIn->type) {
    case IMAGING_TYPE_INT32:
        ImagingSectionEnter(&cookie);
        for (y = 0; y < imIn->ysize; y++) {
            INT32* in  = imIn->image32[y];
            INT32* out = imOut->image32[y];
            /* FIXME: add clipping? */
            for (x = 0; x < imIn->xsize; x++)
                out[x] = in[x] * scale + offset;
        }
        ImagingSectionLeave(&cookie);
        break;
    case IMAGING_TYPE_FLOAT32:
        ImagingSectionEnter(&cookie);
        for (y = 0; y < imIn->ysize; y++) {
            FLOAT32* in  = (FLOAT32*) imIn->image32[y];
            FLOAT32* out = (FLOAT32*) imOut->image32[y];
            for (x = 0; x < imIn->xsize; x++)
                out[x] = in[x] * scale + offset;
        }
        ImagingSectionLeave(&cookie);
        break;
    case IMAGING_TYPE_SPECIAL:
        if (strcmp(imIn->mode,"I;16") == 0) {
            ImagingSectionEnter(&cookie);
            for (y = 0; y < imIn->ysize; y++) {
                char* in  = (char*)imIn->image[y];
                char* out = (char*)imOut->image[y];
                /* FIXME: add clipping? */
                for (x = 0; x < imIn->xsize; x++) {
                    UINT16 v;
                    memcpy(&v, in + x * sizeof(v), sizeof(v));
                    v = v * scale + offset;
                    memcpy(out + x * sizeof(UINT16), &v, sizeof(v));
                }
            }
            ImagingSectionLeave(&cookie);
            break;
	}
        /* FALL THROUGH */
    default:
        ImagingDelete(imOut);
        return (Imaging) ImagingError_ValueError("internal error");
    }

    return imOut;
}
