1 /* xform.c - Image transformation functions for gifsicle.
2    Copyright (C) 1997-9 Eddie Kohler, eddietwo@lcs.mit.edu
3    This file is part of gifsicle.
4 
5    Gifsicle is free software. It is distributed under the GNU Public License,
6    version 2 or later; you can copy, distribute, or alter it at will, as long
7    as this notice is kept intact and this source code is made available. There
8    is no warranty, express or implied. */
9 
10 #include "config.h"
11 #include "gifsicle.h"
12 #include <stdio.h>
13 #include <string.h>
14 #include <ctype.h>
15 #include <assert.h>
16 #include <errno.h>
17 #include <limits.h>
18 
19 
20 /******
21  * color transforms
22  **/
23 
24 Gt_ColorTransform *
append_color_transform(Gt_ColorTransform * list,color_transform_func func,void * data)25 append_color_transform(Gt_ColorTransform *list,
26 		       color_transform_func func, void *data)
27 {
28   Gt_ColorTransform *trav;
29   Gt_ColorTransform *xform = Gif_New(Gt_ColorTransform);
30   xform->next = 0;
31   xform->func = func;
32   xform->data = data;
33 
34   for (trav = list; trav && trav->next; trav = trav->next)
35     ;
36   if (trav) {
37     trav->next = xform;
38     return list;
39   } else
40     return xform;
41 }
42 
43 Gt_ColorTransform *
delete_color_transforms(Gt_ColorTransform * list,color_transform_func func)44 delete_color_transforms(Gt_ColorTransform *list, color_transform_func func)
45 {
46   Gt_ColorTransform *prev = 0, *trav = list;
47   while (trav) {
48     Gt_ColorTransform *next = trav->next;
49     if (trav->func == func) {
50       if (prev) prev->next = next;
51       else list = next;
52       Gif_Delete(trav);
53     } else
54       prev = trav;
55     trav = next;
56   }
57   return list;
58 }
59 
60 void
apply_color_transforms(Gt_ColorTransform * list,Gif_Stream * gfs)61 apply_color_transforms(Gt_ColorTransform *list, Gif_Stream *gfs)
62 {
63   int i;
64   Gt_ColorTransform *xform;
65   for (xform = list; xform; xform = xform->next) {
66     if (gfs->global)
67       xform->func(gfs->global, xform->data);
68     for (i = 0; i < gfs->nimages; i++)
69       if (gfs->images[i]->local)
70 	xform->func(gfs->images[i]->local, xform->data);
71   }
72 }
73 
74 
75 typedef struct Gt_ColorChange {
76   struct Gt_ColorChange *next;
77   Gif_Color old_color;
78   Gif_Color new_color;
79 } Gt_ColorChange;
80 
81 void
color_change_transformer(Gif_Colormap * gfcm,void * thunk)82 color_change_transformer(Gif_Colormap *gfcm, void *thunk)
83 {
84   int i, have;
85   Gt_ColorChange *first_change = (Gt_ColorChange *)thunk;
86   Gt_ColorChange *change;
87 
88   /* change colors named by color */
89   for (i = 0; i < gfcm->ncol; i++)
90     for (change = first_change; change; change = change->next) {
91       if (!change->old_color.haspixel)
92 	have = GIF_COLOREQ(&gfcm->col[i], &change->old_color);
93       else
94 	have = change->old_color.pixel == i;
95 
96       if (have) {
97 	gfcm->col[i] = change->new_color;
98 	break;			/* ignore remaining color changes */
99       }
100     }
101 }
102 
103 Gt_ColorTransform *
append_color_change(Gt_ColorTransform * list,Gif_Color old_color,Gif_Color new_color)104 append_color_change(Gt_ColorTransform *list,
105 		    Gif_Color old_color, Gif_Color new_color)
106 {
107   Gt_ColorTransform *xform;
108   Gt_ColorChange *change = Gif_New(Gt_ColorChange);
109   change->old_color = old_color;
110   change->new_color = new_color;
111   change->next = 0;
112 
113   for (xform = list; xform && xform->next; xform = xform->next)
114     ;
115   if (!xform || xform->func != &color_change_transformer)
116     return append_color_transform(list, &color_change_transformer, change);
117   else {
118     Gt_ColorChange *prev = (Gt_ColorChange *)(xform->data);
119     while (prev->next) prev = prev->next;
120     prev->next = change;
121     return list;
122   }
123 }
124 
125 
126 void
pipe_color_transformer(Gif_Colormap * gfcm,void * thunk)127 pipe_color_transformer(Gif_Colormap *gfcm, void *thunk)
128 {
129   int i, status;
130   FILE *f;
131   Gif_Color *col = gfcm->col;
132   Gif_Colormap *new_cm = 0;
133   char *command = (char *)thunk;
134   char *tmp_file = tmpnam(0);
135   char *new_command;
136 
137   if (!tmp_file)
138     fatal_error("can't create temporary file!");
139 
140   new_command = Gif_NewArray(char, strlen(command) + strlen(tmp_file) + 4);
141   sprintf(new_command, "%s  >%s", command, tmp_file);
142   f = popen(new_command, "w");
143   if (!f)
144     fatal_error("can't run color transformation command: %s", strerror(errno));
145   Gif_DeleteArray(new_command);
146 
147   for (i = 0; i < gfcm->ncol; i++)
148     fprintf(f, "%d %d %d\n", col[i].red, col[i].green, col[i].blue);
149 
150   errno = 0;
151   status = pclose(f);
152   if (status < 0) {
153     error("color transformation error: %s", strerror(errno));
154     goto done;
155   } else if (status > 0) {
156     error("color transformation command failed");
157     goto done;
158   }
159 
160   f = fopen(tmp_file, "r");
161   if (!f || feof(f)) {
162     error("color transformation command generated no output", command);
163     if (f) fclose(f);
164     goto done;
165   }
166   new_cm = read_colormap_file("<color transformation>", f);
167   fclose(f);
168 
169   if (new_cm) {
170     int nc = new_cm->ncol;
171     if (nc < gfcm->ncol) {
172       nc = gfcm->ncol;
173       warning("too few colors in color transformation results");
174     } else if (nc > gfcm->ncol)
175       warning("too many colors in color transformation results");
176     for (i = 0; i < nc; i++)
177       col[i] = new_cm->col[i];
178   }
179 
180  done:
181   remove(tmp_file);
182   Gif_DeleteColormap(new_cm);
183 }
184 
185 
186 
187 /*****
188  * crop image; returns true if the image exists
189  **/
190 
191 int
crop_image(Gif_Image * gfi,Gt_Crop * crop)192 crop_image(Gif_Image *gfi, Gt_Crop *crop)
193 {
194   int x, y, w, h, j;
195   byte **img;
196 
197   if (!crop->ready) {
198     crop->x = crop->spec_x + gfi->left;
199     crop->y = crop->spec_y + gfi->top;
200     crop->w = crop->spec_w <= 0 ? gfi->width + crop->spec_w : crop->spec_w;
201     crop->h = crop->spec_h <= 0 ? gfi->height + crop->spec_h : crop->spec_h;
202     if (crop->x < 0 || crop->y < 0 || crop->w <= 0 || crop->h <= 0
203 	|| crop->x + crop->w > gfi->width
204 	|| crop->y + crop->h > gfi->height) {
205       error("cropping dimensions don't fit image");
206       crop->ready = 2;
207     } else
208       crop->ready = 1;
209   }
210   if (crop->ready == 2)
211     return 1;
212 
213   x = crop->x - gfi->left;
214   y = crop->y - gfi->top;
215   w = crop->w;
216   h = crop->h;
217 
218   /* Check that the rectangle actually intersects with the image. */
219   if (x < 0) w += x, x = 0;
220   if (y < 0) h += y, y = 0;
221   if (x + w > gfi->width) w = gfi->width - x;
222   if (y + h > gfi->height) h = gfi->height - y;
223 
224   if (w > 0 && h > 0) {
225     img = Gif_NewArray(byte *, h + 1);
226     for (j = 0; j < h; j++)
227       img[j] = gfi->img[y + j] + x;
228     img[h] = 0;
229 
230     /* Change position of image appropriately */
231     if (crop->whole_stream) {
232       /* If cropping the whole stream, then this is the first frame. Position
233 	 it at (0,0). */
234       crop->left_off = x + gfi->left;
235       crop->right_off = y + gfi->top;
236       gfi->left = 0;
237       gfi->top = 0;
238       crop->whole_stream = 0;
239     } else {
240       gfi->left += x - crop->left_off;
241       gfi->top += y - crop->right_off;
242     }
243 
244   } else {
245     /* Empty image */
246     w = h = 0;
247     img = 0;
248   }
249 
250   Gif_DeleteArray(gfi->img);
251   gfi->img = img;
252   gfi->width = w;
253   gfi->height = h;
254   return gfi->img != 0;
255 }
256 
257 
258 /*****
259  * flip and rotate
260  **/
261 
262 void
flip_image(Gif_Image * gfi,int screen_width,int screen_height,int is_vert)263 flip_image(Gif_Image *gfi, int screen_width, int screen_height, int is_vert)
264 {
265   int x, y;
266   int width = gfi->width;
267   int height = gfi->height;
268   byte **img = gfi->img;
269 
270   /* horizontal flips */
271   if (!is_vert) {
272     byte *buffer = Gif_NewArray(byte, width);
273     byte *trav;
274     for (y = 0; y < height; y++) {
275       memcpy(buffer, img[y], width);
276       trav = img[y] + width - 1;
277       for (x = 0; x < width; x++)
278 	*trav-- = buffer[x];
279     }
280     gfi->left = screen_width - (gfi->left + width);
281     Gif_DeleteArray(buffer);
282   }
283 
284   /* vertical flips */
285   if (is_vert) {
286     byte **buffer = Gif_NewArray(byte *, height);
287     memcpy(buffer, img, height * sizeof(byte *));
288     for (y = 0; y < height; y++)
289       img[y] = buffer[height - y - 1];
290     gfi->top = screen_height - (gfi->top + height);
291     Gif_DeleteArray(buffer);
292   }
293 }
294 
295 void
rotate_image(Gif_Image * gfi,int screen_width,int screen_height,int rotation)296 rotate_image(Gif_Image *gfi, int screen_width, int screen_height, int rotation)
297 {
298   int x, y;
299   int width = gfi->width;
300   int height = gfi->height;
301   byte **img = gfi->img;
302   byte *new_data = Gif_NewArray(byte, width * height);
303   byte *trav = new_data;
304 
305   /* this function can only rotate by 90 or 270 degrees */
306   assert(rotation == 1 || rotation == 3);
307 
308   if (rotation == 1) {
309     for (x = 0; x < width; x++)
310       for (y = height - 1; y >= 0; y--)
311 	*trav++ = img[y][x];
312     x = gfi->left;
313     gfi->left = screen_height - (gfi->top + height);
314     gfi->top = x;
315 
316   } else {
317     for (x = width - 1; x >= 0; x--)
318       for (y = 0; y < height; y++)
319 	*trav++ = img[y][x];
320     y = gfi->top;
321     gfi->top = screen_width - (gfi->left + width);
322     gfi->left = y;
323   }
324 
325   Gif_ReleaseUncompressedImage(gfi);
326   gfi->width = height;
327   gfi->height = width;
328   Gif_SetUncompressedImage(gfi, new_data, Gif_DeleteArrayFunc, 0);
329 }
330 
331 
332 /*****
333  * scale
334  **/
335 
336 #define SCALE(d)	((d) << 10)
337 #define UNSCALE(d)	((d) >> 10)
338 #define SCALE_FACTOR	SCALE(1)
339 
340 void
scale_image(Gif_Stream * gfs,Gif_Image * gfi,double xfactor,double yfactor)341 scale_image(Gif_Stream *gfs, Gif_Image *gfi, double xfactor, double yfactor)
342 {
343   byte *new_data;
344   int new_left, new_top, new_right, new_bottom, new_width, new_height;
345 
346   int i, j, new_x, new_y;
347   int scaled_xstep, scaled_ystep, scaled_new_x, scaled_new_y;
348 
349   /* Fri 9 Jan 1999: Fix problem with resizing animated GIFs: we scaled from
350      left edge of the *subimage* to right edge of the subimage, causing
351      consistency problems when several subimages overlap. Solution: always use
352      scale factors relating to the *whole image* (the screen size). */
353 
354   /* use fixed-point arithmetic */
355   scaled_xstep = (int)(SCALE_FACTOR * xfactor + 0.5);
356   scaled_ystep = (int)(SCALE_FACTOR * yfactor + 0.5);
357 
358   /* calculate new width and height based on the four edges (left, right, top,
359      bottom). This is better than simply multiplying the width and height by
360      the scale factors because it avoids roundoff inconsistencies between
361      frames on animated GIFs. Don't allow 0-width or 0-height images; GIF
362      doesn't support them well. */
363   new_left = UNSCALE(scaled_xstep * gfi->left);
364   new_top = UNSCALE(scaled_ystep * gfi->top);
365   new_right = UNSCALE(scaled_xstep * (gfi->left + gfi->width));
366   new_bottom = UNSCALE(scaled_ystep * (gfi->top + gfi->height));
367 
368   new_width = new_right - new_left;
369   new_height = new_bottom - new_top;
370 
371   if (new_width <= 0) new_width = 1, new_right = new_left + 1;
372   if (new_height <= 0) new_height = 1, new_bottom = new_top + 1;
373   if (new_width > UNSCALE(INT_MAX) || new_height > UNSCALE(INT_MAX))
374     fatal_error("new image size is too big for me to handle");
375 
376   assert(gfi->img);
377   new_data = Gif_NewArray(byte, new_width * new_height);
378 
379   new_y = new_top;
380   scaled_new_y = scaled_ystep * gfi->top;
381 
382   for (j = 0; j < gfi->height; j++) {
383     byte *in_line = gfi->img[j];
384     byte *out_data;
385     int x_delta, y_delta, yinc;
386 
387     scaled_new_y += scaled_ystep;
388     /* account for images which should've had 0 height but don't */
389     if (j == gfi->height - 1) scaled_new_y = SCALE(new_bottom);
390 
391     if (scaled_new_y < SCALE(new_y + 1)) continue;
392     y_delta = UNSCALE(scaled_new_y - SCALE(new_y));
393 
394     new_x = new_left;
395     scaled_new_x = scaled_xstep * gfi->left;
396     out_data = &new_data[(new_y - new_top) * new_width + (new_x - new_left)];
397 
398     for (i = 0; i < gfi->width; i++) {
399       scaled_new_x += scaled_xstep;
400       /* account for images which should've had 0 width but don't */
401       if (i == gfi->width - 1) scaled_new_x = SCALE(new_right);
402 
403       x_delta = UNSCALE(scaled_new_x - SCALE(new_x));
404 
405       for (; x_delta > 0; new_x++, x_delta--, out_data++)
406 	for (yinc = 0; yinc < y_delta; yinc++)
407 	  out_data[yinc * new_width] = in_line[i];
408     }
409 
410     new_y += y_delta;
411   }
412 
413   Gif_ReleaseUncompressedImage(gfi);
414   Gif_ReleaseCompressedImage(gfi);
415   gfi->width = new_width;
416   gfi->height = new_height;
417   gfi->left = UNSCALE(scaled_xstep * gfi->left);
418   gfi->top = UNSCALE(scaled_ystep * gfi->top);
419   Gif_SetUncompressedImage(gfi, new_data, Gif_DeleteArrayFunc, 0);
420 }
421 
422 void
resize_stream(Gif_Stream * gfs,int new_width,int new_height)423 resize_stream(Gif_Stream *gfs, int new_width, int new_height)
424 {
425   double xfactor, yfactor;
426   int i;
427 
428   if (new_width <= 0)
429     new_width = (int)(((double)gfs->screen_width / gfs->screen_height) * new_height);
430   if (new_height <= 0)
431     new_height = (int)(((double)gfs->screen_height / gfs->screen_width) * new_width);
432 
433   Gif_CalculateScreenSize(gfs, 0);
434   xfactor = (double)new_width / gfs->screen_width;
435   yfactor = (double)new_height / gfs->screen_height;
436   for (i = 0; i < gfs->nimages; i++)
437     scale_image(gfs, gfs->images[i], xfactor, yfactor);
438 
439   gfs->screen_width = new_width;
440   gfs->screen_height = new_height;
441 }
442