/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "image.h"
#include "fixed.h"
#include "bmp.c"
#ifdef HAVE_JPEG
#include "jpeg.c"
#endif
#ifdef HAVE_PNG
#include "png.c"
#endif
#ifdef HAVE_GIF
#include "gif.c"
#endif
// GD algorithm
#include "gd.c"
// Algorithms from GraphicsMagick
#include "magick.c"
#include "magick_fixed.c"
int
image_init(HV *self, image *im)
{
unsigned char *bptr;
char *file = NULL;
int ret = 1;
if (my_hv_exists(self, "file")) {
// Input from file
SV *path = *(my_hv_fetch(self, "file"));
file = SvPVX(path);
im->fh = IoIFP(sv_2io(*(my_hv_fetch(self, "_fh"))));
im->path = newSVsv(path);
}
else {
// Input from scalar ref
im->fh = NULL;
im->path = newSVpv("(data)", 0);
im->sv_data = *(my_hv_fetch(self, "data"));
if (SvROK(im->sv_data))
im->sv_data = SvRV(im->sv_data);
else
croak("data is not a scalar ref\n");
}
im->pixbuf = NULL;
im->outbuf = NULL;
im->outbuf_size = 0;
im->type = UNKNOWN;
im->sv_offset = 0;
im->image_offset = 0;
im->image_length = 0;
im->width = 0;
im->height = 0;
im->width_padding = 0;
im->width_inner = 0;
im->height_padding = 0;
im->height_inner = 0;
im->flipped = 0;
im->bpp = 0;
im->channels = 0;
im->has_alpha = 0;
im->orientation = ORIENTATION_NORMAL;
im->orientation_orig = ORIENTATION_NORMAL;
im->memory_limit = 0;
im->target_width = 0;
im->target_height = 0;
im->keep_aspect = 0;
im->resize_type = IMAGE_SCALE_TYPE_GD_FIXED;
im->filter = 0;
im->bgcolor = 0;
im->used = 0;
im->palette = NULL;
#ifdef HAVE_JPEG
im->cinfo = NULL;
#endif
#ifdef HAVE_PNG
im->png_ptr = NULL;
im->info_ptr = NULL;
#endif
#ifdef HAVE_GIF
im->gif = NULL;
#endif
// Read new() options
if (my_hv_exists(self, "offset")) {
im->image_offset = SvIV(*(my_hv_fetch(self, "offset")));
if (im->fh != NULL)
PerlIO_seek(im->fh, im->image_offset, SEEK_SET);
}
if (my_hv_exists(self, "length"))
im->image_length = SvIV(*(my_hv_fetch(self, "length")));
Newz(0, im->buf, sizeof(Buffer), Buffer);
buffer_init(im->buf, BUFFER_SIZE);
im->memory_used = BUFFER_SIZE;
// Determine type of file from magic bytes
if (im->fh != NULL) {
if ( !_check_buf(im->fh, im->buf, 8, BUFFER_SIZE) ) {
image_finish(im);
croak("Unable to read image header for %s\n", file);
}
}
else {
im->sv_offset = MIN(sv_len(im->sv_data) - im->image_offset, BUFFER_SIZE);
buffer_append(im->buf, SvPVX(im->sv_data) + im->image_offset, im->sv_offset);
}
bptr = buffer_ptr(im->buf);
switch (bptr[0]) {
case 0xff:
if (bptr[1] == 0xd8 && bptr[2] == 0xff) {
#ifdef HAVE_JPEG
im->type = JPEG;
#else
image_finish(im);
croak("Image::Scale was not built with JPEG support\n");
#endif
}
break;
case 0x89:
if (bptr[1] == 'P' && bptr[2] == 'N' && bptr[3] == 'G'
&& bptr[4] == 0x0d && bptr[5] == 0x0a && bptr[6] == 0x1a && bptr[7] == 0x0a) {
#ifdef HAVE_PNG
im->type = PNG;
#else
image_finish(im);
croak("Image::Scale was not built with PNG support\n");
#endif
}
break;
case 'G':
if (bptr[1] == 'I' && bptr[2] == 'F' && bptr[3] == '8'
&& (bptr[4] == '7' || bptr[4] == '9') && bptr[5] == 'a') {
#ifdef HAVE_GIF
im->type = GIF;
#else
image_finish(im);
croak("Image::Scale was not built with GIF support\n");
#endif
}
break;
case 'B':
if (bptr[1] == 'M') {
im->type = BMP;
}
break;
}
DEBUG_TRACE("Image type: %d\n", im->type);
// Read image header via type-specific function to determine dimensions
switch (im->type) {
#ifdef HAVE_JPEG
case JPEG:
if ( !image_jpeg_read_header(im) ) {
ret = 0;
goto out;
}
break;
#endif
#ifdef HAVE_PNG
case PNG:
if ( !image_png_read_header(im) ) {
ret = 0;
goto out;
}
break;
#endif
#ifdef HAVE_GIF
case GIF:
if ( !image_gif_read_header(im) ) {
ret = 0;
goto out;
}
break;
#endif
case BMP:
image_bmp_read_header(im);
break;
case UNKNOWN:
warn("Image::Scale unknown file type (%s), first 8 bytes were: %02x %02x %02x %02x %02x %02x %02x %02x\n",
SvPVX(im->path), bptr[0], bptr[1], bptr[2], bptr[3], bptr[4], bptr[5], bptr[6], bptr[7]);
ret = 0;
break;
}
DEBUG_TRACE("Image dimenensions: %d x %d, channels %d\n", im->width, im->height, im->channels);
out:
if (ret == 0)
image_finish(im);
return ret;
}
void
image_alloc(image *im, int width, int height)
{
int size = width * height * sizeof(pix);
if (im->memory_limit && im->memory_limit < im->memory_used + size) {
image_finish(im);
croak("Image::Scale memory_limit exceeded (wanted to allocate %d bytes)\n", im->memory_used + size);
}
DEBUG_TRACE("Allocating %d bytes for decompressed image\n", size);
New(0, im->pixbuf, size, pix);
im->memory_used += size;
}
void
image_bgcolor_fill(pix *buf, int size, int bgcolor)
{
int alloc_size = size * sizeof(pix);
int i;
if (bgcolor != 0) {
for (i = 0; i < alloc_size; i += sizeof(pix))
memcpy( ((char *)buf) + i, &bgcolor, sizeof(pix) );
}
else {
Zero(buf, size, pix);
}
}
int
image_resize(image *im)
{
int size;
int ret = 1;
// Check if we have already resized an image with this object,
// if so, clear everything we've already done
if (im->used) {
DEBUG_TRACE("Object already used for a resize, resetting\n");
if (im->outbuf != NULL) {
Safefree(im->outbuf);
im->outbuf = NULL;
im->memory_used -= im->outbuf_size;
}
#ifdef HAVE_JPEG
// For a JPEG we have to reset the scaled size in case we're resizing larger than before
if (im->type == JPEG) {
im->width = im->cinfo->image_width;
im->height = im->cinfo->image_height;
DEBUG_TRACE("JPEG dimensions set back to original %d x %d\n", im->width, im->height);
}
#endif
}
// Load the source image into memory
switch (im->type) {
#ifdef HAVE_JPEG
case JPEG:
if ( !image_jpeg_load(im) ) {
ret = 0;
goto out;
}
break;
#endif
#ifdef HAVE_PNG
case PNG:
if ( !image_png_load(im) ) {
ret = 0;
goto out;
}
break;
#endif
#ifdef HAVE_GIF
case GIF:
if ( !image_gif_load(im) ) {
ret = 0;
goto out;
}
break;
#endif
case BMP:
if ( !image_bmp_load(im) ) {
ret = 0;
goto out;
}
break;
}
// Special case for equal size without resizing
if (im->width == im->target_width && im->height == im->target_height) {
im->outbuf = im->pixbuf;
goto out;
}
// Allocate space for the resized image
size = im->target_width * im->target_height;
im->outbuf_size = size * sizeof(pix);
if (im->memory_limit && im->memory_limit < im->memory_used + im->outbuf_size) {
image_finish(im);
croak("Image::Scale memory_limit exceeded (wanted to allocate %d bytes)\n", im->memory_used + im->outbuf_size);
}
DEBUG_TRACE("Allocating %d bytes for resized image of size %d x %d\n",
im->outbuf_size, im->target_width, im->target_height);
New(0, im->outbuf, size, pix);
im->memory_used += im->outbuf_size;
// Determine padding if necessary
if (im->keep_aspect) {
float source_ar = 1.0 * im->width / im->height;
float dest_ar = 1.0 * im->target_width / im->target_height;
if (source_ar >= dest_ar) {
im->height_padding = (int)((im->target_height - (im->target_width / source_ar)) / 2);
im->height_inner = (int)(im->target_width / source_ar);
if (im->height_inner < 1) // Avoid divide by 0
im->height_inner = 1;
}
else {
im->width_padding = (int)((im->target_width - (im->target_height * source_ar)) / 2);
im->width_inner = (int)(im->target_height * source_ar);
if (im->width_inner < 1) // Avoid divide by 0
im->width_inner = 1;
}
// Fill new space with the bgcolor or zeros
image_bgcolor_fill(im->outbuf, size, im->bgcolor);
DEBUG_TRACE("Using width padding %d, inner width %d, height padding %d, inner height %d, bgcolor %x\n",
im->width_padding, im->width_inner, im->height_padding, im->height_inner, im->bgcolor);
}
// Resize
switch (im->resize_type) {
case IMAGE_SCALE_TYPE_GD:
image_downsize_gd(im);
break;
case IMAGE_SCALE_TYPE_GD_FIXED:
image_downsize_gd_fixed_point(im);
break;
case IMAGE_SCALE_TYPE_GM:
image_downsize_gm(im);
break;
case IMAGE_SCALE_TYPE_GM_FIXED:
image_downsize_gm_fixed_point(im);
break;
default:
image_finish(im);
croak("Image::Scale unknown resize type %d\n", im->resize_type);
}
// If the image was rotated, swap the width/height if necessary
// This is needed for the save_*() functions to output the correct size
if (im->orientation >= 5) {
int tmp = im->target_height;
im->target_height = im->target_width;
im->target_width = tmp;
DEBUG_TRACE("Image was rotated, output now %d x %d\n", im->target_width, im->target_height);
}
// After resizing we can release the source image memory
Safefree(im->pixbuf);
im->pixbuf = NULL;
out:
im->used++;
return ret;
}
void
image_finish(image *im)
{
// Called at DESTROY-time to release all memory if needed.
// Items here may be freed elsewhere so must check that they aren't NULL
DEBUG_TRACE("image_finish\n");
switch (im->type) {
#ifdef HAVE_JPEG
case JPEG:
image_jpeg_finish(im);
break;
#endif
#ifdef HAVE_PNG
case PNG:
image_png_finish(im);
break;
#endif
#ifdef HAVE_GIF
case GIF:
image_gif_finish(im);
break;
#endif
case BMP:
image_bmp_finish(im);
break;
}
if (im->buf != NULL) {
buffer_free(im->buf);
Safefree(im->buf);
im->buf = NULL;
}
if (im->pixbuf != NULL && im->pixbuf != im->outbuf) { // pixbuf = outbuf if resizing to same dimensions
Safefree(im->pixbuf);
im->pixbuf = NULL;
}
if (im->outbuf != NULL) {
Safefree(im->outbuf);
im->outbuf = NULL;
im->outbuf_size = 0;
}
if (im->path != NULL) {
SvREFCNT_dec(im->path);
im->path = NULL;
}
DEBUG_TRACE("Freed all memory, total used: %d\n", im->memory_used);
im->memory_used = 0;
}
inline void
image_get_rotated_coords(image *im, int x, int y, int *ox, int *oy)
{
switch (im->orientation) {
case ORIENTATION_MIRROR_HORIZ: // 2
*ox = im->target_width - 1 - x;
*oy = y;
break;
case ORIENTATION_180: // 3
*ox = im->target_width - 1 - x;
*oy = im->target_height - 1 - y;
break;
case ORIENTATION_MIRROR_VERT: // 4
*ox = x;
*oy = im->target_height - 1 - y;
break;
case ORIENTATION_MIRROR_HORIZ_270_CCW: // 5
*ox = y;
*oy = x;
break;
case ORIENTATION_90_CCW: // 6
*ox = im->target_height - 1 - y;
*oy = x;
break;
case ORIENTATION_MIRROR_HORIZ_90_CCW: // 7
*ox = im->target_height - 1 - y;
*oy = im->target_width - 1 - x;
break;
case ORIENTATION_270_CCW: // 8
*ox = y;
*oy = im->target_width - 1 - x;
break;
default:
if (x == 0 && y == 0 && im->orientation != 0) // An invalid orientation of 0 is often seen in non-rotated images
warn("Image::Scale cannot rotate, unknown orientation value: %d (%s)\n", im->orientation, SvPVX(im->path));
*ox = x;
*oy = y;
break;
}
}