/* 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 #include #include #include #include #include /****** * 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("", 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; }