/*
 *
 * $Header: /usr/u/wjr/src/support/RCS/imconv.c,v 1.6 1992/02/26 04:10:00 wjr Exp $
 *
 * Copyright (c) 1990, 1991 Cornell University.  All Rights Reserved.
 *
 * Copyright (c) 1991 Xerox Corporation.  All Rights Reserved.
 *
 * Use, reproduction and preparation of derivative works of this software is
 * permitted.  Any copy of this software or of any derivative work must
 * include both the above copyright notices of Cornell University and Xerox
 * Corporation and this paragraph.  This software is made available AS IS, and
 * XEROX CORPORATION DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
 * WITHOUT LIMITATION THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE, AND NOTWITHSTANDING ANY OTHER PROVISION CONTAINED
 * HEREIN, ANY LIABILITY FOR DAMAGES RESULTING FROM THE SOFTWARE OR ITS USE IS
 * EXPRESSLY DISCLAIMED, WHETHER ARISING IN CONTRACT, TORT (INCLUDING
 * NEGLIGENCE) OR STRICT LIABILITY, EVEN IF XEROX CORPORATION IS ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGES.
 */

static char rcsid[] = "@(#)$Header: /usr/u/wjr/src/support/RCS/imconv.c,v 1.6 1992/02/26 04:10:00 wjr Exp $";

#include "misc.h"
#include "image.h"
#include "panic.h"

/*
 *	imconv.c - Image conversion.
 *
 * This file contains routines to convert between various forms of Image.
 *
 * Exports:
 *	GrayImage imcLtoG(LongImage lim, long low, long high) - convert the
 *		given LongImage to a GrayImage, with low mapping to zero and
 *		high mapping to 255; values in between scaled linearly.
 *		If low >= high, all values are set to zero. The output
 *		image has the same size and origin.
 *
 *	GrayImage imcLtoGAuto(LongImage lim) - convert the given LongImage
 *		to a GrayImage with the largest value mapping to 255 and
 *		the smallest to 0.
 *
 *	GrayImage imcFtoG(FloatImage fim, double low, double high) -
 *		convert the given FloatImage to a GrayImage with low
 *		mapping to 0 and high mapping to 255.
 *
 *	GrayImage imcFtoGAuto(FloatImage fim) - convert the given FloatImage
 *		to a GrayImage with the largest value mapping to 255 and
 *		the smallest to 0.
 *
 *	GrayImage imcDtoG(DoubleImage fim, double low, double high) -
 *		convert the given DoubleImage to a GrayImage with low
 *		mapping to 0 and high mapping to 255.
 *
 *	GrayImage imcDtoGAuto(DoubleImage fim) - convert the given DoubleImage
 *		to a GrayImage with the largest value mapping to 255 and
 *		the smallest to 0.
 *
 *	GrayImage imcRGBtoG(RGBImage rgbim) - convert the given RGB
 *		image to a GrayImage by computing an appropriate weighted
 *		average of the R, G and B components.
 *
 *	DoubleImage imcGtoD(GrayImage gim) - convert the given GrayImage
 *		to a DoubleImage by simply copying the values across.
 *
 * All routines here return NULL (cast to the appropriate type) on
 *	failure.
 *
 */

GrayImage 
imcLtoG(LongImage lim, long low, long high)
{
    int x, y;
    GrayImage gim;
    int top;
    int right;
    double conv;

    assert(lim != (LongImage)NULL);

    gim = imNewOffset(IMAGE_GRAY, imGetWidth(lim), imGetHeight(lim),
		      imGetXBase(lim), imGetYBase(lim));
    if (gim == (GrayImage)NULL) {
	return((GrayImage)NULL);
	}

    if (high <= low) {
	/* All done: it's already all zero */
	return(gim);
	}

    top = imGetHeight(lim) + imGetYBase(lim);
    right = imGetWidth(lim) + imGetXBase(lim);
    for (y = imGetYBase(lim); y < top; y++) {
	for (x = imGetXBase(lim); x < right; x++) {
	    conv = ((imRef(lim, x, y) - low) * COLRNG) / (high - low);

	    if (conv < 0) {
		conv = 0;
		}
	    else if (conv >= COLRNG) {
		conv = COLRNG - 1;
		}
	    imRef(gim, x, y) = conv;
	    }
	}

    return(gim);
    }

