/*
* image.c: Input and output of PNM images.
*
* Written by: Ullrich Hafner
*
* This file is part of FIASCO (Fractal Image And Sequence COdec)
* Copyright (C) 1994-2000 Ullrich Hafner
*/
/*
* $Date: 2000/06/15 17:21:30 $
* $Author: hafner $
* $Revision: 5.2 $
* $State: Exp $
*/
#include "pnm.h"
#include <string.h>
#include "nstring.h"
#include "types.h"
#include "macros.h"
#include "error.h"
#include "fiasco.h"
#include "misc.h"
#include "image.h"
/*****************************************************************************
prototypes
*****************************************************************************/
static void
init_chroma_tables (void);
/*****************************************************************************
local variables
*****************************************************************************/
static int *Cr_r_tab = NULL;
static int *Cr_g_tab = NULL;
static int *Cb_g_tab = NULL;
static int *Cb_b_tab = NULL;
/*****************************************************************************
public code
*****************************************************************************/
static fiasco_image_t *
make_image_base(void)
{
fiasco_image_t * const imageP = Calloc (1, sizeof (fiasco_image_t));
if (imageP == NULL)
pm_error("Failed to allocate memory for image object");
else {
imageP->delete = fiasco_image_delete;
imageP->get_width = fiasco_image_get_width;
imageP->get_height = fiasco_image_get_height;
imageP->is_color = fiasco_image_is_color;
}
return imageP;
}
fiasco_image_t *
fiasco_image_new_file(const char * const filename)
/*
* FIASCO image constructor.
* Allocate memory for the FIASCO image structure and
* load the image from PNM file `filename'.
*
* Return value:
* pointer to the new image structure
* or NULL in case of an error
*/
{
fiasco_image_t * imageP;
imageP = make_image_base();
imageP->private = read_image_file(filename);
return imageP;
}
fiasco_image_t *
fiasco_image_new_stream(FILE * const ifP,
unsigned int const width,
unsigned int const height,
xelval const maxval,
int const format)
/*
* FIASCO image constructor.
* Allocate memory for the FIASCO image structure and
* load the image from the PNM stream in *ifP, which is positioned just
* after the header. 'width', 'height', 'maxval', and 'format' are the
* parameters of the image, i.e. the contents of that header.
*
* Return value:
* pointer to the new image structure
* or NULL in case of an error
*/
{
fiasco_image_t * imageP;
imageP = make_image_base();
imageP->private = read_image_stream(ifP, width, height, maxval, format);
return imageP;
}
void
fiasco_image_delete (fiasco_image_t *image)
/*
* FIASCO image destructor.
* Free memory of FIASCO image struct.
*
* No return value.
*
* Side effects:
* structure 'image' is discarded.
*/
{
image_t *this = cast_image (image);
if (!this)
return;
try
{
free_image (this);
}
catch
{
return;
}
}
unsigned
fiasco_image_get_width (fiasco_image_t *image)
{
image_t *this = cast_image (image);
if (!this)
return 0;
else
return this->width;
}
unsigned
fiasco_image_get_height (fiasco_image_t *image)
{
image_t *this = cast_image (image);
if (!this)
return 0;
else
return this->width;
}
int
fiasco_image_is_color (fiasco_image_t *image)
{
image_t *this = cast_image (image);
if (!this)
return 0;
else
return this->color;
}
image_t *
cast_image (fiasco_image_t *image)
/*
* Cast pointer `image' to type image_t.
* Check whether `image' is a valid object of type image_t.
*
* Return value:
* pointer to dfiasco_t struct on success
* NULL otherwise
*/
{
image_t *this = (image_t *) image->private;
if (this)
{
if (!streq (this->id, "IFIASCO"))
{
set_error (_("Parameter `image' doesn't match required type."));
return NULL;
}
}
else
{
set_error (_("Parameter `%s' not defined (NULL)."), "image");
}
return this;
}
image_t *
alloc_image (unsigned width, unsigned height, bool_t color, format_e format)
/*
* Image constructor:
* Allocate memory for the image_t structure.
* Image size is given by 'width' and 'height'.
* If 'color' == YES then allocate memory for three color bands (Y, Cb, Cr).
* otherwise just allocate memory for a grayscale image.
* 'format' specifies whether image pixels of color images
* are stored in 4:4:4 or 4:2:0 format.
*
* Return value:
* pointer to the new image structure.
*/
{
image_t *image;
color_e band;
if ((width & 1) || (height & 1))
error ("Width and height of images must be even numbers.");
if (!color)
format = FORMAT_4_4_4;
image = Calloc (1, sizeof (image_t));
image->width = width;
image->height = height;
image->color = color;
image->format = format;
image->reference_count = 1;
STRSCPY(image->id, "IFIASCO");
for (band = first_band (color); band <= last_band (color); band++)
if (format == FORMAT_4_2_0 && band != Y)
image->pixels [band] = Calloc ((width * height) >> 2,
sizeof (word_t));
else
image->pixels [band] = Calloc (width * height, sizeof (word_t));
return image;
}
image_t *
clone_image (image_t *image)
/*
* Copy constructor:
* Construct new image by copying the given `image'.
*
* Return value:
* pointer to the new image structure.
*/
{
image_t *new = alloc_image (image->width, image->height, image->color,
image->format);
color_e band;
for (band = first_band (new->color); band <= last_band (new->color); band++)
if (new->format == FORMAT_4_2_0 && band != Y)
{
memcpy (new->pixels [band], image->pixels [band],
((new->width * new->height) >> 2) * sizeof (word_t));
}
else
{
memcpy (new->pixels [band], image->pixels [band],
new->width * new->height * sizeof (word_t));
}
return new;
}
void
free_image (image_t *image)
/*
* Image destructor:
* Free memory of 'image' struct and pixel data.
*
* No return value.
*
* Side effects:
* structure 'image' is discarded.
*/
{
if (image != NULL)
{
if (--image->reference_count)
return; /* image is still referenced */
else
{
color_e band;
for (band = first_band (image->color);
band <= last_band (image->color); band++)
if (image->pixels [band])
Free (image->pixels [band]);
Free (image);
}
}
else
warning ("Can't free image <NULL>.");
}
static void
read_image_data(image_t * const image, FILE *input, const bool_t color,
const int width, const int height, const xelval maxval,
const int format) {
int row;
int i; /* Cursor into image->pixels arrays */
xel * xelrow;
/* The following are just the normal rgb -> YCbCr conversion matrix,
except normalization to maxval 4095 (12 bit color) is built in
*/
const double coeff_lu_r = +0.2989 / maxval * 4095;
const double coeff_lu_g = +0.5866 / maxval * 4095;
const double coeff_lu_b = +0.1145 / maxval * 4095;
const double coeff_cb_r = -0.1687 / maxval * 4095;
const double coeff_cb_g = -0.3312 / maxval * 4095;
const double coeff_cb_b = +0.5000 / maxval * 4095;
const double coeff_cr_r = +0.5000 / maxval * 4095;
const double coeff_cr_g = -0.4183 / maxval * 4095;
const double coeff_cr_b = -0.0816 / maxval * 4095;
xelrow = pnm_allocrow(width);
i = 0;
for (row = 0; row < height; row++) {
int col;
pnm_readpnmrow(input, xelrow, width, maxval, format);
for (col = 0; col < width; col++) {
if (color) {
image->pixels[Y][i] =
coeff_lu_r * PPM_GETR(xelrow[col])
+ coeff_lu_g * PPM_GETG(xelrow[col])
+ coeff_lu_b * PPM_GETB(xelrow[col]) - 2048;
image->pixels[Cb][i] =
coeff_cb_r * PPM_GETR(xelrow[col])
+ coeff_cb_g * PPM_GETG(xelrow[col])
+ coeff_cb_b * PPM_GETB(xelrow[col]);
image->pixels[Cr][i] =
coeff_cr_r * PPM_GETR(xelrow[col])
+ coeff_cr_g * PPM_GETG(xelrow[col])
+ coeff_cr_b * PPM_GETB(xelrow[col]);
i++;
} else
image->pixels[GRAY][i++] =
PNM_GET1(xelrow[col]) * 4095 / maxval - 2048;
}
}
free(xelrow);
}
image_t *
read_image_stream(FILE * const ifP,
unsigned int const width,
unsigned int const height,
xelval const maxval,
int const format)
/*
* Read one PNM image from stream *ifP, which is positioned just after the
* header. 'width', 'height', 'maxval', and 'format' are the parameters of
* the image (i.e. the contents of that header).
*/
{
image_t *image; /* pointer to new image structure */
bool_t color; /* color image ? (YES/NO) */
if (PNM_FORMAT_TYPE(format) == PPM_FORMAT)
color = YES;
else
color = NO;
if (width < 32)
pm_error("Image must have a width of at least 32 pixels.");
if (height < 32)
pm_error("Image must have a height of at least 32 pixels.");
image = alloc_image (width, height, color, FORMAT_4_4_4);
read_image_data(image, ifP, color, width, height, maxval, format);
return image;
}
image_t *
read_image_file(const char * const filename)
/*
* Read the PNM image from the file named 'filename'.
*
* Return value:
* pointer to the image structure.
*/
{
FILE * ifP;
int width, height; /* image dimensions */
xelval maxval; /* Maxval of image */
int format; /* Image's format code */
image_t * imageP; /* pointer to new image structure */
ifP = pm_openr(filename);
pnm_readpnminit(ifP, &width, &height, &maxval, &format);
imageP = read_image_stream(ifP, width, height, maxval, format);
pm_close(ifP);
return imageP;
}
void
write_image (const char *image_name, const image_t *image)
/*
* Write given 'image' data to the file 'image_name'.
*
* No return value.
*/
{
FILE *output; /* output stream */
int format;
int row;
int i; /* Cursor into image->pixel arrays */
xel * xelrow;
unsigned *gray_clip; /* clipping table */
assert (image && image_name);
if (image->format == FORMAT_4_2_0)
{
warning ("We cannot write images in 4:2:0 format.");
return;
}
if (image_name == NULL)
output = stdout;
else if (streq(image_name, "-"))
output = stdout;
else
output = pm_openw((char*)image_name);
gray_clip = init_clipping (); /* mapping of int -> unsigned */
if (!gray_clip)
error (fiasco_get_error_message ());
init_chroma_tables ();
format = image->color ? PPM_TYPE : PGM_TYPE;
pnm_writepnminit(output, image->width, image->height, 255, format, 0);
xelrow = pnm_allocrow(image->width);
i = 0;
for (row = 0; row < image->height; row++) {
int col;
for (col = 0; col < image->width; col++) {
if (image->color) {
word_t yval, cbval, crval;
yval = image->pixels[Y][i] / 16 + 128;
cbval = image->pixels[Cb][i] / 16;
crval = image->pixels[Cr][i] / 16;
PPM_ASSIGN(xelrow[col],
gray_clip[yval + Cr_r_tab[crval]],
gray_clip[yval + Cr_g_tab[crval] + Cb_g_tab [cbval]],
gray_clip[yval + Cb_b_tab[cbval]]);
} else
/* The 16 below should be 4095/255 = 16.0588 */
PNM_ASSIGN1(xelrow[col],
gray_clip[image->pixels[GRAY][i]/16+128]);
i++;
}
pnm_writepnmrow(output, xelrow,
image->width, 255, format, 0);
}
pnm_freerow(xelrow);
pm_close(output);
}
bool_t
same_image_type (const image_t *img1, const image_t *img2)
/*
* Check whether the given images 'img1' and `img2' are of the same type.
*
* Return value:
* YES if images 'img1' and `img2' are of the same type
* NO otherwise.
*/
{
assert (img1 && img2);
return ((img1->width == img2->width)
&& (img1->height == img2->height)
&& (img1->color == img2->color)
&& (img1->format == img2->format));
}
/*****************************************************************************
private code
*****************************************************************************/
static void
init_chroma_tables (void)
/*
* Chroma tables are used to perform fast YCbCr->RGB color space conversion.
*/
{
int crval, cbval, i;
if (Cr_r_tab != NULL || Cr_g_tab != NULL ||
Cb_g_tab != NULL || Cb_b_tab != NULL)
return;
Cr_r_tab = Calloc (768, sizeof (int));
Cr_g_tab = Calloc (768, sizeof (int));
Cb_g_tab = Calloc (768, sizeof (int));
Cb_b_tab = Calloc (768, sizeof (int));
for (i = 256; i < 512; i++)
{
cbval = crval = i - 128 - 256;
Cr_r_tab[i] = 1.4022 * crval + 0.5;
Cr_g_tab[i] = -0.7145 * crval + 0.5;
Cb_g_tab[i] = -0.3456 * cbval + 0.5;
Cb_b_tab[i] = 1.7710 * cbval + 0.5;
}
for (i = 0; i < 256; i++)
{
Cr_r_tab[i] = Cr_r_tab[256];
Cr_g_tab[i] = Cr_g_tab[256];
Cb_g_tab[i] = Cb_g_tab[256];
Cb_b_tab[i] = Cb_b_tab[256];
}
for (i = 512; i < 768; i++)
{
Cr_r_tab[i] = Cr_r_tab[511];
Cr_g_tab[i] = Cr_g_tab[511];
Cb_g_tab[i] = Cb_g_tab[511];
Cb_b_tab[i] = Cb_b_tab[511];
}
Cr_r_tab += 256 + 128;
Cr_g_tab += 256 + 128;
Cb_g_tab += 256 + 128;
Cb_b_tab += 256 + 128;
}