]> git.lyx.org Git - lyx.git/blob - src/frontends/xforms/xformsImage.C
(Michael Schmitt): the proper fix for the off-by-one cropping of graphics
[lyx.git] / src / frontends / xforms / xformsImage.C
1 /**
2  * \file xformsImage.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Angus Leeming
7  *
8  * Full author contact details are available in file CREDITS
9  */
10
11 #include <config.h>
12
13 #ifdef __GNUG__
14 #pragma implementation
15 #endif
16
17 #include "xformsImage.h"
18 #include "graphics/GraphicsParams.h"
19 #include "LColor.h"
20 #include "converter.h"              // formats
21 #include "debug.h"
22 #include "support/LAssert.h"
23 #include "support/lyxfunctional.h"  // compare_memfun
24
25 #include FORMS_H_LOCATION
26
27 #ifdef HAVE_FLIMAGE_H
28 # include <flimage.h>
29 #else
30 # ifdef HAVE_X11_FLIMAGE_H
31 # include <X11/flimage.h>
32 # endif
33 #endif
34
35 #include <boost/tuple/tuple.hpp>
36
37 using std::find_if;
38
39 namespace {
40
41 void init_graphics();
42
43 unsigned int packedcolor(LColor::color c);
44
45 } // namespace anon
46
47
48 namespace grfx {
49
50 /// Access to this class is through this static method.
51 Image::ImagePtr xformsImage::newImage()
52 {
53         init_graphics();
54
55         ImagePtr ptr;
56         ptr.reset(new xformsImage);
57         return ptr;
58 }
59
60
61 /// Return the list of loadable formats.
62 Image::FormatList xformsImage::loadableFormats()
63 {
64         static FormatList fmts;
65         if (!fmts.empty())
66                 return fmts;
67
68         init_graphics();
69
70         // The formats recognised by LyX
71         Formats::const_iterator begin = formats.begin();
72         Formats::const_iterator end   = formats.end();
73
74         lyxerr[Debug::GRAPHICS]
75                 << "\nThe image loader can load the following directly:\n";
76
77         // Don't forget the Fortran numbering used by xforms!
78         int const nformats = flimage_get_number_of_formats();
79         for (int i = 1; i <= nformats; ++i) {
80
81                 FLIMAGE_FORMAT_INFO const * info = flimage_get_format_info(i);
82                 string const formal_name =
83                         info->formal_name ? info->formal_name : string();
84                 string ext =
85                         info->extension   ? info->extension   : string();
86
87                 if (ext.empty() || ext == "gz")
88                         continue;
89
90                 if (ext == "rgb") ext = "sgi";
91
92                 lyxerr[Debug::GRAPHICS]
93                         << formal_name << ", extension \"" << ext << "\"\n";
94
95                 Formats::const_iterator it =
96                         find_if(begin, end,
97                                 lyx::compare_memfun(&Format::extension, ext));
98                 if (it != end)
99                         fmts.push_back(it->name());
100         }
101
102         lyxerr[Debug::GRAPHICS]
103                 << "\nOf these, LyX recognises the following formats:\n";
104
105         FormatList::const_iterator fbegin = fmts.begin();
106         FormatList::const_iterator fend   = fmts.end();
107         for (FormatList::const_iterator fit = fbegin; fit != fend; ++fit) {
108                 if (fit != fbegin)
109                         lyxerr[Debug::GRAPHICS] << ", ";
110                 lyxerr[Debug::GRAPHICS] << *fit;
111         }
112         lyxerr[Debug::GRAPHICS] << '\n' << std::endl;
113
114         return fmts;
115 }
116
117
118 xformsImage::xformsImage()
119         : image_(0),
120           pixmap_(0),
121           pixmap_status_(PIXMAP_UNINITIALISED)
122 {}
123
124
125 xformsImage::xformsImage(xformsImage const & other)
126         : Image(other),
127           image_(0),
128           pixmap_(0),
129           pixmap_status_(PIXMAP_UNINITIALISED)
130 {
131         if (other.image_) {
132                 image_ = flimage_dup(other.image_);
133                 image_->u_vdata = this;
134         }
135 }
136
137
138 xformsImage::~xformsImage()
139 {
140         if (image_)
141                 flimage_free(image_);
142         if (pixmap_)
143                 XFreePixmap(fl_get_display(), pixmap_);
144 }
145
146
147 Image * xformsImage::clone() const
148 {
149         return new xformsImage(*this);
150 }
151
152
153 unsigned int xformsImage::getWidth() const
154 {
155         if (!image_)
156                 return 0;
157
158         return image_->w;
159 }
160
161
162 unsigned int xformsImage::getHeight() const
163 {
164         if (!image_)
165                 return 0;
166         return image_->h;
167 }
168
169
170 bool xformsImage::isDrawable() const
171 {
172         return pixmap_;
173 }
174
175
176 Pixmap xformsImage::getPixmap() const
177 {
178         if (!pixmap_status_ == PIXMAP_SUCCESS)
179                 return 0;
180         return pixmap_;
181 }
182
183
184 void xformsImage::load(string const & filename)
185 {
186         if (image_) {
187                 lyxerr[Debug::GRAPHICS]
188                         << "Image is loaded already!" << std::endl;
189                 finishedLoading(false);
190                 return;
191         }
192
193         image_ = flimage_open(filename.c_str());
194         if (!image_) {
195                 lyxerr[Debug::GRAPHICS]
196                         << "Unable to open image" << std::endl;
197                 finishedLoading(false);
198                 return;
199         }
200
201         // Set this now and we won't need to bother again.
202         image_->fill_color = packedcolor(LColor::graphicsbg);
203
204         // Used by the callback routines to return to this
205         image_->u_vdata = this;
206
207         // Begin the reading process.
208         flimage_read(image_);
209 }
210
211
212 bool xformsImage::setPixmap(Params const & params)
213 {
214         if (!image_ || params.display == NoDisplay)
215                 return false;
216
217         Display * display = fl_get_display();
218
219         if (pixmap_ && pixmap_status_ == PIXMAP_SUCCESS)
220                 XFreePixmap(display, pixmap_);
221
222         int color_key;
223         switch (params.display) {
224         case MonochromeDisplay:
225                 color_key = FL_IMAGE_MONO;
226                 break;
227         case GrayscaleDisplay:
228                 color_key = FL_IMAGE_GRAY;
229                 break;
230         case ColorDisplay:
231         default: // NoDisplay cannot happen!
232                 color_key = FL_IMAGE_RGB;
233                 break;
234         }
235
236         if (color_key != FL_IMAGE_RGB) {
237                 flimage_convert(image_, color_key, 0);
238         }
239
240         unsigned int fill = packedcolor(LColor::graphicsbg);
241         if (fill != image_->fill_color) {
242                 // the background color has changed.
243                 // Note that in grayscale/monochrome images the background is
244                 // grayed also, so this call will have no visible effect. Sorry!
245                 flimage_replace_pixel(image_, image_->fill_color, fill);
246                 image_->fill_color = fill;
247         }
248
249         image_->xdisplay = display;
250         Screen * screen  = ScreenOfDisplay(display, fl_screen);
251
252         pixmap_ = flimage_to_pixmap(image_, XRootWindowOfScreen(screen));
253         pixmap_status_ = pixmap_ ? PIXMAP_SUCCESS : PIXMAP_FAILED;
254
255         return pixmap_status_ == PIXMAP_SUCCESS;
256 }
257
258
259 void xformsImage::clip(Params const & params)
260 {
261         if (!image_)
262                 return;
263
264         if (params.bb.empty())
265                 // No clipping is necessary.
266                 return;
267
268         int const new_width  = params.bb.xr - params.bb.xl;
269         int const new_height = params.bb.yt - params.bb.yb;
270
271         // No need to check if the width, height are > 0 because the
272         // Bounding Box would be empty() in this case.
273         if (new_width > image_->w || new_height > image_->h)
274                 // Bounds are invalid.
275                 return;
276
277         if (new_width == image_->w && new_height == image_->h)
278                 // Bounds are unchanged.
279                 return;
280
281         // flimage.h: image_ members w and h are of type int
282         // (though always >= 0)
283         // GraphicsParams.h: params.bb members xl, xr, yt and yb are of
284         // type unsigned int.
285         // We must, therefore, be careful...
286         int const xoffset_l = params.bb.xl;
287         int const yoffset_b = params.bb.yb;
288         int const xoffset_r = image_->w > params.bb.xr ?
289                 image_->w - params.bb.xr : 0;
290         int const yoffset_t = image_->h > params.bb.yt ?
291                 image_->h - params.bb.yt : 0;
292
293         flimage_crop(image_, xoffset_l, yoffset_t, xoffset_r, yoffset_b);
294 }
295
296
297 void xformsImage::rotate(Params const & params)
298 {
299         if (!image_)
300                 return ;
301
302         if (!params.angle)
303                 // No rotation is necessary.
304                 return;
305
306         // The angle passed to flimage_rotate is the angle in one-tenth of a
307         // degree units.
308
309         // Work around xforms bug when params.angle == 270
310         // the 'InternalError: bad special angle' error.
311         // This bug fix is not needed in xforms 1.0 and greater.
312         if (params.angle == 270) {
313                 flimage_rotate(image_,  900, FLIMAGE_SUBPIXEL);
314                 flimage_rotate(image_, 1800, FLIMAGE_SUBPIXEL);
315         } else {
316                 flimage_rotate(image_,
317                                int(params.angle * 10),
318                                FLIMAGE_SUBPIXEL);
319         }
320 }
321
322
323 void xformsImage::scale(Params const & params)
324 {
325         if (!image_)
326                 return;
327
328         unsigned int width;
329         unsigned int height;
330         boost::tie(width, height) = getScaledDimensions(params);
331
332         if (width == getWidth() && height == getHeight())
333                 // No scaling needed
334                 return;
335
336         flimage_scale(image_, width, height, FLIMAGE_SUBPIXEL);
337 }
338
339
340 void xformsImage::statusCB(string const & status_message)
341 {
342         if (status_message.empty())
343                 return;
344
345         if (prefixIs(status_message, "Done Reading")) {
346                 if (image_) {
347                         flimage_close(image_);
348                 }
349
350                 finishedLoading(true);
351         }
352 }
353
354
355 void xformsImage::errorCB(string const & error_message)
356 {
357         if (error_message.empty())
358                 return;
359
360         if (image_) {
361                 flimage_close(image_);
362         }
363
364         finishedLoading(false);
365 }
366
367 } // namespace grfx
368
369
370 namespace {
371
372 extern "C" {
373
374 int status_report(FL_IMAGE * ob, const char *s)
375 {
376         lyx::Assert(ob && ob->u_vdata);
377
378         string const str = s ? rtrim(s) : string();
379         if (str.empty())
380                 return 0;
381
382         lyxerr[Debug::GRAPHICS]
383                 << "xforms image loader. Status: " << str << std::endl;
384
385         grfx::xformsImage * ptr =
386                 static_cast<grfx::xformsImage *>(ob->u_vdata);
387         ptr->statusCB(str);
388
389         return 0;
390 }
391
392
393 static void error_report(FL_IMAGE * ob, const char *s)
394 {
395         lyx::Assert(ob && ob->u_vdata);
396
397         string const str = s ? rtrim(s) : string();
398         if (str.empty())
399                 return;
400
401         lyxerr[Debug::GRAPHICS]
402                 << "xforms image loader. Error: " << str << std::endl;
403
404         grfx::xformsImage * ptr =
405                 static_cast<grfx::xformsImage *>(ob->u_vdata);
406         ptr->errorCB(str);
407 }
408
409 } // extern "C"
410
411
412 void init_graphics()
413 {
414         // Paranoia check
415         static bool initialised = false;
416         if (initialised)
417                 return;
418         initialised = true;
419
420         flimage_enable_bmp();
421         flimage_enable_fits();
422         flimage_enable_gif();
423 #ifdef USE_JPEG_IMAGE_LOADER
424         flimage_enable_jpeg();
425 #endif
426
427         // xforms itself uses pngtopnm to convert to a loadable format.
428         // We prefer to use our own conversion mechanism, therefore.
429         // flimage_enable_png();
430
431         flimage_enable_pnm();
432
433         // xforms recognises PS but not EPS
434         // It dies horribly with lots of older PostScript files.
435         // Easiest, therefore, to disable PS support and insist that a PS-type
436         // file is converted to a bitmap format.
437         // flimage_enable_ps();
438
439         flimage_enable_sgi();
440         flimage_enable_tiff();
441         flimage_enable_xbm();
442         flimage_enable_xwd();
443         // xforms can load most XPM files, but will occasionally crash
444         // with some files created by ImakeMagick's convert program.
445         // Turn off xpm support for the time being.
446         // flimage_enable_xpm();
447
448         // xforms stores this permanently (does not make a copy) so
449         // this should never be destroyed.
450         static FLIMAGE_SETUP setup;
451         setup.visual_cue    = status_report;
452         setup.error_message = error_report;
453         flimage_setup(&setup);
454 }
455
456
457 unsigned int packedcolor(LColor::color c)
458 {
459         string const x11color = lcolor.getX11Name(c);
460
461         Display * display = fl_get_display();
462         Colormap cmap     = fl_state[fl_get_vclass()].colormap;
463         XColor xcol;
464         XColor ccol;
465         if (XLookupColor(display, cmap, x11color.c_str(), &xcol, &ccol) == 0)
466                 // Unable to parse x11color.
467                 return FL_PACK(255,255,255);
468
469         // Note that X stores the RGB values in the range 0 - 65535
470         // whilst we require them in the range 0 - 255.
471         unsigned int const r = xcol.red   / 256;
472         unsigned int const g = xcol.green / 256;
473         unsigned int const b = xcol.blue  / 256;
474
475         return FL_PACK(r, g, b);
476 }
477
478 } // namespace anon