1 /* Tiny display-an-image demo program.
2  *
3  * This is not supposed to be a complete image viewer, it's just supposed to
4  * show how to display a VIPS image (or the result of a VIPS computation) in a
5  * window.
6  *
7  * 8-bit RGB images only, though it would be easy to fix this.
8  *
9  * Compile with:
10 
11 	g++ -g -Wall gtkdisp2.cc \
12 		`pkg-config vipsCC-7.26 gtkmm-2.4 --cflags --libs`
13 
14  */
15 
16 #include <stdio.h>
17 
18 #include <gtkmm.h>
19 
20 #include <vips/vips>
21 
22 using namespace vips;
23 
24 /* We need the C API as well for the region stuff.
25  */
26 #include <vips/vips.h>
27 
28 /* Subclass DrawingArea to make a widget that displays a VImage.
29  */
30 class ImageArea : public Gtk::DrawingArea
31 {
32 private:
33   /* The image we display.
34    */
35   VImage image;
36 
37   /* The derived image we paint to the screen.
38    */
39   VImage display_image;
40 
41   /* The region we prepare from to draw the pixels,
42    */
43   VipsRegion * region;
44 
45   /* We send this packet of data from the bg worker thread to the main GUI
46    * thread when a tile has been calculated.
47    */
48   typedef struct {
49     ImageArea * image_area;
50     VipsRect rect;
51   } Update;
52 
53   /* The main GUI thread runs this when it's idle and there are tiles that need
54    * painting.
55    */
render_cb(Update * update)56   static gboolean render_cb (Update * update)
57   {
58     update->image_area->queue_draw_area (update->rect.left,
59 		                         update->rect.top,
60 					 update->rect.width,
61 					 update->rect.height);
62 
63     g_free (update);
64 
65     return FALSE;
66   }
67 
68   /* Come here from the vips_sink_screen() background thread when a tile has
69    * been calculated.
70    *
71    * We can't paint the screen directly since the main GUI thread might be
72    * doing something. Instead, we add an idle callback which will be
73    * run by the main GUI thread when it next hits the mainloop.
74    */
75   static void
sink_notify(VipsImage * image,VipsRect * rect,void * client)76   sink_notify (VipsImage *image, VipsRect *rect, void *client)
77   {
78     ImageArea * image_area = (ImageArea *) client;
79     Update * update = g_new (Update, 1);
80 
81     update->rect = *rect;
82     update->image_area = image_area;
83 
84     g_idle_add ((GSourceFunc) render_cb, update);
85   }
86 
87   /* Edit these to add or remove things from the display pipe we build.
88    * These should be wired up to something in a GUI.
89    */
90   static const gboolean zoom_in = FALSE;
91   static const gboolean zoom_out = FALSE;
92   static const gboolean edge_detect = FALSE;
93 
94   /* Make the image for display from the raw disc image. Could do
95    * anything here, really. Uncomment sections to try different effects.
96    * Convert to 8-bit RGB would be a good idea.
97    */
98   void
build_display_image()99   build_display_image ()
100   {
101     VImage t = image;
102 
103     if (zoom_out)
104       t = t.subsample (4, 4);
105 
106     if (zoom_in)
107       t = t.zoom (4, 4);
108 
109     if (edge_detect)
110       {
111 	VIMask m (3, 3, 1, 0, -1, -1, -1, -1, 8, -1, -1, -1, -1);
112 
113 	t = t.conv (m);
114       }
115 
116     /* vips_sink_screen() is not wrapped by C++ ... we need to drop down to C
117      * here.
118      *
119      * We ask for a cache of 1000 64x64 tiles, enough to be able to repaint a
120      * 1920 x 1200 screen, plus a bit.
121      */
122     if (vips_sink_screen (t.image (), display_image.image (), NULL,
123 			  64, 64, 1000, 0, sink_notify, this))
124       verror ();
125 
126     /* display_image depends on t .. we need to keep t alive as long
127      * as display_image is alive.
128      */
129     display_image._ref->addref (t._ref);
130   }
131 
132   void
expose_rect(GdkRectangle * expose)133   expose_rect (GdkRectangle * expose)
134   {
135     /* Clip against the image size ... we don't want to try painting outside the
136      * image area.
137      */
138     VipsRect image = {0, 0, display_image.Xsize (), display_image.Ysize ()};
139     VipsRect area = {expose->x, expose->y, expose->width, expose->height};
140     VipsRect clip;
141 
142     vips_rect_intersectrect (&image, &area, &clip);
143     if (vips_rect_isempty (&clip))
144       return;
145 
146     /* Calculate pixels. If this area is not in cache, we will see black
147      * pixels, a background thread will start calculating stuff, and we will
148      * get a notify callback from the bg thread when our pixels area ready. If
149      * the area is in cache, we see pixels immediately.
150      *
151      * If we took the trouble, we could use the mask image to see what parts
152      * of the resulting region were from cache and what parts were
153      * uncalculated.
154      */
155     if (vips_region_prepare (region, &clip))
156       return;
157     guchar *buf = (guchar *) VIPS_REGION_ADDR (region, clip.left, clip.top);
158     int lsk = VIPS_REGION_LSKIP (region);
159 
160     get_window ()->draw_rgb_image (get_style ()->get_white_gc (),
161 				   clip.left, clip.top, clip.width, clip.height,
162 				   Gdk::RGB_DITHER_MAX, buf, lsk);
163   }
164 
on_expose_event(GdkEventExpose * event)165   virtual bool on_expose_event (GdkEventExpose * event)
166   {
167     GdkRectangle *expose;
168     int i, n;
169 
170     gdk_region_get_rectangles (event->region, &expose, &n);
171     for (i = 0; i < n; i++)
172       expose_rect (&expose[i]);
173     g_free (expose);
174 
175     return TRUE;
176   }
177 
178 public:
ImageArea()179   ImageArea ()
180   {
181     region = NULL;
182   }
183 
~ImageArea()184   virtual ~ ImageArea ()
185   {
186     if (region)
187       {
188 	g_object_unref (region);
189         region = NULL;
190       }
191   }
192 
193   void
set_image(VImage new_image)194   set_image (VImage new_image)
195   {
196     image = new_image;
197 
198     /* Reset the display image.
199      */
200     VImage null;
201     display_image = null;
202     if (region)
203       {
204 	g_object_unref (region);
205 	region = NULL;
206       }
207 
208     build_display_image ();
209     region = vips_region_new (display_image.image ());
210     set_size_request (display_image.Xsize (), display_image.Ysize ());
211   }
212 };
213 
214 int
main(int argc,char ** argv)215 main (int argc, char **argv)
216 {
217   Gtk::Main kit (argc, argv);
218 
219   if (argc != 2)
220     error_exit ("usage: %s <filename>", argv[0]);
221   if (vips_init (argv[0]))
222     verror ();
223 
224   VImage image(argv[1]);
225 
226   Gtk::Window window;
227   window.set_default_size (500, 500);
228   Gtk::ScrolledWindow scrolled_window;
229   window.add (scrolled_window);
230   ImageArea area;
231   area.set_image (image);
232   scrolled_window.add (area);
233   window.show_all ();
234 
235   Gtk::Main::run (window);
236 
237   return 0;
238 }
239