/**************************************************************************
 *{@C
 *      Copyright:      1988-2020 Paul Obermeier (obermeier@poSoft.de)
 *
 *      Module:         ImageProcessing
 *      Filename:       IP_Io.c
 *
 *      Author:         Paul Obermeier
 *
 *	Description:	Functions to create/destroy and read/write images.
 *
 *      Additional documentation:
 *                      None.
 *
 *	Exported functions:
 *			IP_ImageNew
 *			IP_ImageDelete
 *			IP_Write
 *			IP_Read
 *			IP_WriteSimple
 *			IP_NewImageFromFile
 *
 **************************************************************************/

#include <stdio.h>
#include <string.h>

#include "UT_Compat.h"
#include "UT_Error.h"
#include "UT_Memory.h"

#include "FF_Image.h"

#include "IP_Image.h"
#include "IP_ImagePrivate.h"

/***************************************************************************
 *[@e
 *      Name:		IP_ImageNew
 *
 *      Usage:		Create a new image.
 *
 *      Synopsis:	IP_ImageId IP_ImageNew
 *                              (Int32 width, Int32 height, Float32 aspect)
 *
 *      Description:	Memory for an image with "width" by "height" pixels,
 *                      and with an aspect ratio of "aspect", is allocated.
 *                      The current pixel data format defines the format
 *                      for the channels in the new image.
 *                      The current color correction parameters define the
 *                      color correction parameters for the new image.
 *                      The colors of the pixels in the image are undefined.
 *
 *      Return value:   An identifier for the new image if successful,
 *                      else NULL.
 *
 *      See also:       IP_ImageDelete
 *
 ***************************************************************************/

IP_ImageId IP_ImageNew (Int32 width, Int32 height, Float32 aspect)
{
    UT_MemState	memstate;
    IP_ImageStruct	*img;
    Int32		i;

    if (width < 1) {
	UT_ErrSetNum (UT_ErrParamRange, str_inotless, str_image_width, 1);
	return NULL;
    }
    if (height < 1) {
	UT_ErrSetNum (UT_ErrParamRange, str_inotless, str_image_height, 1);
	return NULL;
    }
    if (aspect < IP_MinAllowedAspect || aspect > IP_MaxAllowedAspect) {
	UT_ErrSetNum (UT_ErrParamRange, str_range_inclusive,
		     str_image_aspect, IP_MinAllowedAspect, IP_MaxAllowedAspect);
	return NULL;
    }
    memstate = UT_MemRemember ();
    if (!(img = UT_MemTemp (IP_ImageStruct))) {
	UT_MemRestore (memstate);
	return NULL;
    }
    img->pimg.desc.width = width;
    img->pimg.desc.height = height;
    img->pimg.desc.aspect = aspect;
    img->pimg.desc.red = IP_StateHueRed;
    img->pimg.desc.green = IP_StateHueGreen;
    img->pimg.desc.blue = IP_StateHueBlue;
    img->pimg.desc.white = IP_StateHueWhite;
    img->pimg.desc.gamma = IP_StateGamma;
    for (i = 0; i < FF_NumImgChanTypes; ++i)
	img->pimg.channel[i] = IP_StateFormat[i];
    img->pimg.firstscan = 0;
    img->pimg.lastscan = height - 1;
    if (!FF_ImgNew (&img->pimg)) {
	UT_MemRestore (memstate);
	return NULL;
    }
    UT_MemSave (memstate);
    return img;
}

/***************************************************************************
 *[@e
 *      Name:		IP_ImageDelete
 *
 *      Usage:		Delete an image.
 *
 *      Synopsis:	void IP_ImageDelete (IP_ImageId img)
 *
 *      Description:	Free all memory used for image "img".
 *
 *      Return value:	None.
 *
 *      See also:	IP_ImageNew
 *
 ***************************************************************************/

void IP_ImageDelete (IP_ImageId img)
{
    FF_ImgDelete (&img->pimg);
    UT_MemFree (img);
    return;
}

