/* ****************************************************************
 *                           clr_clip.c
 * ****************************************************************
 *  MODULE PURPOSE:
 *      This module contains routines for bringing colors outside
 *      the displayable gamut into the displayable gamut.
 *
 *  MODULE CONTENTS:
 *      CLR_clamp_rgb   - clamps rgb values to 0.0-1.0 range
 *      CLR_scale_rgb   - scales rgb values to 0.0-1.0 range
 *      CLR_clip_rgb    - clips rgb values to 0.0-1.0 range
 */
#include <stdio.h>
#include <math.h>
#include "clr.h"

/* define the max and min scaling values for equal intensity
 *  clipping.  These are defined to be slightly inside of the
 *  0 to 1 range to avoid numerical problems that occur when
 *  colors are on or very close to the clipping boundary.
 */
#define MAX_CLIP    0.9999  /* 1 - MAX_CLIP > 1 res step */
#define MIN_CLIP    0.0001  /* less than 1 res step */

/* ****************************************************************
 * CLR_clamp_rgb (rgb)
 *  CLR_RGB     rgb     (in)  - the input rgb
 *
 * Returns clamped color.  Values greater than 1 are  clamped to 1,
 *  values less than 0 are clamped to 0.
 */
CLR_RGB CLR_clamp_rgb(in_rgb)
CLR_RGB     in_rgb;
{   CLR_RGB     rgb;
    rgb = in_rgb;
    if (rgb.r < 0.0) rgb.r = 0.0;
    else if (rgb.r > 1.0) rgb.r = 1.0;
    if (rgb.g < 0.0) rgb.g = 0.0;
    else if (rgb.g > 1.0) rgb.g = 1.0;
    if (rgb.b < 0.0) rgb.b = 0.0;
    else if (rgb.b > 1.0) rgb.b = 1.0;
    return rgb;
}

/* ****************************************************************
 * CLR_scale_rgb (rgb)
 *  CLR_RGB     rgb     (in)  - the input rgb
 *
 * Returns scaled color.  Values less than 0 are clamped to zero.
 *  If any values are greater than 1, all color components are
 *  scaled so the offending value is equal to 1.
 */
CLR_RGB CLR_scale_rgb(in_rgb)
CLR_RGB     in_rgb;
{   double      scale=1.0;
    double      tmp_scale;
    CLR_RGB     rgb;
    rgb = in_rgb;
    if (rgb.r < 0.0) rgb.r = 0.0;
    else if ((rgb.r > 1.0) && ((tmp_scale = (1.0 / rgb.r))
	< scale)) scale = tmp_scale;
    if (rgb.g < 0.0) rgb.g = 0.0;
    else if ((rgb.g > 1.0) && ((tmp_scale = (1.0 / rgb.g))
	< scale)) scale = tmp_scale;
    if (rgb.b < 0.0) rgb.b = 0.0;
    else if ((rgb.b > 1.0) && ((tmp_scale = (1.0 / rgb.b))
	< scale)) scale = tmp_scale;
    if (scale < 1.0) {
	rgb.r *= scale;
	rgb.g *= scale;
	rgb.b *= scale;
    }
    return rgb;
}

/* ****************************************************************
 * CLR_RGB CLR_clip_rgb(in_rgb)
 *  CLR_RGB     in_rgb  (in) - the input rgb
 *
 * Returns the clipped color - clipping is by desaturating the
 *  color by shifting it towards the neutral axis in a plane
 *  perpendicular to the neutral axis.  The neutral axis is taken
 *  as the vector from the monitor black to the monitor white.
 */
CLR_RGB CLR_clip_rgb(in_rgb)
CLR_RGB     in_rgb;
{   CLR_RGB     diff, out_rgb;
    double      axis_rgb, diff_mult, tmp_mult;

    /* check to see if clipping is required
     */
    if ((in_rgb.r < 0.0) || (in_rgb.r > 1.0) ||
	(in_rgb.g < 0.0) || (in_rgb.g > 1.0) ||
	(in_rgb.b < 0.0) || (in_rgb.b > 1.0)) {
	/* clipping is required, determine the distance from
	 *  the origin to the equal intensity plane containing
	 *  the color.  The distance is normalized the origin
	 *  at color (0,0,0) and a distance of 1 at (1,1,1)
	 */
	axis_rgb = (in_rgb.r + in_rgb.g + in_rgb.b) * .333333;
	/* check for the intensity plane of the color being
	 *  outside the displayable range -- if it is, set
	 *  color to either black or white.
	 */
	if (axis_rgb <= MIN_CLIP)
	    /* this is not a visible color -- it should not
	     *  have been computed
	     */
	    out_rgb.r = out_rgb.g = out_rgb.b = 0.0;
	else if (axis_rgb >= MAX_CLIP)
	    /* This is way beyond white in intensity, set it
	     *  to the white point.
	     */
	    out_rgb.r = out_rgb.g = out_rgb.b = 1.0;
	else {
	    /* the intensity plane is within the displayable
	     *  range.  Compute the vector from the neutral
	     *  axis to the color on it's intensity plane.
	     *  The intersection of the neutral axis and the
	     *  intensity plane is at r=g=b=axis_rgb.
	     */
	    diff.r = in_rgb.r - axis_rgb;
	    diff.g = in_rgb.g - axis_rgb;
	    diff.b = in_rgb.b - axis_rgb;
	    /* determine the relative length of the vector
	     *  to the edge of the displayable color gamut.
	     */
	    diff_mult = 1.0;
	    if (in_rgb.r > 1.0) {
		if ((tmp_mult = (MAX_CLIP - axis_rgb) / diff.r)
		    < diff_mult) diff_mult = tmp_mult;
	    } else if (in_rgb.r < 0.0) {
		if ((tmp_mult = (MIN_CLIP - axis_rgb) / diff.r)
		    < diff_mult) diff_mult = tmp_mult;
	    }

	    if (in_rgb.g > 1.0) {
		if ((tmp_mult = (MAX_CLIP - axis_rgb) / diff.g)
		    < diff_mult) diff_mult = tmp_mult;
	    } else if (in_rgb.g < 0.0) {
		if ((tmp_mult = (MIN_CLIP - axis_rgb) / diff.g)
		    < diff_mult) diff_mult = tmp_mult;
	    }

	    if (in_rgb.b > 1.0) {
		if ((tmp_mult = (MAX_CLIP - axis_rgb) / diff.b)
		    < diff_mult) diff_mult = tmp_mult;
	    } else if (in_rgb.b < 0.0) {
		if ((tmp_mult = (MIN_CLIP - axis_rgb) / diff.b)
		    < diff_mult) diff_mult = tmp_mult;
	    }

	    /* determine the location of the color at the
	     *  edge of the displayable color gamut.
	     */
	    out_rgb.r = axis_rgb + (diff.r * diff_mult);
	    out_rgb.g = axis_rgb + (diff.g * diff_mult);
	    out_rgb.b = axis_rgb + (diff.b * diff_mult);
	}
	return out_rgb;
    }
    else
	return in_rgb;
}
/* ************************************************************* */
