/* xform.c - Image transformation functions for gifsicle.
Copyright (C) 1997-9 Eddie Kohler, eddietwo@lcs.mit.edu
This file is part of gifsicle.
Gifsicle is free software. It is distributed under the GNU Public License,
version 2 or later; you can copy, distribute, or alter it at will, as long
as this notice is kept intact and this source code is made available. There
is no warranty, express or implied. */
#include "config.h"
#include "gifsicle.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <errno.h>
#include <limits.h>
/******
* color transforms
**/
Gt_ColorTransform *
append_color_transform(Gt_ColorTransform *list,
color_transform_func func, void *data)
{
Gt_ColorTransform *trav;
Gt_ColorTransform *xform = Gif_New(Gt_ColorTransform);
xform->next = 0;
xform->func = func;
xform->data = data;
for (trav = list; trav && trav->next; trav = trav->next)
;
if (trav) {
trav->next = xform;
return list;
} else
return xform;
}
Gt_ColorTransform *
delete_color_transforms(Gt_ColorTransform *list, color_transform_func func)
{
Gt_ColorTransform *prev = 0, *trav = list;
while (trav) {
Gt_ColorTransform *next = trav->next;
if (trav->func == func) {
if (prev) prev->next = next;
else list = next;
Gif_Delete(trav);
} else
prev = trav;
trav = next;
}
return list;
}
void
apply_color_transforms(Gt_ColorTransform *list, Gif_Stream *gfs)
{
int i;
Gt_ColorTransform *xform;
for (xform = list; xform; xform = xform->next) {
if (gfs->global)
xform->func(gfs->global, xform->data);
for (i = 0; i < gfs->nimages; i++)
if (gfs->images[i]->local)
xform->func(gfs->images[i]->local, xform->data);
}
}
typedef struct Gt_ColorChange {
struct Gt_ColorChange *next;
Gif_Color old_color;
Gif_Color new_color;
} Gt_ColorChange;
void
color_change_transformer(Gif_Colormap *gfcm, void *thunk)
{
int i, have;
Gt_ColorChange *first_change = (Gt_ColorChange *)thunk;
Gt_ColorChange *change;
/* change colors named by color */
for (i = 0; i < gfcm->ncol; i++)
for (change = first_change; change; change = change->next) {
if (!change->old_color.haspixel)
have = GIF_COLOREQ(&gfcm->col[i], &change->old_color);
else
have = change->old_color.pixel == i;
if (have) {
gfcm->col[i] = change->new_color;
break; /* ignore remaining color changes */
}
}
}
Gt_ColorTransform *
append_color_change(Gt_ColorTransform *list,
Gif_Color old_color, Gif_Color new_color)
{
Gt_ColorTransform *xform;
Gt_ColorChange *change = Gif_New(Gt_ColorChange);
change->old_color = old_color;
change->new_color = new_color;
change->next = 0;
for (xform = list; xform && xform->next; xform = xform->next)
;
if (!xform || xform->func != &color_change_transformer)
return append_color_transform(list, &color_change_transformer, change);
else {
Gt_ColorChange *prev = (Gt_ColorChange *)(xform->data);
while (prev->next) prev = prev->next;
prev->next = change;
return list;
}
}
void
pipe_color_transformer(Gif_Colormap *gfcm, void *thunk)
{
int i, status;
FILE *f;
Gif_Color *col = gfcm->col;
Gif_Colormap *new_cm = 0;
char *command = (char *)thunk;
char *tmp_file = tmpnam(0);
char *new_command;
if (!tmp_file)
fatal_error("can't create temporary file!");
new_command = Gif_NewArray(char, strlen(command) + strlen(tmp_file) + 4);
sprintf(new_command, "%s >%s", command, tmp_file);
f = popen(new_command, "w");
if (!f)
fatal_error("can't run color transformation command: %s", strerror(errno));
Gif_DeleteArray(new_command);
for (i = 0; i < gfcm->ncol; i++)
fprintf(f, "%d %d %d\n", col[i].red, col[i].green, col[i].blue);
errno = 0;
status = pclose(f);
if (status < 0) {
error("color transformation error: %s", strerror(errno));
goto done;
} else if (status > 0) {
error("color transformation command failed");
goto done;
}
f = fopen(tmp_file, "r");
if (!f || feof(f)) {
error("color transformation command generated no output", command);
if (f) fclose(f);
goto done;
}
new_cm = read_colormap_file("<color transformation>", f);
fclose(f);
if (new_cm) {
int nc = new_cm->ncol;
if (nc < gfcm->ncol) {
nc = gfcm->ncol;
warning("too few colors in color transformation results");
} else if (nc > gfcm->ncol)
warning("too many colors in color transformation results");
for (i = 0; i < nc; i++)
col[i] = new_cm->col[i];
}
done:
remove(tmp_file);
Gif_DeleteColormap(new_cm);
}
/*****
* crop image; returns true if the image exists
**/
int
crop_image(Gif_Image *gfi, Gt_Crop *crop)
{
int x, y, w, h, j;
byte **img;
if (!crop->ready) {
crop->x = crop->spec_x + gfi->left;
crop->y = crop->spec_y + gfi->top;
crop->w = crop->spec_w <= 0 ? gfi->width + crop->spec_w : crop->spec_w;
crop->h = crop->spec_h <= 0 ? gfi->height + crop->spec_h : crop->spec_h;
if (crop->x < 0 || crop->y < 0 || crop->w <= 0 || crop->h <= 0
|| crop->x + crop->w > gfi->width
|| crop->y + crop->h > gfi->height) {
error("cropping dimensions don't fit image");
crop->ready = 2;
} else
crop->ready = 1;
}
if (crop->ready == 2)
return 1;
x = crop->x - gfi->left;
y = crop->y - gfi->top;
w = crop->w;
h = crop->h;
/* Check that the rectangle actually intersects with the image. */
if (x < 0) w += x, x = 0;
if (y < 0) h += y, y = 0;
if (x + w > gfi->width) w = gfi->width - x;
if (y + h > gfi->height) h = gfi->height - y;
if (w > 0 && h > 0) {
img = Gif_NewArray(byte *, h + 1);
for (j = 0; j < h; j++)
img[j] = gfi->img[y + j] + x;
img[h] = 0;
/* Change position of image appropriately */
if (crop->whole_stream) {
/* If cropping the whole stream, then this is the first frame. Position
it at (0,0). */
crop->left_off = x + gfi->left;
crop->right_off = y + gfi->top;
gfi->left = 0;
gfi->top = 0;
crop->whole_stream = 0;
} else {
gfi->left += x - crop->left_off;
gfi->top += y - crop->right_off;
}
} else {
/* Empty image */
w = h = 0;
img = 0;
}
Gif_DeleteArray(gfi->img);
gfi->img = img;
gfi->width = w;
gfi->height = h;
return gfi->img != 0;
}
/*****
* flip and rotate
**/
void
flip_image(Gif_Image *gfi, int screen_width, int screen_height, int is_vert)
{
int x, y;
int width = gfi->width;
int height = gfi->height;
byte **img = gfi->img;
/* horizontal flips */
if (!is_vert) {
byte *buffer = Gif_NewArray(byte, width);
byte *trav;
for (y = 0; y < height; y++) {
memcpy(buffer, img[y], width);
trav = img[y] + width - 1;
for (x = 0; x < width; x++)
*trav-- = buffer[x];
}
gfi->left = screen_width - (gfi->left + width);
Gif_DeleteArray(buffer);
}
/* vertical flips */
if (is_vert) {
byte **buffer = Gif_NewArray(byte *, height);
memcpy(buffer, img, height * sizeof(byte *));
for (y = 0; y < height; y++)
img[y] = buffer[height - y - 1];
gfi->top = screen_height - (gfi->top + height);
Gif_DeleteArray(buffer);
}
}
void
rotate_image(Gif_Image *gfi, int screen_width, int screen_height, int rotation)
{
int x, y;
int width = gfi->width;
int height = gfi->height;
byte **img = gfi->img;
byte *new_data = Gif_NewArray(byte, width * height);
byte *trav = new_data;
/* this function can only rotate by 90 or 270 degrees */
assert(rotation == 1 || rotation == 3);
if (rotation == 1) {
for (x = 0; x < width; x++)
for (y = height - 1; y >= 0; y--)
*trav++ = img[y][x];
x = gfi->left;
gfi->left = screen_height - (gfi->top + height);
gfi->top = x;
} else {
for (x = width - 1; x >= 0; x--)
for (y = 0; y < height; y++)
*trav++ = img[y][x];
y = gfi->top;
gfi->top = screen_width - (gfi->left + width);
gfi->left = y;
}
Gif_ReleaseUncompressedImage(gfi);
gfi->width = height;
gfi->height = width;
Gif_SetUncompressedImage(gfi, new_data, Gif_DeleteArrayFunc, 0);
}
/*****
* scale
**/
#define SCALE(d) ((d) << 10)
#define UNSCALE(d) ((d) >> 10)
#define SCALE_FACTOR SCALE(1)
void
scale_image(Gif_Stream *gfs, Gif_Image *gfi, double xfactor, double yfactor)
{
byte *new_data;
int new_left, new_top, new_right, new_bottom, new_width, new_height;
int i, j, new_x, new_y;
int scaled_xstep, scaled_ystep, scaled_new_x, scaled_new_y;
/* Fri 9 Jan 1999: Fix problem with resizing animated GIFs: we scaled from
left edge of the *subimage* to right edge of the subimage, causing
consistency problems when several subimages overlap. Solution: always use
scale factors relating to the *whole image* (the screen size). */
/* use fixed-point arithmetic */
scaled_xstep = (int)(SCALE_FACTOR * xfactor + 0.5);
scaled_ystep = (int)(SCALE_FACTOR * yfactor + 0.5);
/* calculate new width and height based on the four edges (left, right, top,
bottom). This is better than simply multiplying the width and height by
the scale factors because it avoids roundoff inconsistencies between
frames on animated GIFs. Don't allow 0-width or 0-height images; GIF
doesn't support them well. */
new_left = UNSCALE(scaled_xstep * gfi->left);
new_top = UNSCALE(scaled_ystep * gfi->top);
new_right = UNSCALE(scaled_xstep * (gfi->left + gfi->width));
new_bottom = UNSCALE(scaled_ystep * (gfi->top + gfi->height));
new_width = new_right - new_left;
new_height = new_bottom - new_top;
if (new_width <= 0) new_width = 1, new_right = new_left + 1;
if (new_height <= 0) new_height = 1, new_bottom = new_top + 1;
if (new_width > UNSCALE(INT_MAX) || new_height > UNSCALE(INT_MAX))
fatal_error("new image size is too big for me to handle");
assert(gfi->img);
new_data = Gif_NewArray(byte, new_width * new_height);
new_y = new_top;
scaled_new_y = scaled_ystep * gfi->top;
for (j = 0; j < gfi->height; j++) {
byte *in_line = gfi->img[j];
byte *out_data;
int x_delta, y_delta, yinc;
scaled_new_y += scaled_ystep;
/* account for images which should've had 0 height but don't */
if (j == gfi->height - 1) scaled_new_y = SCALE(new_bottom);
if (scaled_new_y < SCALE(new_y + 1)) continue;
y_delta = UNSCALE(scaled_new_y - SCALE(new_y));
new_x = new_left;
scaled_new_x = scaled_xstep * gfi->left;
out_data = &new_data[(new_y - new_top) * new_width + (new_x - new_left)];
for (i = 0; i < gfi->width; i++) {
scaled_new_x += scaled_xstep;
/* account for images which should've had 0 width but don't */
if (i == gfi->width - 1) scaled_new_x = SCALE(new_right);
x_delta = UNSCALE(scaled_new_x - SCALE(new_x));
for (; x_delta > 0; new_x++, x_delta--, out_data++)
for (yinc = 0; yinc < y_delta; yinc++)
out_data[yinc * new_width] = in_line[i];
}
new_y += y_delta;
}
Gif_ReleaseUncompressedImage(gfi);
Gif_ReleaseCompressedImage(gfi);
gfi->width = new_width;
gfi->height = new_height;
gfi->left = UNSCALE(scaled_xstep * gfi->left);
gfi->top = UNSCALE(scaled_ystep * gfi->top);
Gif_SetUncompressedImage(gfi, new_data, Gif_DeleteArrayFunc, 0);
}
void
resize_stream(Gif_Stream *gfs, int new_width, int new_height)
{
double xfactor, yfactor;
int i;
if (new_width <= 0)
new_width = (int)(((double)gfs->screen_width / gfs->screen_height) * new_height);
if (new_height <= 0)
new_height = (int)(((double)gfs->screen_height / gfs->screen_width) * new_width);
Gif_CalculateScreenSize(gfs, 0);
xfactor = (double)new_width / gfs->screen_width;
yfactor = (double)new_height / gfs->screen_height;
for (i = 0; i < gfs->nimages; i++)
scale_image(gfs, gfs->images[i], xfactor, yfactor);
gfs->screen_width = new_width;
gfs->screen_height = new_height;
}
syntax highlighted by Code2HTML, v. 0.9.1