/***************************************************************************
 *[@e
 *	Name:		IP_Write
 *
 *	Usage:		Write an image to a file.
 *
 *	Synopsis:	UT_Bool IP_Write
 *				(IP_ImageId img,
 *				const char fname[],
 *				const char fmt[],
 *				const char vsn[])
 *
 *	Description:	Image "img" is written to pixel file "fname" using
 *			pixel file format "fmt" and file format version "vsn".
 *			The current pixel data format and the current
 *			compression method select which channels will be
 *			written to the file and in which format, and how the
 *			pixel data will be compressed.
 *
 *	Return value:	UT_True if successful, else UT_False.
 *
 *	See also:	IP_WriteSimple
 *			IP_Read
 *
 ***************************************************************************/

UT_Bool IP_Write (IP_ImageId img,
		const char fname[],
		const char fmt[],
		const char vsn[])
{
    FF_ImgFileDesc	pxf;
    Int32		i, istart, iend, istep;

    pxf.desc = img->pimg.desc;
    for (i = 0; i < FF_NumImgChanTypes; ++i) {
	pxf.channel[i] = IP_StateFormat[i];
	pxf.compression[i] = IP_StateCompression[i];
    }
    strncpy (pxf.format, fmt, FF_ImgMaxNameLen);
    pxf.format[FF_ImgMaxNameLen - 1] = '\0';
    strncpy (pxf.version, vsn, FF_ImgMaxNameLen);
    pxf.version[FF_ImgMaxNameLen - 1] = '\0';
    if (!FF_ImgOpenWrite (fname, &pxf))
	return UT_False;
    if (!FF_ImgSetSrcImg (&pxf, &img->pimg, UT_False, UT_False)) {
	FF_ImgClose (&pxf);
	UT_ErrAppendStr (". ", str_cant_write, fname);
	return UT_False;
    }
    if (pxf.scanorder == FF_ImgOrderTypeBottomUp) {
	istart = 0;
	iend = pxf.desc.height - 1;
	istep = 1;
    } else {
	istart = pxf.desc.height - 1;
	iend = 0;
	istep = -1;
    }
    for (i = istart; i != iend + istep; i += istep) {
	if (!FF_ImgWriteRow (&pxf, i, 0)) {
	    FF_ImgClose (&pxf);
	    UT_ErrAppendStr (". ", str_cant_write, fname);
	    return UT_False;
	}
    }
    FF_ImgClose (&pxf);
    return UT_True;
}

/***************************************************************************
 *[@e
 *	Name:		IP_Read
 *
 *	Usage:		Read an image from a file.
 *
 *	Synopsis:	UT_Bool IP_Read
 *				(IP_ImageId img, const char fname[],
 *				UT_Bool stretch, UT_Bool gamma, UT_Bool color)
 *
 *	Description:	The pixel data stored in pixel file "fname" is are
 *			read into image "img".
 *
 *			If "stretch" is UT_True, the pixels in the file are
 *			stretched or squashed so that the width and height of
 *			the image in the file become equal to width and height
 *			of "img".
 *			If "stretch" is UT_False, the image in the file is clipped
 *			or padded with zeros in order to match the width and
 *			height of "img".
 *
 *			If the "gamma" flag is UT_True, the image is gamma-
 *			corrected while it is read from the file.
 *			If the "color" floag is UT_True, the FF_ImgChanTypeRed,
 *			FF_ImgChanTypeGreen and FF_ImgChanTypeBlue channels are 
 *			color corrected while they are read from the file.
 *
 *	Return value:	UT_True if successful, else UT_False.
 *
 *	See also:	IP_NewImageFromFile
 *			IP_Write
 *
 ***************************************************************************/

UT_Bool IP_Read (IP_ImageId img, const char fname[],
	        UT_Bool stretch, UT_Bool gamma, UT_Bool color)
{
    FF_ImgFileDesc pxf;
    Int32          i, istart, iend, istep;

    memset (&pxf, 0, sizeof (FF_ImgFileDesc));

    if (!FF_ImgOpenRead (fname, &pxf))
	return UT_False;
    if (!FF_ImgSetDestImg (&pxf, &img->pimg, stretch, gamma, color)) {
	FF_ImgClose (&pxf);
	UT_ErrAppendStr (". ", str_cant_read, fname);
	return UT_False;
    }
    if (pxf.scanorder == FF_ImgOrderTypeBottomUp) {
	istart = 0;
	if (stretch)
	    iend = img->pimg.desc.height - 1;
	else
	    iend = pxf.desc.height - 1;
	istep = 1;
    } else {
	iend = 0;
	if (stretch)
	    istart = img->pimg.desc.height - 1;
	else
	    istart = pxf.desc.height - 1;
	istep = -1;
    }
    for (i = istart; i != iend + istep; i += istep) {
	if (!FF_ImgReadRow (&pxf, i, 0)) {
	    FF_ImgClose (&pxf);
	    UT_ErrAppendStr (". ", str_cant_read, fname);
	    return UT_False;
	}
    }
    FF_ImgClose (&pxf);
    return UT_True;
}

