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