1 /* Imagedisplay widget code ... display entire image, place this widget in a
2 * scrolledwindow to get clipping/scrolling behaviour.
3 */
4
5 /*
6
7 Copyright (C) 1991-2003 The National Gallery
8
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License along
20 with this program; if not, write to the Free Software Foundation, Inc.,
21 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22
23 */
24
25 /*
26
27 These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
28
29 */
30
31 /*
32 #define DEBUG
33 */
34
35 /* Trace painting actions
36 #define DEBUG_PAINT
37 */
38
39 /*
40 #define DEBUG_GEO
41 */
42
43 #include "ip.h"
44
45 enum {
46 SIG_AREA_CHANGED, /* xywh area changed, canvas cods */
47 SIG_LAST
48 };
49
50 static GtkDrawingAreaClass *parent_class = NULL;
51
52 static guint imagedisplay_signals[SIG_LAST] = { 0 };
53
54 /* Handy!
55 */
56 void
imagedisplay_queue_draw_area(Imagedisplay * id,Rect * area)57 imagedisplay_queue_draw_area( Imagedisplay *id, Rect *area )
58 {
59 #ifdef DEBUG_PAINT
60 printf( "imagedisplay_queue_draw_area: "
61 "left = %d, top = %d, width = %d, height = %d\n",
62 area->left, area->top, area->width, area->height );
63 #endif /*DEBUG_PAINT*/
64
65 gtk_widget_queue_draw_area( GTK_WIDGET( id ),
66 area->left, area->top, area->width, area->height );
67 }
68
69 /* Repaint an area of the image.
70 */
71 static void
imagedisplay_paint_image(Imagedisplay * id,Rect * area)72 imagedisplay_paint_image( Imagedisplay *id, Rect *area )
73 {
74 Conversion *conv = id->conv;
75
76 guchar *buf;
77 int lsk;
78
79 #ifdef DEBUG_PAINT
80 g_print( "imagedisplay_paint_image: at %d x %d, size %d x %d ",
81 area->left, area->top, area->width, area->height );
82 gobject_print( G_OBJECT( id ) );
83 #endif /*DEBUG_PAINT*/
84
85 /* Request pixels. We ask the mask first, to get an idea of what's
86 * currently in cache, then request tiles of pixels. We must always
87 * request pixels, even if the mask is blank, because the request
88 * will trigger a notify later which will reinvoke us.
89 */
90 if( conv->mreg &&
91 im_prepare( conv->mreg, area ) ) {
92 #ifdef DEBUG_PAINT
93 printf( "imagedisplay_paint_image: mask paint error\n" );
94 printf( "\t%s\n", im_error_buffer() );
95 #endif /*DEBUG_PAINT*/
96
97 return;
98 }
99 if( im_prepare( conv->ireg, area ) ) {
100 #ifdef DEBUG_PAINT
101 printf( "imagedisplay_paint_image: paint error\n" );
102 printf( "\t%s\n", im_error_buffer() );
103 #endif /*DEBUG_PAINT*/
104
105 im_error_clear();
106
107 return;
108 }
109
110 /* Is the mask all zero? Skip the paint.
111 */
112 if( conv->mreg ) {
113 gboolean found;
114 int x, y;
115
116 buf = (guchar *)
117 IM_REGION_ADDR( conv->mreg, area->left, area->top );
118 lsk = IM_REGION_LSKIP( conv->mreg );
119 found = FALSE;
120
121 for( y = 0; y < area->height; y++ ) {
122 for( x = 0; x < area->width; x++ )
123 if( buf[x] ) {
124 found = TRUE;
125 break;
126 }
127
128 if( found )
129 break;
130
131 buf += lsk;
132 }
133
134 if( !found ) {
135 #ifdef DEBUG_PAINT
136 printf( "imagedisplay_paint_image: zero mask\n" );
137 #endif /*DEBUG_PAINT*/
138
139 return;
140 }
141 }
142
143 /* Paint into window.
144 */
145
146 buf = (guchar *) IM_REGION_ADDR( conv->ireg, area->left, area->top );
147 lsk = IM_REGION_LSKIP( conv->ireg );
148
149 if( conv->ireg->im->Bands == 3 )
150 gdk_draw_rgb_image( GTK_WIDGET( id )->window,
151 GTK_WIDGET( id )->style->white_gc,
152 area->left, area->top, area->width, area->height,
153 GDK_RGB_DITHER_MAX,
154 buf, lsk );
155 else if( conv->ireg->im->Bands == 1 )
156 gdk_draw_gray_image( GTK_WIDGET( id )->window,
157 GTK_WIDGET( id )->style->white_gc,
158 area->left, area->top, area->width, area->height,
159 GDK_RGB_DITHER_MAX,
160 buf, lsk );
161 }
162
163 /* Paint an area with the background pattern.
164 */
165 static void
imagedisplay_paint_background(Imagedisplay * id,Rect * expose)166 imagedisplay_paint_background( Imagedisplay *id, Rect *expose )
167 {
168 #ifdef DEBUG_PAINT
169 g_print( "imagedisplay_paint_background: at %d x %d, size %d x %d\n",
170 expose->left, expose->top, expose->width, expose->height );
171 #endif /*DEBUG_PAINT*/
172
173 gdk_draw_rectangle( GTK_WIDGET( id )->window,
174 id->back_gc, TRUE,
175 expose->left, expose->top, expose->width, expose->height );
176 }
177
178 /* Paint areas outside the image.
179 */
180 static void
imagedisplay_paint_background_clipped(Imagedisplay * id,Rect * expose)181 imagedisplay_paint_background_clipped( Imagedisplay *id, Rect *expose )
182 {
183 Conversion *conv = id->conv;
184 Rect clip;
185
186 #ifdef DEBUG_PAINT
187 g_print( "imagedisplay_paint_background_clipped: canvas %d x %d\n",
188 conv->canvas.width, conv->canvas.height );
189 #endif /*DEBUG_PAINT*/
190
191 /* If the expose touches the image, we cut it into two parts:
192 * everything to the right of the image, and everything strictly
193 * below.
194 */
195 im_rect_intersectrect( expose, &conv->canvas, &clip );
196 if( !im_rect_isempty( &clip ) ) {
197 Rect area;
198
199 area = *expose;
200 area.left = conv->canvas.width;
201 area.width -= clip.width;
202 if( area.width > 0 )
203 imagedisplay_paint_background( id, &area );
204
205 area = *expose;
206 area.top = conv->canvas.height;
207 area.width = clip.width;
208 area.height -= clip.height;
209 if( area.height > 0 )
210 imagedisplay_paint_background( id, &area );
211 }
212 else
213 imagedisplay_paint_background( id, expose );
214 }
215
216 static void
imagedisplay_paint(Imagedisplay * id,Rect * area)217 imagedisplay_paint( Imagedisplay *id, Rect *area )
218 {
219 Conversion *conv = id->conv;
220 const int tsize = conv->tile_size;
221
222 Rect clip;
223 int xs, ys;
224 int x, y;
225
226 /* There's no image to paint.
227 */
228 if( !conv->ireg )
229 return;
230
231 /* Clip non-image parts of the expose.
232 */
233 im_rect_intersectrect( area, &conv->canvas, &clip );
234 if( im_rect_isempty( &clip ) )
235 return;
236
237 #ifdef DEBUG_PAINT
238 g_print( "imagedisplay_paint: at %d x %d, size %d x %d\n",
239 clip.left, clip.top, clip.width, clip.height );
240 #endif /*DEBUG_PAINT*/
241
242 /* Round left/top down to the start tile.
243 */
244 xs = (clip.left / tsize) * tsize;
245 ys = (clip.top / tsize) * tsize;
246
247 /* Now loop painting image tiles.
248 */
249 for( y = ys; y < IM_RECT_BOTTOM( &clip ); y += tsize )
250 for( x = xs; x < IM_RECT_RIGHT( &clip ); x += tsize ) {
251 Rect tile;
252 Rect tile2;
253
254 tile.left = x;
255 tile.top = y;
256 tile.width = conv->tile_size;
257 tile.height = conv->tile_size;
258 im_rect_intersectrect( &tile, &clip, &tile2 );
259
260 imagedisplay_paint_image( id, &tile2 );
261 }
262 }
263
264 /* Expose signal handler.
265 */
266 static gint
imagedisplay_expose(GtkWidget * widget,GdkEventExpose * event)267 imagedisplay_expose( GtkWidget *widget, GdkEventExpose *event )
268 {
269 Imagedisplay *id = IMAGEDISPLAY( widget );
270
271 GdkRectangle *rect;
272 int i, n;
273
274 if( !GTK_WIDGET_DRAWABLE( id ) ||
275 event->area.width == 0 ||
276 event->area.height == 0 ||
277 !GTK_WIDGET( id )->window ||
278 !GTK_WIDGET_VISIBLE( id ) )
279 return( FALSE );
280
281 gdk_region_get_rectangles( event->region, &rect, &n );
282 #ifdef DEBUG_PAINT
283 g_print( "imagedisplay_expose: %d rectangles\n", n );
284 #endif /*DEBUG_PAINT*/
285 for( i = 0; i < n; i++ ) {
286 Rect area;
287
288 area.left = rect[i].x;
289 area.top = rect[i].y;
290 area.width = rect[i].width;
291 area.height = rect[i].height;
292
293 /* Clear to background. Always do this, to make sure we paint
294 * outside the image area.
295 */
296 imagedisplay_paint_background_clipped( id, &area );
297
298 /* And paint pixels.
299 */
300 imagedisplay_paint( id, &area );
301 }
302 g_free( rect );
303
304 return( FALSE );
305 }
306
307 /* Resize signal.
308 */
309 static gboolean
imagedisplay_configure_event(GtkWidget * widget,GdkEventConfigure * event)310 imagedisplay_configure_event( GtkWidget *widget, GdkEventConfigure *event )
311 {
312 Imagedisplay *id = IMAGEDISPLAY( widget );
313
314 #ifdef DEBUG_GEO
315 g_print( "imagedisplay_configure_event: %d x %d:\n",
316 event->width, event->height );
317 #endif /*DEBUG_GEO*/
318
319 /* Note new size in visible hint. Except if parent is a viewport ...
320 * if it's a viewport, someone else will have to track the visible
321 * area.
322 */
323 if( !GTK_IS_VIEWPORT( gtk_widget_get_parent( widget ) ) ) {
324 id->conv->visible.width = event->width;
325 id->conv->visible.height = event->height;
326 }
327
328 /* Recalculate shrink to fit, if necessary.
329 */
330 if( id->shrink_to_fit ) {
331 #ifdef DEBUG_GEO
332 g_print( "imagedisplay_configure_event_cb: shrink-to-fit\n" );
333 #endif /*DEBUG_GEO*/
334
335 conversion_set_mag( id->conv, 0 );
336 }
337
338 return( FALSE );
339 }
340
341 static void
imagedisplay_destroy(GtkObject * object)342 imagedisplay_destroy( GtkObject *object )
343 {
344 Imagedisplay *id = IMAGEDISPLAY( object );
345
346 #ifdef DEBUG
347 g_print( "imagedisplay_destroy: " );
348 gobject_print( G_OBJECT( id ) );
349 #endif /*DEBUG*/
350
351 FREESID( id->changed_sid, id->conv );
352 FREESID( id->area_changed_sid, id->conv );
353 UNREF( id->conv );
354
355 UNREF( id->back_gc );
356 UNREF( id->top_gc );
357 UNREF( id->bottom_gc );
358
359 GTK_OBJECT_CLASS( parent_class )->destroy( object );
360 }
361
362 /* Conversion has changed ... resize to fit.
363 */
364 static void
imagedisplay_real_conversion_changed(Imagedisplay * id)365 imagedisplay_real_conversion_changed( Imagedisplay *id )
366 {
367 GtkRequisition *requisition = >K_WIDGET( id )->requisition;
368 Rect *canvas = &id->conv->canvas;
369
370 g_assert( IS_IMAGEDISPLAY( id ) );
371
372 #ifdef DEBUG
373 g_print( "imagedisplay_real_conversion_changed: " );
374 gobject_print( G_OBJECT( id ) );
375 #endif /*DEBUG*/
376
377 /* If we're in shrink-to-fit mode, do a shrink.
378 * Otherwise resize to hold the new image.
379 */
380 if( id->shrink_to_fit )
381 conversion_set_mag( id->conv, 0 );
382 else if( requisition->width != canvas->width ||
383 requisition->height != canvas->height ) {
384 #ifdef DEBUG_GEO
385 g_print( "imagedisplay_real_conversion_"
386 "changed: requesting new size "
387 "%d x %d\n",
388 id->conv->canvas.width,
389 id->conv->canvas.height );
390 #endif /*DEBUG_GEO*/
391
392 requisition->width = canvas->width;
393 requisition->height = canvas->height;
394 gtk_widget_queue_resize( GTK_WIDGET( id ) );
395 }
396 }
397
398 static void
imagedisplay_real_area_changed(Imagedisplay * id,Rect * dirty)399 imagedisplay_real_area_changed( Imagedisplay *id, Rect *dirty )
400 {
401 imagedisplay_queue_draw_area( id, dirty );
402 }
403
404 static void
imagedisplay_realize(GtkWidget * widget)405 imagedisplay_realize( GtkWidget *widget )
406 {
407 Imagedisplay *id = IMAGEDISPLAY( widget );
408
409 GdkColor fg, bg;
410
411 GTK_WIDGET_CLASS( parent_class )->realize( widget );
412
413 gdk_window_set_back_pixmap( widget->window, NULL, FALSE );
414 gtk_widget_set_double_buffered( widget, FALSE );
415
416 id->back_gc = gdk_gc_new( widget->window );
417 fg.red = fg.green = fg.blue = 0x90 << 8;
418 bg.red = bg.green = bg.blue = 0xA0 << 8;
419 gdk_gc_set_rgb_fg_color( id->back_gc, &fg );
420 gdk_gc_set_rgb_bg_color( id->back_gc, &bg );
421
422 id->top_gc = gdk_gc_new( widget->window );
423 id->bottom_gc = gdk_gc_new( widget->window );
424 }
425
426 /* Init Imagedisplay class.
427 */
428 static void
imagedisplay_class_init(ImagedisplayClass * class)429 imagedisplay_class_init( ImagedisplayClass *class )
430 {
431 GtkObjectClass *object_class = (GtkObjectClass *) class;
432 GtkWidgetClass *widget_class = (GtkWidgetClass *) class;
433
434 parent_class = g_type_class_peek_parent( class );
435
436 object_class->destroy = imagedisplay_destroy;
437
438 widget_class->expose_event = imagedisplay_expose;
439 widget_class->configure_event = imagedisplay_configure_event;
440 widget_class->realize = imagedisplay_realize;
441
442 class->conversion_changed = imagedisplay_real_conversion_changed;
443 class->area_changed = imagedisplay_real_area_changed;
444
445 imagedisplay_signals[SIG_AREA_CHANGED] = g_signal_new( "area_changed",
446 G_OBJECT_CLASS_TYPE( class ),
447 G_SIGNAL_RUN_FIRST,
448 G_STRUCT_OFFSET( ImagedisplayClass, area_changed ),
449 NULL, NULL,
450 g_cclosure_marshal_VOID__POINTER,
451 G_TYPE_NONE, 1,
452 G_TYPE_POINTER );
453 }
454
455 static void
imagedisplay_init(Imagedisplay * id)456 imagedisplay_init( Imagedisplay *id )
457 {
458 id->conv = NULL;
459 id->changed_sid = 0;
460 id->area_changed_sid = 0;
461 id->shrink_to_fit = FALSE;
462
463 id->back_gc = NULL;
464 id->top_gc = NULL;
465 id->bottom_gc = NULL;
466 }
467
468 GType
imagedisplay_get_type(void)469 imagedisplay_get_type( void )
470 {
471 static GType type = 0;
472
473 if( !type ) {
474 static const GTypeInfo info = {
475 sizeof( ImagedisplayClass ),
476 NULL, /* base_init */
477 NULL, /* base_finalize */
478 (GClassInitFunc) imagedisplay_class_init,
479 NULL, /* class_finalize */
480 NULL, /* class_data */
481 sizeof( Imagedisplay ),
482 32, /* n_preallocs */
483 (GInstanceInitFunc) imagedisplay_init,
484 };
485
486 type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
487 "Imagedisplay", &info, 0 );
488 }
489
490 return( type );
491 }
492
493 /* Conversion has changed ... repaint everything.
494 */
495 static void
imagedisplay_conversion_changed_cb(Conversion * conv,Imagedisplay * id)496 imagedisplay_conversion_changed_cb( Conversion *conv, Imagedisplay *id )
497 {
498 #ifdef DEBUG
499 printf( "imagedisplay_conversion_changed_cb: " );
500 gobject_print( G_OBJECT( id ) );
501 #endif /*DEBUG*/
502
503 IMAGEDISPLAY_GET_CLASS( id )->conversion_changed( id );
504
505 g_signal_emit( G_OBJECT( id ),
506 imagedisplay_signals[SIG_AREA_CHANGED], 0, &conv->canvas );
507 }
508
509 /* Part of the repaint has changed.
510 */
511 static void
imagedisplay_conversion_area_changed_cb(Conversion * conv,Rect * dirty,Imagedisplay * id)512 imagedisplay_conversion_area_changed_cb( Conversion *conv,
513 Rect *dirty, Imagedisplay *id )
514 {
515 #ifdef DEBUG
516 printf( "imagedisplay_conversion_area_changed_cb: "
517 "left = %d, top = %d, width = %d, height = %d, ",
518 dirty->left, dirty->top, dirty->width, dirty->height );
519 gobject_print( G_OBJECT( id ) );
520 #endif /*DEBUG*/
521
522 g_signal_emit( G_OBJECT( id ),
523 imagedisplay_signals[SIG_AREA_CHANGED], 0, dirty );
524 }
525
526 /* Install a conversion. Only allow this once.
527 */
528 void
imagedisplay_set_conversion(Imagedisplay * id,Conversion * conv)529 imagedisplay_set_conversion( Imagedisplay *id, Conversion *conv )
530 {
531 g_assert( !id->conv );
532
533 if( conv ) {
534 id->conv = conv;
535 id->changed_sid = g_signal_connect( id->conv, "changed",
536 G_CALLBACK( imagedisplay_conversion_changed_cb ), id );
537 id->area_changed_sid = g_signal_connect( id->conv,
538 "area_changed",
539 G_CALLBACK( imagedisplay_conversion_area_changed_cb ),
540 id );
541 g_object_ref( G_OBJECT( conv ) );
542 iobject_sink( IOBJECT( conv ) );
543
544 /* Trigger a change on the conv so we update.
545 */
546 iobject_changed( IOBJECT( conv ) );
547 }
548 }
549
550 /* Make a new Imagedisplay. Pass in the conversion we should show, conv can
551 * be NULL ... wait for one to be installed.
552 */
553 Imagedisplay *
imagedisplay_new(Conversion * conv)554 imagedisplay_new( Conversion *conv )
555 {
556 Imagedisplay *id = g_object_new( TYPE_IMAGEDISPLAY, NULL );
557
558 #ifdef DEBUG
559 g_print( "imagedisplay_new: " );
560 gobject_print( G_OBJECT( id ) );
561 #endif /*DEBUG*/
562
563 imagedisplay_set_conversion( id, conv );
564
565 return( id );
566 }
567
568 void
imagedisplay_set_shrink_to_fit(Imagedisplay * id,gboolean shrink_to_fit)569 imagedisplay_set_shrink_to_fit( Imagedisplay *id, gboolean shrink_to_fit )
570 {
571 id->shrink_to_fit = shrink_to_fit;
572
573 if( shrink_to_fit )
574 conversion_set_mag( id->conv, 0 );
575 }
576