GrayImage
imcLtoGAuto(LongImage lim)
{
    int x, y;
    long low;
    long high;
    int top;
    int right;

    assert(lim != (LongImage)NULL);

    if ((imGetWidth(lim) != 0) && (imGetHeight(lim) != 0)) {
	low = high = imRef(lim, imGetXBase(lim), imGetYBase(lim));
	}
    else {
	return(imcLtoG(lim, 0, 0));
	}
    top = imGetHeight(lim) + imGetYBase(lim);
    right = imGetWidth(lim) + imGetXBase(lim);
    for (y = imGetYBase(lim); y < top; y++) {
	for (x = imGetXBase(lim); x < right; x++) {
	    low = MIN(low, imRef(lim, x, y));
	    high = MAX(high, imRef(lim, x, y));
	    }
	}

    return(imcLtoG(lim, low, high));
    }

/*
 * This routine converts a FloatImage into a GrayImage. It scales the
 * float values to the range (0..COLRNG-1), with values at low or below
 * going to zero, values at high or above going to COLRNG-1, and values
 * in between to the appropriate linearly interpolated value.
 *
 * It returns the newly created GrayImage on success, and (GrayImage)NULL
 * on failure.
 */
GrayImage
imcFtoG(FloatImage fim, double low, double high)
{
    int x, y;
    GrayImage gim;
    int top;
    int right;
    int conv;

    assert(fim != (FloatImage)NULL);

    /* Allocate the new image */
    gim = imNewOffset(IMAGE_GRAY, imGetWidth(fim), imGetHeight(fim),
		      imGetXBase(fim), imGetYBase(fim));
    if (gim == (GrayImage)NULL) {
	return((GrayImage)NULL);
	}

    if (low >= high) {
	/*
	 * Nothing to be done - there's nothing there to convert
	 * (or rather, it's all the same, and will be squashed to
	 * zero, which is what is in the image now, as it's a new image).
	 */
	return(gim);
	}
	
    /* Convert the image */
    top = imGetHeight(fim) + imGetYBase(fim);
    right = imGetWidth(fim) + imGetXBase(fim);
    for (y = imGetYBase(fim); y < top; y++) {
	for (x = imGetXBase(fim); x < right; x++) {
	    conv = (imRef(fim, x, y) - low) / (high - low) * COLRNG;

	    /* Check to see if it went out of range */
	    if (conv < 0) {
		conv = 0;
		}
	    else if (conv >= COLRNG) {
		conv = COLRNG - 1;
		}
	    imRef(gim, x, y) = conv;
	    }
	}

    return(gim);
    }

/*
 * This routine converts a FloatImage into a GrayImage, with the lowest
 * value in the FloatImage becoming 0, and the highest value becoming
 * COLRNG-1.
 *
 * It returns the new GrayImage on success, and (GrayImage)NULL on failure.
 */
GrayImage
imcFtoGAuto(FloatImage fim)
{
    double low, high;
    int x, y;
    int top;
    int right;

    assert(fim != (FloatImage)NULL);

    if ((imGetWidth(fim) != 0) && (imGetHeight(fim) != 0)) {
	low = high = imRef(fim, imGetXBase(fim), imGetYBase(fim));
	}
    else {
	return(imcFtoG(fim, 0., 0.));
	}

    top = imGetHeight(fim) + imGetYBase(fim);
    right = imGetWidth(fim) + imGetXBase(fim);
    for (y = imGetYBase(fim); y < top; y++) {
	for (x = imGetXBase(fim); x < right; x++) {
	    low = MIN(low, imRef(fim, x, y));
	    high = MAX(high, imRef(fim, x, y));
	    }
	}

    /* Do the scaling itself */
    return(imcFtoG(fim, low, high));
    }

/*
 * This routine converts a DoubleImage into a GrayImage. It scales the
 * double values to the range (0..COLRNG-1), with values at low or below
 * going to zero, values at high or above going to COLRNG-1, and values
 * in between to the appropriate linearly interpolated value.
 *
 * It returns the newly created GrayImage on success, and (GrayImage)NULL
 * on failure.
 */
GrayImage
imcDtoG(DoubleImage dim, double low, double high)
{
    int x, y;
    GrayImage gim;
    int top;
    int right;
    int conv;

    assert(dim != (DoubleImage)NULL);

    /* Allocate the new image */
    gim = imNewOffset(IMAGE_GRAY, imGetWidth(dim), imGetHeight(dim),
		      imGetXBase(dim), imGetYBase(dim));
    if (gim == (GrayImage)NULL) {
	return((GrayImage)NULL);
	}

    if (low >= high) {
	/*
	 * Nothing to be done - there's nothing there to convert
	 * (or rather, it's all the same, and will be squashed to
	 * zero, which is what is in the image now, as it's a new image).
	 */
	return(gim);
	}
	
    /* Convert the image */
    top = imGetHeight(dim) + imGetYBase(dim);
    right = imGetWidth(dim) + imGetXBase(dim);
    for (y = imGetYBase(dim); y < top; y++) {
	for (x = imGetXBase(dim); x < right; x++) {
	    conv = (imRef(dim, x, y) - low) / (high - low) * COLRNG;

	    /* Check to see if it went out of range */
	    if (conv < 0) {
		conv = 0;
		}
	    else if (conv >= COLRNG) {
		conv = COLRNG - 1;
		}
	    imRef(gim, x, y) = conv;
	    }
	}

    return(gim);
    }

