blob: 3ccf60265d52000f18e4723cd37169fa22c2726d [file] [log] [blame]
/*
* "$Id: image.c 7473 2008-04-21 17:51:58Z mike $"
*
* Base image support for CUPS.
*
* Copyright 2007-2011 by Apple Inc.
* Copyright 1993-2005 by Easy Software Products.
*
* These coded instructions, statements, and computer programs are the
* property of Apple Inc. and are protected by Federal copyright
* law. Distribution and use rights are outlined in the file "LICENSE.txt"
* which should have been included with this file. If this file is
* file is missing or damaged, see the license at "http://www.cups.org/".
*
* This file is subject to the Apple OS-Developed Software exception.
*
* Contents:
*
* cupsImageClose() - Close an image file.
* cupsImageGetCol() - Get a column of pixels from an image.
* cupsImageGetColorSpace() - Get the image colorspace.
* cupsImageGetDepth() - Get the number of bytes per pixel.
* cupsImageGetHeight() - Get the height of an image.
* cupsImageGetRow() - Get a row of pixels from an image.
* cupsImageGetWidth() - Get the width of an image.
* cupsImageGetXPPI() - Get the horizontal resolution of an image.
* cupsImageGetYPPI() - Get the vertical resolution of an image.
* cupsImageOpen() - Open an image file and read it into memory.
* _cupsImagePutCol() - Put a column of pixels to an image.
* _cupsImagePutRow() - Put a row of pixels to an image.
* cupsImageSetMaxTiles() - Set the maximum number of tiles to cache.
* flush_tile() - Flush the least-recently-used tile in the cache.
* get_tile() - Get a cached tile.
*/
/*
* Include necessary headers...
*/
#include "image-private.h"
/*
* Local functions...
*/
static void flush_tile(cups_image_t *img);
static cups_ib_t *get_tile(cups_image_t *img, int x, int y);
/*
* 'cupsImageClose()' - Close an image file.
*/
void
cupsImageClose(cups_image_t *img) /* I - Image to close */
{
cups_ic_t *current, /* Current cached tile */
*next; /* Next cached tile */
/*
* Wipe the tile cache file (if any)...
*/
if (img->cachefile >= 0)
{
DEBUG_printf(("Closing/removing swap file \"%s\"...\n", img->cachename));
close(img->cachefile);
unlink(img->cachename);
}
/*
* Free the image cache...
*/
DEBUG_puts("Freeing memory...");
for (current = img->first, next = NULL; current != NULL; current = next)
{
DEBUG_printf(("Freeing cache (%p, next = %p)...\n", current, next));
next = current->next;
free(current);
}
/*
* Free the rest of memory...
*/
if (img->tiles != NULL)
{
DEBUG_printf(("Freeing tiles (%p)...\n", img->tiles[0]));
free(img->tiles[0]);
DEBUG_printf(("Freeing tile pointers (%p)...\n", img->tiles));
free(img->tiles);
}
free(img);
}
/*
* 'cupsImageGetCol()' - Get a column of pixels from an image.
*/
int /* O - -1 on error, 0 on success */
cupsImageGetCol(cups_image_t *img, /* I - Image */
int x, /* I - Column */
int y, /* I - Start row */
int height, /* I - Column height */
cups_ib_t *pixels) /* O - Pixel data */
{
int bpp, /* Bytes per pixel */
twidth, /* Tile width */
count; /* Number of pixels to get */
const cups_ib_t *ib; /* Pointer into tile */
if (img == NULL || x < 0 || x >= img->xsize || y >= img->ysize)
return (-1);
if (y < 0)
{
height += y;
y = 0;
}
if ((y + height) > img->ysize)
height = img->ysize - y;
if (height < 1)
return (-1);
bpp = cupsImageGetDepth(img);
twidth = bpp * (CUPS_TILE_SIZE - 1);
while (height > 0)
{
ib = get_tile(img, x, y);
if (ib == NULL)
return (-1);
count = CUPS_TILE_SIZE - (y & (CUPS_TILE_SIZE - 1));
if (count > height)
count = height;
y += count;
height -= count;
for (; count > 0; count --, ib += twidth)
switch (bpp)
{
case 4 :
*pixels++ = *ib++;
case 3 :
*pixels++ = *ib++;
*pixels++ = *ib++;
case 1 :
*pixels++ = *ib++;
break;
}
}
return (0);
}
/*
* 'cupsImageGetColorSpace()' - Get the image colorspace.
*/
cups_icspace_t /* O - Colorspace */
cupsImageGetColorSpace(
cups_image_t *img) /* I - Image */
{
return (img->colorspace);
}
/*
* 'cupsImageGetDepth()' - Get the number of bytes per pixel.
*/
int /* O - Bytes per pixel */
cupsImageGetDepth(cups_image_t *img) /* I - Image */
{
return (abs(img->colorspace));
}
/*
* 'cupsImageGetHeight()' - Get the height of an image.
*/
unsigned /* O - Height in pixels */
cupsImageGetHeight(cups_image_t *img) /* I - Image */
{
return (img->ysize);
}
/*
* 'cupsImageGetRow()' - Get a row of pixels from an image.
*/
int /* O - -1 on error, 0 on success */
cupsImageGetRow(cups_image_t *img, /* I - Image */
int x, /* I - Start column */
int y, /* I - Row */
int width, /* I - Width of row */
cups_ib_t *pixels) /* O - Pixel data */
{
int bpp, /* Bytes per pixel */
count; /* Number of pixels to get */
const cups_ib_t *ib; /* Pointer to pixels */
if (img == NULL || y < 0 || y >= img->ysize || x >= img->xsize)
return (-1);
if (x < 0)
{
width += x;
x = 0;
}
if ((x + width) > img->xsize)
width = img->xsize - x;
if (width < 1)
return (-1);
bpp = img->colorspace < 0 ? -img->colorspace : img->colorspace;
while (width > 0)
{
ib = get_tile(img, x, y);
if (ib == NULL)
return (-1);
count = CUPS_TILE_SIZE - (x & (CUPS_TILE_SIZE - 1));
if (count > width)
count = width;
memcpy(pixels, ib, count * bpp);
pixels += count * bpp;
x += count;
width -= count;
}
return (0);
}
/*
* 'cupsImageGetWidth()' - Get the width of an image.
*/
unsigned /* O - Width in pixels */
cupsImageGetWidth(cups_image_t *img) /* I - Image */
{
return (img->xsize);
}
/*
* 'cupsImageGetXPPI()' - Get the horizontal resolution of an image.
*/
unsigned /* O - Horizontal PPI */
cupsImageGetXPPI(cups_image_t *img) /* I - Image */
{
return (img->xppi);
}
/*
* 'cupsImageGetYPPI()' - Get the vertical resolution of an image.
*/
unsigned /* O - Vertical PPI */
cupsImageGetYPPI(cups_image_t *img) /* I - Image */
{
return (img->yppi);
}
/*
* 'cupsImageOpen()' - Open an image file and read it into memory.
*/
cups_image_t * /* O - New image */
cupsImageOpen(
const char *filename, /* I - Filename of image */
cups_icspace_t primary, /* I - Primary colorspace needed */
cups_icspace_t secondary, /* I - Secondary colorspace if primary no good */
int saturation, /* I - Color saturation level */
int hue, /* I - Color hue adjustment */
const cups_ib_t *lut) /* I - RGB gamma/brightness LUT */
{
FILE *fp; /* File pointer */
unsigned char header[16], /* First 16 bytes of file */
header2[16]; /* Bytes 2048-2064 (PhotoCD) */
cups_image_t *img; /* New image buffer */
int status; /* Status of load... */
DEBUG_printf(("cupsImageOpen(\"%s\", %d, %d, %d, %d, %p)\n",
filename ? filename : "(null)", primary, secondary,
saturation, hue, lut));
/*
* Figure out the file type...
*/
if ((fp = fopen(filename, "r")) == NULL)
return (NULL);
if (fread(header, 1, sizeof(header), fp) == 0)
{
fclose(fp);
return (NULL);
}
fseek(fp, 2048, SEEK_SET);
memset(header2, 0, sizeof(header2));
fread(header2, 1, sizeof(header2), fp);
fseek(fp, 0, SEEK_SET);
/*
* Allocate memory...
*/
img = calloc(sizeof(cups_image_t), 1);
if (img == NULL)
{
fclose(fp);
return (NULL);
}
/*
* Load the image as appropriate...
*/
img->max_ics = CUPS_TILE_MINIMUM;
img->xppi = 128;
img->yppi = 128;
if (!memcmp(header, "GIF87a", 6) || !memcmp(header, "GIF89a", 6))
status = _cupsImageReadGIF(img, fp, primary, secondary, saturation, hue,
lut);
else if (!memcmp(header, "BM", 2))
status = _cupsImageReadBMP(img, fp, primary, secondary, saturation, hue,
lut);
else if (header[0] == 0x01 && header[1] == 0xda)
status = _cupsImageReadSGI(img, fp, primary, secondary, saturation, hue,
lut);
else if (header[0] == 0x59 && header[1] == 0xa6 &&
header[2] == 0x6a && header[3] == 0x95)
status = _cupsImageReadSunRaster(img, fp, primary, secondary, saturation,
hue, lut);
else if (header[0] == 'P' && header[1] >= '1' && header[1] <= '6')
status = _cupsImageReadPNM(img, fp, primary, secondary, saturation, hue,
lut);
else if (!memcmp(header2, "PCD_IPI", 7))
status = _cupsImageReadPhotoCD(img, fp, primary, secondary, saturation,
hue, lut);
else if (!memcmp(header + 8, "\000\010", 2) ||
!memcmp(header + 8, "\000\030", 2))
status = _cupsImageReadPIX(img, fp, primary, secondary, saturation, hue,
lut);
#if defined(HAVE_LIBPNG) && defined(HAVE_LIBZ)
else if (!memcmp(header, "\211PNG", 4))
status = _cupsImageReadPNG(img, fp, primary, secondary, saturation, hue,
lut);
#endif /* HAVE_LIBPNG && HAVE_LIBZ */
#ifdef HAVE_LIBJPEG
else if (!memcmp(header, "\377\330\377", 3) && /* Start-of-Image */
header[3] >= 0xe0 && header[3] <= 0xef) /* APPn */
status = _cupsImageReadJPEG(img, fp, primary, secondary, saturation, hue,
lut);
#endif /* HAVE_LIBJPEG */
#ifdef HAVE_LIBTIFF
else if (!memcmp(header, "MM\000\052", 4) ||
!memcmp(header, "II\052\000", 4))
status = _cupsImageReadTIFF(img, fp, primary, secondary, saturation, hue,
lut);
#endif /* HAVE_LIBTIFF */
else
{
fclose(fp);
status = -1;
}
if (status)
{
free(img);
return (NULL);
}
else
return (img);
}
/*
* '_cupsImagePutCol()' - Put a column of pixels to an image.
*/
int /* O - -1 on error, 0 on success */
_cupsImagePutCol(
cups_image_t *img, /* I - Image */
int x, /* I - Column */
int y, /* I - Start row */
int height, /* I - Column height */
const cups_ib_t *pixels) /* I - Pixels to put */
{
int bpp, /* Bytes per pixel */
twidth, /* Width of tile */
count; /* Number of pixels to put */
int tilex, /* Column within tile */
tiley; /* Row within tile */
cups_ib_t *ib; /* Pointer to pixels in tile */
if (img == NULL || x < 0 || x >= img->xsize || y >= img->ysize)
return (-1);
if (y < 0)
{
height += y;
y = 0;
}
if ((y + height) > img->ysize)
height = img->ysize - y;
if (height < 1)
return (-1);
bpp = cupsImageGetDepth(img);
twidth = bpp * (CUPS_TILE_SIZE - 1);
tilex = x / CUPS_TILE_SIZE;
tiley = y / CUPS_TILE_SIZE;
while (height > 0)
{
ib = get_tile(img, x, y);
if (ib == NULL)
return (-1);
img->tiles[tiley][tilex].dirty = 1;
tiley ++;
count = CUPS_TILE_SIZE - (y & (CUPS_TILE_SIZE - 1));
if (count > height)
count = height;
y += count;
height -= count;
for (; count > 0; count --, ib += twidth)
switch (bpp)
{
case 4 :
*ib++ = *pixels++;
case 3 :
*ib++ = *pixels++;
*ib++ = *pixels++;
case 1 :
*ib++ = *pixels++;
break;
}
}
return (0);
}
/*
* '_cupsImagePutRow()' - Put a row of pixels to an image.
*/
int /* O - -1 on error, 0 on success */
_cupsImagePutRow(
cups_image_t *img, /* I - Image */
int x, /* I - Start column */
int y, /* I - Row */
int width, /* I - Row width */
const cups_ib_t *pixels) /* I - Pixel data */
{
int bpp, /* Bytes per pixel */
count; /* Number of pixels to put */
int tilex, /* Column within tile */
tiley; /* Row within tile */
cups_ib_t *ib; /* Pointer to pixels in tile */
if (img == NULL || y < 0 || y >= img->ysize || x >= img->xsize)
return (-1);
if (x < 0)
{
width += x;
x = 0;
}
if ((x + width) > img->xsize)
width = img->xsize - x;
if (width < 1)
return (-1);
bpp = img->colorspace < 0 ? -img->colorspace : img->colorspace;
tilex = x / CUPS_TILE_SIZE;
tiley = y / CUPS_TILE_SIZE;
while (width > 0)
{
ib = get_tile(img, x, y);
if (ib == NULL)
return (-1);
img->tiles[tiley][tilex].dirty = 1;
count = CUPS_TILE_SIZE - (x & (CUPS_TILE_SIZE - 1));
if (count > width)
count = width;
memcpy(ib, pixels, count * bpp);
pixels += count * bpp;
x += count;
width -= count;
tilex ++;
}
return (0);
}
/*
* 'cupsImageSetMaxTiles()' - Set the maximum number of tiles to cache.
*
* If the "max_tiles" argument is 0 then the maximum number of tiles is
* computed from the image size or the RIP_CACHE environment variable.
*/
void
cupsImageSetMaxTiles(
cups_image_t *img, /* I - Image to set */
int max_tiles) /* I - Number of tiles to cache */
{
int cache_size, /* Size of tile cache in bytes */
min_tiles, /* Minimum number of tiles to cache */
max_size; /* Maximum cache size in bytes */
char *cache_env, /* Cache size environment variable */
cache_units[255]; /* Cache size units */
min_tiles = max(CUPS_TILE_MINIMUM,
1 + max((img->xsize + CUPS_TILE_SIZE - 1) / CUPS_TILE_SIZE,
(img->ysize + CUPS_TILE_SIZE - 1) / CUPS_TILE_SIZE));
if (max_tiles == 0)
max_tiles = ((img->xsize + CUPS_TILE_SIZE - 1) / CUPS_TILE_SIZE) *
((img->ysize + CUPS_TILE_SIZE - 1) / CUPS_TILE_SIZE);
cache_size = max_tiles * CUPS_TILE_SIZE * CUPS_TILE_SIZE *
cupsImageGetDepth(img);
if ((cache_env = getenv("RIP_MAX_CACHE")) != NULL)
{
switch (sscanf(cache_env, "%d%254s", &max_size, cache_units))
{
case 0 :
max_size = 32 * 1024 * 1024;
break;
case 1 :
max_size *= 4 * CUPS_TILE_SIZE * CUPS_TILE_SIZE;
break;
case 2 :
if (tolower(cache_units[0] & 255) == 'g')
max_size *= 1024 * 1024 * 1024;
else if (tolower(cache_units[0] & 255) == 'm')
max_size *= 1024 * 1024;
else if (tolower(cache_units[0] & 255) == 'k')
max_size *= 1024;
else if (tolower(cache_units[0] & 255) == 't')
max_size *= 4 * CUPS_TILE_SIZE * CUPS_TILE_SIZE;
break;
}
}
else
max_size = 32 * 1024 * 1024;
if (cache_size > max_size)
max_tiles = max_size / CUPS_TILE_SIZE / CUPS_TILE_SIZE /
cupsImageGetDepth(img);
if (max_tiles < min_tiles)
max_tiles = min_tiles;
img->max_ics = max_tiles;
DEBUG_printf(("max_ics=%d...\n", img->max_ics));
}
/*
* 'flush_tile()' - Flush the least-recently-used tile in the cache.
*/
static void
flush_tile(cups_image_t *img) /* I - Image */
{
int bpp; /* Bytes per pixel */
cups_itile_t *tile; /* Pointer to tile */
bpp = cupsImageGetDepth(img);
tile = img->first->tile;
if (!tile->dirty)
{
tile->ic = NULL;
return;
}
if (img->cachefile < 0)
{
if ((img->cachefile = cupsTempFd(img->cachename,
sizeof(img->cachename))) < 0)
{
tile->ic = NULL;
tile->dirty = 0;
return;
}
DEBUG_printf(("Created swap file \"%s\"...\n", img->cachename));
}
if (tile->pos >= 0)
{
if (lseek(img->cachefile, tile->pos, SEEK_SET) != tile->pos)
{
tile->ic = NULL;
tile->dirty = 0;
return;
}
}
else
{
if ((tile->pos = lseek(img->cachefile, 0, SEEK_END)) < 0)
{
tile->ic = NULL;
tile->dirty = 0;
return;
}
}
write(img->cachefile, tile->ic->pixels, bpp * CUPS_TILE_SIZE * CUPS_TILE_SIZE);
tile->ic = NULL;
tile->dirty = 0;
}
/*
* 'get_tile()' - Get a cached tile.
*/
static cups_ib_t * /* O - Pointer to tile or NULL */
get_tile(cups_image_t *img, /* I - Image */
int x, /* I - Column in image */
int y) /* I - Row in image */
{
int bpp, /* Bytes per pixel */
tilex, /* Column within tile */
tiley, /* Row within tile */
xtiles, /* Number of tiles horizontally */
ytiles; /* Number of tiles vertically */
cups_ic_t *ic; /* Cache pointer */
cups_itile_t *tile; /* Tile pointer */
if (img->tiles == NULL)
{
xtiles = (img->xsize + CUPS_TILE_SIZE - 1) / CUPS_TILE_SIZE;
ytiles = (img->ysize + CUPS_TILE_SIZE - 1) / CUPS_TILE_SIZE;
DEBUG_printf(("Creating tile array (%dx%d)\n", xtiles, ytiles));
if ((img->tiles = calloc(sizeof(cups_itile_t *), ytiles)) == NULL)
return (NULL);
if ((tile = calloc(xtiles * sizeof(cups_itile_t), ytiles)) == NULL)
return (NULL);
for (tiley = 0; tiley < ytiles; tiley ++)
{
img->tiles[tiley] = tile;
for (tilex = xtiles; tilex > 0; tilex --, tile ++)
tile->pos = -1;
}
}
bpp = cupsImageGetDepth(img);
tilex = x / CUPS_TILE_SIZE;
tiley = y / CUPS_TILE_SIZE;
tile = img->tiles[tiley] + tilex;
x &= (CUPS_TILE_SIZE - 1);
y &= (CUPS_TILE_SIZE - 1);
if ((ic = tile->ic) == NULL)
{
if (img->num_ics < img->max_ics)
{
if ((ic = calloc(sizeof(cups_ic_t) +
bpp * CUPS_TILE_SIZE * CUPS_TILE_SIZE, 1)) == NULL)
{
if (img->num_ics == 0)
return (NULL);
flush_tile(img);
ic = img->first;
}
else
{
ic->pixels = ((cups_ib_t *)ic) + sizeof(cups_ic_t);
img->num_ics ++;
DEBUG_printf(("Allocated cache tile %d (%p)...\n", img->num_ics, ic));
}
}
else
{
DEBUG_printf(("Flushing old cache tile (%p)...\n", img->first));
flush_tile(img);
ic = img->first;
}
ic->tile = tile;
tile->ic = ic;
if (tile->pos >= 0)
{
DEBUG_printf(("Loading cache tile from file position " CUPS_LLFMT "...\n",
CUPS_LLCAST tile->pos));
lseek(img->cachefile, tile->pos, SEEK_SET);
read(img->cachefile, ic->pixels, bpp * CUPS_TILE_SIZE * CUPS_TILE_SIZE);
}
else
{
DEBUG_puts("Clearing cache tile...");
memset(ic->pixels, 0, bpp * CUPS_TILE_SIZE * CUPS_TILE_SIZE);
}
}
if (ic == img->first)
{
if (ic->next != NULL)
ic->next->prev = NULL;
img->first = ic->next;
ic->next = NULL;
ic->prev = NULL;
}
else if (img->first == NULL)
img->first = ic;
if (ic != img->last)
{
/*
* Remove the cache entry from the list...
*/
if (ic->prev != NULL)
ic->prev->next = ic->next;
if (ic->next != NULL)
ic->next->prev = ic->prev;
/*
* And add it to the end...
*/
if (img->last != NULL)
img->last->next = ic;
ic->prev = img->last;
img->last = ic;
}
ic->next = NULL;
return (ic->pixels + bpp * (y * CUPS_TILE_SIZE + x));
}
/*
* End of "$Id: image.c 7473 2008-04-21 17:51:58Z mike $".
*/