Make round pen buttons.
[colorize.git] / gui / image.c
1 #include <gtk/gtk.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <math.h>
6 #include "main.h"
7 #include "../src/ppm.h"
8 #include "../src/mark.h"
9 #include "../src/yuv.h"
10 #include "image.h"
11 #include "palette.h"
12 #include "timeline.h"
13 #include "../src/dir_seperator.h"
14
15 /* currently loaded image */
16 #define UNDO_MAX 20
17 GdkPixbuf *img_pixbuf = NULL;
18 int img_width, img_height;
19 int copy_width, copy_height;
20 char img_name[256];
21 unsigned short *img_grey_buffer = NULL;
22 unsigned char *img_mark_buffer = NULL, *img_mark_buffer_undo[UNDO_MAX], *img_mark_buffer_copy;
23 int button_down_x_undo[UNDO_MAX], button_down_y_undo[UNDO_MAX];
24 int undo_current, undo_num;
25 int img_scale_x = 16, img_scale_y = 16;
26 int anything_modified;
27 extern int button_down_x, button_down_y;
28
29 /* load image and create pixbuf */
30 void create_image(const char *filename, int resize)
31 {
32         int rc;
33         static char imgfile[256];
34
35         destroy_image();
36
37         anything_modified = 0;
38
39         strcpy(img_name, filename);
40         if (rendered) {
41                 const char *p, *q;
42                 p = filename;
43                 while((q = strchr(p, DIR_SEPERATOR)))
44                 p = q + 1;
45                 strcpy(imgfile, filename);
46                 imgfile[p - filename] = '\0';
47                 strcat(imgfile, "colorized_");
48                 strcat(imgfile, p);
49         } else
50                 strcpy(imgfile, filename);
51         rc = load_img(-1, &img_grey_buffer, &img_width, &img_height, imgfile, 0);
52         if (rc) {
53                 img_grey_buffer = NULL;
54                 if (!rendered)
55                         printerror("Failed to load grey image '%s'", imgfile);
56                 else {
57                         img_width = 16;
58                         img_height = 16;
59                         goto no_mark;
60                 }
61                 return;
62         }
63         img_mark_buffer = malloc(img_width*img_height);
64         if (!img_mark_buffer) {
65                 free(img_grey_buffer);
66                 img_grey_buffer = NULL;
67                 return;
68         }
69         memset(img_mark_buffer, 0, img_width*img_height);
70         undo_current = undo_num = 0;
71         memset(img_mark_buffer_undo, 0, sizeof(img_mark_buffer_undo));
72         if (frame_list) {
73                 if (load_marked(img_mark_buffer, img_width, img_height, frame_list[timeline_selected].filename) == 0)
74                         frame_list[timeline_selected].marked = 1;
75                 else
76                         frame_list[timeline_selected].marked = 0;
77         }
78
79 no_mark:
80         /* create pixbuf */
81         create_or_reset_pixbuf(resize);
82 }
83
84 void create_or_reset_pixbuf(int resize)
85 {
86         int w, h, dw = 0, dh = 0;
87         GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(main_window));
88         GdkRectangle max;
89
90         if (resize && screen) {
91                 /* process GTK window event to have correct size */
92                 while (gtk_events_pending())
93                         gtk_main_iteration();
94
95                 /* calculate the maximum windows size, so that the border will not exceed the screen size */
96                 gdk_window_get_frame_extents(gtk_widget_get_window(main_window), &max);
97                 max.width -= main_window->allocation.width;
98                 max.height -= main_window->allocation.height;
99                 if (max.width < 0)
100                         max.width = 0;
101                 if (max.height < 0)
102                         max.height = 0;
103                 max.width = gdk_screen_get_width(screen)-max.width;
104                 max.height = gdk_screen_get_height(screen)-max.height;
105
106 try_smaller:
107                 w = img_scroll->allocation.width-25;
108                 h = img_scroll->allocation.height-25;
109                 if (w < img_width*img_scale_x/16) {
110                         w = main_window->allocation.width - w + img_width*img_scale_x/16;
111                         if (w > max.width) {
112                                 if (img_scale_x > 4) {
113                                         int aspect = img_scale_y / img_scale_x;
114                                         img_scale_x -= 4;
115                                         img_scale_y = img_scale_x * aspect;
116                                         goto try_smaller;
117                                 }
118                                 w = max.width;
119                         }
120                         dw = w - main_window->allocation.width;
121                 }
122                 if (h < img_height*img_scale_y/16) {
123                         h = main_window->allocation.height - h + img_height*img_scale_y/16;
124                         if (h > max.height) {
125                                 if (img_scale_x > 4) {
126                                         int aspect = img_scale_y / img_scale_x;
127                                         img_scale_x -= 4;
128                                         img_scale_y = img_scale_x * aspect;
129                                         goto try_smaller;
130                                 }
131                                 h = max.height;
132                         }
133                         dh = h - main_window->allocation.height;
134                 }
135 //              printf("%d %d\n", gdk_screen_get_width(screen), gdk_screen_get_height(screen));
136                 if (dw || dh)
137                         gtk_window_resize(GTK_WINDOW(main_window), main_window->allocation.width+dw, main_window->allocation.height+dh);
138         }
139
140         if (img_pixbuf) {
141                 g_object_unref(img_pixbuf);
142                 img_pixbuf = NULL;
143         }
144         img_pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, img_width*img_scale_x/16, img_height*img_scale_y/16);
145
146         gtk_drawing_area_size(GTK_DRAWING_AREA(img_drawing_area), img_width*img_scale_x/16, img_height*img_scale_y/16);
147
148         /* label */
149         timeline_show_name(timeline_selected);
150 }
151
152 /* free image */
153 void destroy_image(void)
154 {
155         int i;
156
157         if (!img_pixbuf)
158                 return;
159
160         g_object_unref(img_pixbuf);
161         img_pixbuf = NULL;
162
163         free(img_grey_buffer);
164         img_grey_buffer = NULL;
165         free(img_mark_buffer);
166         img_mark_buffer = NULL;
167         for (i = 0; i < UNDO_MAX; i++) {
168                 if (img_mark_buffer_undo[i]) {
169                         free(img_mark_buffer_undo[i]);
170                         img_mark_buffer_undo[i] = NULL;
171                 }
172         }
173 }
174
175 /* draw (area) of pixbuf */
176 void draw_image(int x, int y, int w, int h)
177 {
178         GdkDrawable *draw = gtk_widget_get_window(img_drawing_area);
179         int window_width, window_height, x_offset, y_offset;
180         gdk_drawable_get_size (draw, &window_width, &window_height);
181         double _r, _g, _b, _y, _u, _v, u_palette, v_palette;
182         int cr, cg, cb;
183         int _c, preview_asis = 0;
184
185         if (w < 0)
186                 w = window_width;
187         if (h < 0)
188                 h = window_height;
189
190         guchar *data;
191         unsigned char compose[w*3];
192         int i, j, rs;
193         unsigned char c;
194
195         if (!img_pixbuf)
196                 return;
197         data = gdk_pixbuf_get_pixels(img_pixbuf);
198         rs = gdk_pixbuf_get_rowstride(img_pixbuf);
199
200         /* this should not happen */
201         if (window_width < img_width*img_scale_x/16)
202                 window_width = img_width*img_scale_x/16;
203         if (window_height < img_height*img_scale_y/16)
204                 window_height = img_height*img_scale_y/16;
205
206         /* we need to calculate an offset, since the drawing area is now larger */
207         x_offset = (window_width - img_width*img_scale_x/16) / 2;
208         y_offset = (window_height - img_height*img_scale_y/16) / 2;
209
210         /* clip to window size */
211         if (x < 0)
212                 x = 0;
213         if (x >= window_width)
214                 x = window_width - 1;
215         if (y < 0)
216                 y = 0;
217         if (y >= window_height)
218                 y = window_height - 1;
219         if (x + w > window_width)
220                 w = window_width - x;
221         if (w <= 0)
222                 return;
223         if (y + h > window_height)
224                 h = window_height - y;
225         if (h <= 0)
226                 return;
227
228         /* draw top border */
229         if (y < y_offset) {
230                 int temp = (h+y >= y_offset) ? y_offset-y : h; /* height */
231                 gdk_draw_rectangle(draw, img_drawing_area->style->bg_gc[GTK_WIDGET_STATE(img_drawing_area)], TRUE, x, y, w, (h+y >= y_offset) ? y_offset-y : h);
232                 h -= temp;
233                 y += temp;
234                 if (h <= 0)
235                         return;
236         }
237
238         /* draw bottom border */
239         if (y+h >= y_offset+img_height*img_scale_y/16) {
240                 int temp = (y < y_offset+img_height*img_scale_y/16) ? y_offset+img_height*img_scale_y/16 : y; /* y start */
241                 gdk_draw_rectangle(draw, img_drawing_area->style->bg_gc[GTK_WIDGET_STATE(img_drawing_area)], TRUE, x, temp, w, h - (temp-y));
242                 h = temp-y;
243                 if (h <= 0)
244                         return;
245         }
246
247         /* draw left border */
248         if (x < x_offset) {
249                 int temp = (w+x >= x_offset) ? x_offset-x : w; /* width */
250                 gdk_draw_rectangle(draw, img_drawing_area->style->bg_gc[GTK_WIDGET_STATE(img_drawing_area)], TRUE, x, y, (w+x >= x_offset) ? x_offset-x : w, h);
251                 w -= temp;
252                 x += temp;
253                 if (w <= 0)
254                         return;
255         }
256
257         /* draw right border */
258         if (x+w >= x_offset+img_width*img_scale_x/16) {
259                 int temp = (x < x_offset+img_width*img_scale_x/16) ? x_offset+img_width*img_scale_x/16 : x; /* x start **/
260                 gdk_draw_rectangle(draw, img_drawing_area->style->bg_gc[GTK_WIDGET_STATE(img_drawing_area)], TRUE, temp, y, w - (temp-x), h);
261                 w = temp-x;
262                 if (w <= 0)
263                         return;
264         }
265
266         x -= x_offset;
267         y -= y_offset;
268
269         if (img_grey_buffer) {
270                 if (preview) {
271                         rgb2yuv_pixle(mark_palette[mark_selected].r / 255.0F, mark_palette[mark_selected].g / 255.0F, mark_palette[mark_selected].b / 255.0F, &_y, &u_palette, &v_palette);
272                         /* check for white color (no change) */
273                         if (mark_palette[mark_selected].r == 255 && mark_palette[mark_selected].g == 255 && mark_palette[mark_selected].r == 255)
274                                 preview_asis = 1;
275                 }
276                 /* compose image (segment) line by line */
277                 for (i = 0; i < h; i++) {
278                         for (j = 0; j < w; j++) {
279                                 if (preview && !rendered && !flowview) {
280                                         /* apply selected color from palette to all pixles */
281                                         _r = img_grey_buffer[((j+x)*16/img_scale_x+img_width*((i+y)*16/img_scale_y))*3] / 65535.0F;
282                                         _g = img_grey_buffer[((j+x)*16/img_scale_x+img_width*((i+y)*16/img_scale_y))*3+1] / 65535.0F;
283                                         _b = img_grey_buffer[((j+x)*16/img_scale_x+img_width*((i+y)*16/img_scale_y))*3+2] / 65535.0F;
284                                         rgb2yuv_pixle(_r, _g, _b, &_y, &_u, &_v);
285                                         _y = (_y - 0.5) * mark_palette[mark_selected].contrast + 0.5;
286                                         _y += mark_palette[mark_selected].bright;
287                                         if (_y < 0)
288                                                 _y = 0;
289                                         if (_y > 1)
290                                                 _y = 1;
291                                         if (preview_asis)
292                                                 yuv2rgb_pixle(_y, _u, _v, &_r, &_g, &_b);
293                                         else
294                                                 yuv2rgb_pixle(_y, u_palette, v_palette, &_r, &_g, &_b);
295                                         _c = _r * 255.0F;
296                                         if (_c < 0)
297                                                 _c = 0;
298                                         else if (_c > 255)
299                                                 _c = 255;
300                                         compose[j*3] = _c;
301                                                 _c = _g * 255.0F;
302                                         if (_c < 0)
303                                                 _c = 0;
304                                         else if (_c > 255)
305                                                 _c = 255;
306                                         compose[j*3+1] = _c;
307                                                 _c = _b * 255.0F;
308                                         if (_c < 0)
309                                                 _c = 0;
310                                         else if (_c > 255)
311                                                 _c = 255;
312                                         compose[j*3+2] = _c;
313
314                                 } else {
315                                         compose[j*3] = img_grey_buffer[((j+x)*16/img_scale_x+img_width*((i+y)*16/img_scale_y))*3] >> 8;
316                                         compose[j*3+1] = img_grey_buffer[((j+x)*16/img_scale_x+img_width*((i+y)*16/img_scale_y))*3+1] >> 8;
317                                         compose[j*3+2] = img_grey_buffer[((j+x)*16/img_scale_x+img_width*((i+y)*16/img_scale_y))*3+2] >> 8;
318                                 }
319                                 c = img_mark_buffer[(j+x)*16/img_scale_x+img_width*((i+y)*16/img_scale_y)];
320                                 if (c > 0 && !flowview && !(rendered && preview)) {
321                                         if (highlight) {
322                                                 if (c-1 == mark_selected) {
323                                                         compose[j*3] = 255;
324                                                         compose[j*3+1] = 0;
325                                                         compose[j*3+2] = 0;
326                                                 } else {
327                                                         compose[j*3] = 0;
328                                                         compose[j*3+1] = 0;
329                                                         compose[j*3+2] = 128;
330                                                 }
331                                         } else {
332                                                 cr = mark_palette[c-1].r;
333                                                 cg = mark_palette[c-1].g;
334                                                 cb = mark_palette[c-1].b;
335                                                 if (cr == 255 && cg == 255 && cb == 255) {
336                                                         compose[j*3] = compose[j*3+1] = compose[j*3+2] = ((((i+y)>>3)&1) == (((j+x)>>3)&1)) ? 255 : 192;
337                                                 } else
338                                                 {
339                                                         compose[j*3] = cr;
340                                                         compose[j*3+1] = cg;
341                                                         compose[j*3+2] = cb;
342                                                 }
343                                         }
344                                 }
345                         }
346                         memcpy(data + rs*(i+y) + x*3, compose, w*3);
347                 }
348         } else {
349                 for (i = 0; i < h; i++)
350                         memset(data + rs*(i+y) + x*3, 0, w*3);
351         }
352
353         gdk_draw_pixbuf(draw, NULL, img_pixbuf, x, y, x+x_offset, y+y_offset, w, h, GDK_RGB_DITHER_NONE, 0, 0);
354
355 }
356
357 static const char *brushs[] = {
358         NULL,
359         "#",
360         " # "
361         "###"
362         " # ",
363         " ### "
364         "#####"
365         "#####"
366         "#####"
367         " ### ",
368         NULL,
369         "  #####  "
370         " ####### "
371         "#########"
372         "#########"
373         "#########"
374         "#########"
375         "#########"
376         " ####### "
377         "  #####  ",
378         "    ###    "
379         "  #######  "
380         " ######### "
381         " ######### "
382         "###########"
383         "###########"
384         "###########"
385         " ######### "
386         " ######### "
387         "  #######  "
388         "    ###    ",
389         NULL,
390         NULL,
391         NULL,
392         "      #######      "
393         "    ###########    "
394         "   #############   "
395         "  ###############  "
396         " ################# "
397         " ################# "
398         "###################"
399         "###################"
400         "###################"
401         "###################"
402         "###################"
403         "###################"
404         "###################"
405         " ################# "
406         " ################# "
407         "  ###############  "
408         "   #############   "
409         "    ###########    "
410         "      #######      ",
411 };
412
413 /* plot brush on mark buffer */
414 void paint_brush(int x, int y, int size, int paint)
415 {
416         GdkDrawable *draw = gtk_widget_get_window(img_drawing_area);
417         int window_width, window_height;
418         gdk_drawable_get_size (draw, &window_width, &window_height);
419         const char *mask;
420
421         /* we need to calculate an offset, since the drawing area is now larger */
422         x -= (window_width - img_width*img_scale_x/16) / 2;
423         y -= (window_height - img_height*img_scale_y/16) / 2;
424
425         x = x*16/img_scale_x;
426         y = y*16/img_scale_y;
427
428         if (!img_mark_buffer || flowview)
429                 return;
430
431         int x1 = x - size + 1;
432         int x2 = x + size - 1;
433         int y1 = y - size + 1;
434         int y2 = y + size - 1;
435         int i, j;
436
437         if (x1 >= img_width)
438                 return;
439         if (x1 < 0)
440                 x1 = 0;
441         if (x2 >= img_width)
442                 x2 = img_width - 1;
443         if (x2 < 0)
444                 return;
445         if (y1 >= img_height)
446                 return;
447         if (y1 < 0)
448                 y1 = 0;
449         if (y2 >= img_height)
450                 y2 = img_height - 1;
451         if (y2 < 0)
452                 return;
453
454         /* only paint color, if paint is set */
455         if (paint)
456                 paint = mark_selected + 1;
457
458         mask = brushs[size];
459         for (i = y1; i <= y2; i++) {
460                 for (j = x1; j <= x2; j++) {
461                         if (*mask++ == '#')
462                                 img_mark_buffer[j+img_width*i] = paint;
463                 }
464         }
465 }
466
467 void erase_mark(int index)
468 {
469         int i;
470
471         if (!img_mark_buffer)
472                 return;
473
474         /* no index, erase all */
475         if (!index) {
476                 memset(img_mark_buffer, 0, img_width * img_height);
477                 return;
478         }
479
480         /* only erase indexed color */
481         for (i = 0; i < img_width * img_height; i++) {
482                 if (img_mark_buffer[i] == index)
483                         img_mark_buffer[i] = 0;
484         }
485 }
486
487 void move_mark(int x, int y)
488 {
489         int i, j;
490         unsigned char *src;
491
492         if (!img_mark_buffer || undo_current == 0)
493                 return;
494
495         src = img_mark_buffer_undo[undo_current-1];
496         if (!src)
497                 return;
498
499         if (y < 0 || (y == 0 && x < 0)) {
500                 /* move up in memory */
501                 for (j = 0; j < img_height; j++) {
502                         for (i = 0; i < img_width; i++) {
503                                 if (i < x || i >= img_width+x || j < y || j >= img_height+y)
504                                         img_mark_buffer[j*img_width+i] = 0;
505                                 else
506                                         img_mark_buffer[j*img_width+i] = src[(j-y)*img_width+(i-x)];
507                         }
508                 }
509         } else {
510                 /* move down in memory */
511                 for (j = img_height-1; j >= 0; j--) {
512                         for (i = img_width-1; i >= 0; i--) {
513                                 if (i < x || i >= img_width+x || j < y || j >= img_height+y)
514                                         img_mark_buffer[j*img_width+i] = 0;
515                                 else
516                                         img_mark_buffer[j*img_width+i] = src[(j-y)*img_width+(i-x)];
517                         }
518                 }
519         }
520 }
521
522 void copy_mark_to_undo(void)
523 {
524         anything_modified = 1;
525
526         if (!img_mark_buffer)
527                 return;
528
529         if (undo_current < UNDO_MAX) {
530                 /* allocate new undobuffer and append */
531                 if (!img_mark_buffer_undo[undo_current])
532                         img_mark_buffer_undo[undo_current] = malloc(img_width*img_height);
533                 if (!img_mark_buffer_undo[undo_current])
534                         return;
535                 undo_current++;
536         } else {
537                 /* shift and use oldest undobuffer */
538                 unsigned char *temp = img_mark_buffer_undo[0];
539                 int i;
540                 for (i = 0; i < UNDO_MAX - 1; i++)
541                         img_mark_buffer_undo[i] = img_mark_buffer_undo[i+1];
542                 img_mark_buffer_undo[UNDO_MAX - 1] = temp;
543         }
544         undo_num = undo_current;
545 //printf("storing undo #%d\n", undo_current-1);
546         memcpy(img_mark_buffer_undo[undo_current-1], img_mark_buffer, img_width*img_height);
547         button_down_x_undo[undo_current-1] = button_down_x;
548         button_down_y_undo[undo_current-1] = button_down_y;
549 }
550
551 void copy_undo_to_mark(int redo)
552 {
553         if (!img_mark_buffer)
554                 return;
555
556 //printf("num is %d\n", undo_num);
557         if (!redo) {
558                 /* nothing to undo */
559                 if (undo_current == 0)
560                         return;
561
562                 /* if we are at the end of the history, we need to add the current image to undo history */
563                 if (undo_current == undo_num) {
564                         copy_mark_to_undo();
565                         undo_current--;
566                 }
567                 undo_current--;
568 //printf("restore undo #%d\n", undo_current);
569         } else {
570                 /* nothing to redo */
571                 if (undo_current >= undo_num-1)
572                         return;
573                 undo_current++;
574 //printf("restore undo #%d\n", undo_current);
575         }
576         memcpy(img_mark_buffer, img_mark_buffer_undo[undo_current], img_width*img_height);
577         button_down_x = button_down_x_undo[undo_current];
578         button_down_y = button_down_y_undo[undo_current];
579 }
580
581 void copy_color(int index)
582 {
583         int i;
584
585         if (img_mark_buffer_copy)
586                 free(img_mark_buffer_copy);
587         img_mark_buffer_copy = malloc(img_width*img_height);
588         if (!img_mark_buffer_copy)
589                 return;
590
591         memcpy(img_mark_buffer_copy, img_mark_buffer, img_width*img_height);
592         copy_width = img_width;
593         copy_height = img_height;
594
595         /* erase indexed color except given index */
596         for (i = 0; i < img_width * img_height; i++) {
597                 if (index && img_mark_buffer_copy[i] != index)
598                         img_mark_buffer_copy[i] = 0;
599         }
600 }
601
602 void paste_color(void)
603 {
604         int i;
605
606         if (!img_mark_buffer_copy)
607                 return;
608
609         if (copy_width != img_width || copy_height != img_height) {
610                 printerror("Image in copy buffer has different dimenstions.");
611                 return;
612         }
613
614         /* paste indexed pixles */
615         for (i = 0; i < img_width * img_height; i++) {
616                 if (img_mark_buffer_copy[i])
617                         img_mark_buffer[i] = img_mark_buffer_copy[i];
618         }
619 }
620