/*
 * This routine converts a DoubleImage into a GrayImage, with the lowest
 * value in the DoubleImage becoming 0, and the highest value becoming
 * COLRNG-1.
 *
 * It returns the new GrayImage on success, and (GrayImage)NULL on failure.
 */
GrayImage
imcDtoGAuto(DoubleImage dim)
{
    double low, high;
    int x, y;
    int top;
    int right;

    assert(dim != (DoubleImage)NULL);

    if ((imGetWidth(dim) != 0) && (imGetHeight(dim) != 0)) {
	low = high = imRef(dim, imGetXBase(dim), imGetYBase(dim));
	}
    else {
	return(imcDtoG(dim, 0., 0.));
	}

    top = imGetHeight(dim) + imGetYBase(dim);
    right = imGetWidth(dim) + imGetXBase(dim);
    for (y = imGetYBase(dim); y < top; y++) {
	for (x = imGetXBase(dim); x < right; x++) {
	    low = MIN(low, imRef(dim, x, y));
	    high = MAX(high, imRef(dim, x, y));
	    }
	}

    /* Do the scaling itself */
    return(imcDtoG(dim, low, high));
    }

/*
 * These weights are magic numbers which apparently are appropriate
 * for converting RGB values into a single gray-level value.
 */
#define	RED_WEIGHT	(0.299)
#define GREEN_WEIGHT	(0.587)
#define BLUE_WEIGHT	(0.114)

/*
 * This routine converts an RGB image into a GrayImage. It does this
 * by computing, for each pixel, a weighted average of the R, G, and B
 * components of the input image.
 *
 * It returns the new GrayImage on success, and (GrayImage)NULL on
 * failure.
 */
GrayImage
imcRGBtoG(RGBImage rgbim)
{
    GrayImage gim;
    int x, y;
    int top;
    int right;

    assert(rgbim != (RGBImage)NULL);

    /* Allocate the new image */
    gim = imNewOffset(IMAGE_GRAY, imGetWidth(rgbim), imGetHeight(rgbim),
		      imGetXBase(rgbim), imGetYBase(rgbim));
    if (gim == (GrayImage)NULL) {
	return((GrayImage)NULL);
	}

    /* Compute the values */
    top = imGetHeight(rgbim) + imGetYBase(rgbim);
    right = imGetWidth(rgbim) + imGetXBase(rgbim);
    for (y = imGetYBase(rgbim); y < top; y++) {
	for (x = imGetXBase(rgbim); x < right; x++) {
	    imRef(gim, x, y) = imRef(rgbim, x, y).r * RED_WEIGHT +
		               imRef(rgbim, x, y).g * GREEN_WEIGHT +
			       imRef(rgbim, x, y).b * BLUE_WEIGHT + 0.5;
	    }
	}

    /* Return the new image */
    return(gim);
    }

/*
 * This routine converts a GrayImage to a DoubleImage by simply copying
 * the values across.
 *
 * It returns the new DoubleImage on success, and (DoubleImage)NULL on
 * failure.
 */
DoubleImage
imcGtoD(GrayImage gim)
{
    DoubleImage dim;
    int x, y;
    int top;
    int right;

    assert(gim != (GrayImage)NULL);

    /* Allocate the new image */
    dim = (DoubleImage)imNewOffset(IMAGE_DOUBLE,
				   imGetWidth(gim), imGetHeight(gim),
				   imGetXBase(gim), imGetYBase(gim));
    if (dim == (DoubleImage)NULL) {
	return((DoubleImage)NULL);
	}

    /* Compute the values */
    top = imGetHeight(gim) + imGetYBase(gim);
    right = imGetWidth(gim) + imGetXBase(gim);
    for (y = imGetYBase(gim); y < top; y++) {
	for (x = imGetXBase(gim); x < right; x++) {
	    imRef(dim, x, y) = imRef(gim, x, y);
	    }
	}

    /* Return the new image */
    return(dim);
    }
