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 = &GTK_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