/***************************************************************************
 *[@e
 *	Name:		IP_WriteSimple
 *
 *	Usage:		Write an image to a file - simple version.
 *
 *	Synopsis:	UT_Bool IP_WriteSimple
 *				(IP_ImageId img,
 *				const char fname[],
 *				const char fmt[],
 *				const char vsn[])
 *
 *	Description:	Image "img" is written to pixel file "fname", using
 *			file format "fmt" and format version "vsn".
 *			The pixel data format in the file will be the same as
 *			in "img" for all channels.
 *			The current compression method is applied to all
 *			channels.
 *
 *	Return value:	UT_True if successful, else UT_False.
 *
 *	See also:	IP_NewImageFromFile
 *			IP_Write
 *
 ***************************************************************************/

UT_Bool IP_WriteSimple
	(IP_ImageId img,
	 const char fname[],
	 const char fmt[],
	 const char vsn[])
{
    FF_ImgFmtType oldfmt[FF_NumImgChanTypes], curfmt[FF_NumImgChanTypes];

    IP_GetFormat (oldfmt);
    IP_ImageInfo (img, NULL, curfmt);
    IP_SetFormat (curfmt);
    if (!IP_Write (img, fname, fmt, vsn)) {
	IP_SetFormat (oldfmt);
	return UT_False;
    }
    IP_SetFormat (oldfmt);
    return UT_True;
}

/***************************************************************************
 *[@e
 *	Name:		IP_NewImageFromFile
 *
 *	Usage:		Read an image from a file - simple version.
 *
 *	Synopsis:	IP_ImageId IP_NewImageFromFile (const char fname[])
 *
 *	Description:	A new IP_ImageId with the same width, height, aspect
 *			ratio and pixel data formats as the image stored in
 *			file "fname" is created. The pixel data in the file are
 *			copied into the new IP_ImageId without stretching or
 *			squashing and without gamma or color correction.
 *
 *	Return value:	An identifier for the image read from the file
 *			if successful, else NULL.
 *
 *	See also:	IP_WriteSimple
 *			IP_Read
 *
 ***************************************************************************/

IP_ImageId IP_NewImageFromFile (const char fname[])
{
    IP_ImageId		img;
    FF_ImgFmtType	oldfmt[FF_NumImgChanTypes], fmt[FF_NumImgChanTypes];
    FF_ImgHdr		olddesc, desc;

    IP_GetColorCorrect (&olddesc.gamma,
		        &olddesc.red, &olddesc.green,
		        &olddesc.blue, &olddesc.white);
    IP_GetFormat (oldfmt);
    if (!IP_FileInfo (fname, &desc, fmt) ||
	!IP_SetColorCorrect (desc.gamma,
			   &desc.red, &desc.green,
			   &desc.blue, &desc.white))
	return NULL;
    if (!IP_SetFormat (fmt)) {
	IP_SetColorCorrect (olddesc.gamma,
			    &olddesc.red, &olddesc.green,
			    &olddesc.blue, &olddesc.white);
	return NULL;
    }
    img = IP_ImageNew (desc.width, desc.height, desc.aspect);
    if (!img) {
	IP_SetFormat (oldfmt);
	IP_SetColorCorrect (olddesc.gamma,
			    &olddesc.red, &olddesc.green,
			    &olddesc.blue, &olddesc.white);
	return NULL;
    }
    if (!IP_Read (img, fname, UT_False, UT_False, UT_False)) {
	IP_ImageDelete (img);
	IP_SetFormat (oldfmt);
	IP_SetColorCorrect (olddesc.gamma,
			    &olddesc.red, &olddesc.green,
			    &olddesc.blue, &olddesc.white);
	return NULL;
    }
    IP_SetFormat (oldfmt);
    IP_SetColorCorrect (olddesc.gamma,
		        &olddesc.red, &olddesc.green,
		        &olddesc.blue, &olddesc.white);
    return img;
}
