]> git.lyx.org Git - lyx.git/blob - src/graphics/GraphicsImageXPM.C
partial framebox support
[lyx.git] / src / graphics / GraphicsImageXPM.C
1 /*
2  * \file GraphicsImageXPM.C
3  * Copyright 2002 the LyX Team
4  * Read the file COPYING
5  *
6  * \author Baruch Even <baruch.even@writeme.com>
7  * \author Angus Leeming <leeming@lyx.org>
8  */
9
10 #include <config.h>
11
12 #ifdef __GNUG__
13 #pragma implementation
14 #endif
15
16 #include "GraphicsImageXPM.h"
17 #include "GraphicsParams.h"
18 #include "frontends/xforms/ColorHandler.h"
19 #include "debug.h"
20 #include "support/filetools.h"    // IsFileReadable
21 #include "support/lstrings.h"
22 #include "Lsstream.h"
23
24 #include <boost/tuple/tuple.hpp>
25 #include <boost/bind.hpp>
26
27 #include FORMS_H_LOCATION
28
29 #include <iomanip>                // std::setfill, etc
30 #include <cmath>                  // cos, sin
31 #include <cstdlib>                // malloc, free
32
33 #ifndef CXX_GLOBAL_CSTD
34 using std::cos;
35 using std::sin;
36 using std::malloc;
37 using std::strcpy;
38 using std::strlen;
39 #endif
40
41 namespace grfx {
42
43 /// Access to this class is through this static method.
44 Image::ImagePtr ImageXPM::newImage()
45 {
46         ImagePtr ptr;
47         ptr.reset(new ImageXPM);
48         return ptr;
49 }
50
51
52 /// Return the list of loadable formats.
53 Image::FormatList ImageXPM::loadableFormats()
54 {
55         FormatList formats(1);
56         formats[0] = "xpm";
57         return formats;
58 }
59
60
61 ImageXPM::ImageXPM()
62         : pixmap_(0),
63           pixmap_status_(PIXMAP_UNINITIALISED)
64 {}
65
66
67 ImageXPM::ImageXPM(ImageXPM const & other)
68         : Image(other),
69           image_(other.image_),
70           pixmap_(0),
71           pixmap_status_(PIXMAP_UNINITIALISED)
72 {}
73
74
75 ImageXPM::~ImageXPM()
76 {
77         if (pixmap_)
78                 XFreePixmap(fl_get_display(), pixmap_);
79 }
80
81
82 Image * ImageXPM::clone() const
83 {
84         return new ImageXPM(*this);
85 }
86
87
88 unsigned int ImageXPM::getWidth() const
89 {
90         return image_.width();
91 }
92
93
94 unsigned int ImageXPM::getHeight() const
95 {
96         return image_.height();
97 }
98
99
100 bool ImageXPM::isDrawable() const
101 {
102         return pixmap_;
103 }
104
105
106 Pixmap ImageXPM::getPixmap() const
107 {
108         if (!pixmap_status_ == PIXMAP_SUCCESS)
109                 return 0;
110         return pixmap_;
111 }
112
113
114 void ImageXPM::load(string const & filename)
115 {
116         if (filename.empty()) {
117                 finishedLoading(false);
118                 return;
119         }
120
121         if (!image_.empty()) {
122                 lyxerr[Debug::GRAPHICS]
123                         << "Image is loaded already!" << std::endl;
124                 finishedLoading(false);
125                 return;
126         }
127
128         XpmImage * xpm_image = new XpmImage;
129
130         int const success =
131                 XpmReadFileToXpmImage(const_cast<char *>(filename.c_str()),
132                                       xpm_image, 0);
133
134         switch (success) {
135         case XpmOpenFailed:
136                 lyxerr[Debug::GRAPHICS]
137                         << "No XPM image file found." << std::endl;
138                 break;
139
140         case XpmFileInvalid:
141                 lyxerr[Debug::GRAPHICS]
142                         << "File format is invalid" << std::endl;
143                 break;
144
145         case XpmNoMemory:
146                 lyxerr[Debug::GRAPHICS]
147                         << "Insufficient memory to read in XPM file"
148                         << std::endl;
149                 break;
150         }
151
152         if (success != XpmSuccess) {
153                 XpmFreeXpmImage(xpm_image);
154                 delete xpm_image;
155
156                 lyxerr[Debug::GRAPHICS]
157                         << "Error reading XPM file '"
158                         << XpmGetErrorString(success) << "'"
159                         << std::endl;
160         } else {
161                 image_.reset(*xpm_image);
162         }
163
164         finishedLoading(success == XpmSuccess);
165 }
166
167
168 bool ImageXPM::setPixmap(Params const & params)
169 {
170         if (image_.empty() || params.display == NoDisplay) {
171                 return false;
172         }
173
174         Display * display = fl_get_display();
175
176         if (pixmap_ && pixmap_status_ == PIXMAP_SUCCESS)
177                 XFreePixmap(display, pixmap_);
178
179         //(BE 2000-08-05)
180         // This might be a dirty thing, but I dont know any other solution.
181         Screen * screen = ScreenOfDisplay(display, fl_screen);
182
183         Pixmap pixmap;
184         Pixmap mask;
185
186         XpmAttributes attrib;
187
188         // Allow libXPM lots of leeway when trying to allocate colors.
189         attrib.closeness = 10000;
190         attrib.valuemask = XpmCloseness;
191
192         // The XPM file format allows multiple pixel colours to be defined
193         // as c_color, g_color or m_color.
194         switch (params.display) {
195         case MonochromeDisplay:
196                 attrib.color_key = XPM_MONO;
197                 break;
198         case GrayscaleDisplay:
199                 attrib.color_key = XPM_GRAY;
200                 break;
201         case ColorDisplay:
202         default: // NoDisplay cannot happen!
203                 attrib.color_key = XPM_COLOR;
204                 break;
205         }
206
207         attrib.valuemask |= XpmColorKey;
208
209         // Set the color "none" entry to the color of the background.
210         XpmColorSymbol xpm_col[2];
211         xpm_col[0].name = 0;
212         xpm_col[0].value = "none";
213         xpm_col[0].pixel = lyxColorHandler->colorPixel(LColor::graphicsbg);
214
215         // some image magick versions use this
216         xpm_col[1].name = 0;
217         xpm_col[1].value = "opaque";
218         xpm_col[1].pixel = lyxColorHandler->colorPixel(LColor::black);
219
220         attrib.numsymbols = 2;
221         attrib.colorsymbols = xpm_col;
222         attrib.valuemask |= XpmColorSymbols;
223
224         // Load up the pixmap
225         XpmImage xpm_image = image_.get();
226         int const status =
227                 XpmCreatePixmapFromXpmImage(display,
228                                             XRootWindowOfScreen(screen),
229                                             &xpm_image,
230                                             &pixmap, &mask, &attrib);
231
232         XpmFreeAttributes(&attrib);
233
234         if (status != XpmSuccess) {
235                 lyxerr << "Error creating pixmap from xpm_image '"
236                        << XpmGetErrorString(status) << "'"
237                        << std::endl;
238                 pixmap_status_ = PIXMAP_FAILED;
239                 return false;
240         }
241
242         pixmap_ = pixmap;
243         pixmap_status_ = PIXMAP_SUCCESS;
244         return true;
245 }
246
247
248 void ImageXPM::clip(Params const & params)
249 {
250         if (image_.empty())
251                 return;
252
253         if (params.bb.empty())
254                 // No clipping is necessary.
255                 return;
256
257         typedef unsigned int dimension;
258
259         dimension const new_width  = params.bb.xr - params.bb.xl;
260         dimension const new_height = params.bb.yt - params.bb.yb;
261
262         if (new_width > image_.width() || new_height > image_.height())
263                 // Bounds are invalid.
264                 return;
265
266         if (new_width == image_.width() && new_height == image_.height())
267                 // Bounds are unchanged.
268                 return;
269
270         dimension * new_data = image_.initialisedData(new_width, new_height);
271         dimension * it = new_data;
272
273         // The image is stored in memory from upper-left to lower-right,
274         // so we loop from yt to yb.
275         dimension const * old_data = image_.data();
276         dimension const * start_row = old_data +
277                 image_.width() * (image_.height() - params.bb.yt);
278
279         // the Bounding Box dimensions are never less than zero, so we can use
280         // "unsigned int row" here
281         for (dimension row = params.bb.yb; row < params.bb.yt; ++row) {
282                 dimension const * begin = start_row + params.bb.xl;
283                 dimension const * end   = start_row + params.bb.xr;
284                 it = std::copy(begin, end, it);
285                 start_row += image_.width();
286         }
287
288         image_.resetData(new_width, new_height, new_data);
289 }
290
291
292 void ImageXPM::rotate(Params const & params)
293 {
294         if (image_.empty())
295                 return ;
296
297         if (!params.angle)
298                 // No rotation is necessary.
299                 return;
300
301         // Ascertain the bounding box of the rotated image
302         // Rotate about the bottom-left corner
303         static double const pi = 3.14159265358979323846;
304         // The minus sign is needed to rotate in the same sense as xdvi et al.
305         double const angle = -double(params.angle) * pi / 180.0;
306         double const cos_a = cos(angle);
307         double const sin_a = sin(angle);
308
309         // (0, 0)
310         double max_x = 0; double min_x = 0;
311         double max_y = 0; double min_y = 0;
312
313         // (old_xpm->width, 0)
314         double x_rot = cos_a * image_.width();
315         double y_rot = sin_a * image_.width();
316         max_x = std::max(max_x, x_rot); min_x = std::min(min_x, x_rot);
317         max_y = std::max(max_y, y_rot); min_y = std::min(min_y, y_rot);
318
319         // (image_.width, image_.height)
320         x_rot = cos_a * image_.width() - sin_a * image_.height();
321         y_rot = sin_a * image_.width() + cos_a * image_.height();
322         max_x = std::max(max_x, x_rot); min_x = std::min(min_x, x_rot);
323         max_y = std::max(max_y, y_rot); min_y = std::min(min_y, y_rot);
324
325         // (0, image_.height)
326         x_rot = - sin_a * image_.height();
327         y_rot =   cos_a * image_.height();
328         max_x = std::max(max_x, x_rot); min_x = std::min(min_x, x_rot);
329         max_y = std::max(max_y, y_rot); min_y = std::min(min_y, y_rot);
330
331         typedef unsigned int dimension;
332
333         dimension const new_width  = 1 + int(max_x - min_x); // round up!
334         dimension const new_height = 1 + int(max_y - min_y);
335
336         dimension * new_data = image_.initialisedData(new_width, new_height);
337         dimension const * old_data = image_.data();
338
339         // rotate the data
340         for (dimension y_old = 0; y_old < image_.height(); ++y_old) {
341                 for (dimension x_old = 0; x_old < image_.width(); ++x_old) {
342                         double const x_pos = cos_a*x_old - sin_a*y_old - min_x;
343                         double const y_pos = sin_a*x_old + cos_a*y_old - min_y;
344
345                         // ensure that there are no rounding errors
346                         dimension x_new = (x_pos > 0) ? dimension(x_pos) : 0;
347                         dimension y_new = (y_pos > 0) ? dimension(y_pos) : 0;
348                         x_new = std::min(new_width  - 1, x_new);
349                         y_new = std::min(new_height - 1, y_new);
350
351                         size_t const id_old = x_old + image_.width() * y_old;
352                         size_t const id_new = x_new + new_width * y_new;
353
354                         new_data[id_new] = old_data[id_old];
355                 }
356         }
357
358         image_.resetData(new_width, new_height, new_data);
359 }
360
361
362 void ImageXPM::scale(Params const & params)
363 {
364         if (image_.empty())
365                 return;
366
367         typedef unsigned int dimension;
368
369         dimension new_width;
370         dimension new_height;
371         boost::tie(new_width, new_height) = getScaledDimensions(params);
372
373         if (new_width == getWidth() && new_height == getHeight())
374                 // No scaling needed
375                 return;
376
377         dimension * new_data = image_.initialisedData(new_width, new_height);
378         dimension const * old_data = image_.data();
379
380         double const x_scale = double(image_.width())  / double(new_width);
381         double const y_scale = double(image_.height()) / double(new_height);
382
383         // A very simple scaling routine.
384         // Ascertain the old pixel corresponding to the new one.
385         // There is no dithering at all here.
386         for (dimension x_new = 0; x_new < new_width; ++x_new) {
387                 dimension x_old = dimension(x_new * x_scale);
388
389                 for (dimension y_new = 0; y_new < new_height; ++y_new) {
390                         dimension y_old = dimension(y_new * y_scale);
391
392                         size_t const id_old = x_old + image_.width() * y_old;
393                         size_t const id_new = x_new + new_width * y_new;
394
395                         new_data[id_new] = old_data[id_old];
396                 }
397         }
398
399         image_.resetData(new_width, new_height, new_data);
400 }
401
402 } // namespace grfx
403
404
405 namespace {
406
407 void free_color_table(XpmColor * colorTable, size_t size);
408
409 void copy_color_table(XpmColor const * in, size_t size, XpmColor * out);
410
411 bool contains_color_none(XpmImage const & image);
412
413 string const unique_color_string(XpmImage const & image);
414
415 // libXpm cannot cope with strings of the form #rrrrggggbbbb,
416 // #rrrgggbbb or #rgb, so convert them to #rrggbb.
417 string const convertTo7chars(string const &);
418
419 // create a copy (using malloc and strcpy). If (!in) return 0;
420 char * clone_c_string(char const * in);
421
422 // Given a string of the form #ff0571 create appropriate grayscale and
423 // monochrome colors.
424 void mapcolor(char const * c_color, char ** g_color_ptr, char ** m_color_ptr);
425
426 } // namespace anon
427
428
429 namespace grfx {
430
431 ImageXPM::Data::Data()
432         : width_(0), height_(0), cpp_(0), ncolors_(0)
433 {}
434
435
436 ImageXPM::Data::~Data()
437 {
438 }
439
440
441 void ImageXPM::Data::reset(XpmImage & image)
442 {
443         width_ = image.width;
444         height_ = image.height;
445         cpp_ = image.cpp;
446
447         // Move the data ptr into this store and free up image.data
448         data_.reset(image.data, free);
449         image.data = 0;
450
451         // Don't just store the color table, but check first that it contains
452         // all that we require of it.
453         // The idea is to store the color table in a shared_ptr and for all
454         // modified images to use the same table.
455         // It must, therefore, have a c_color "none" entry and g_color and
456         // m_color entries corresponding to each and every c_color entry
457         // (except "none"!)
458
459         // 1. Create a copy of the color table.
460         // Add a c_color "none" entry to the table if it isn't already there.
461         bool const add_color = !contains_color_none(image);
462
463         if (add_color) {
464
465                 ncolors_ = 1 + image.ncolors;
466                 size_t const mem_size = sizeof(XpmColor) * ncolors_;
467                 XpmColor * table = static_cast<XpmColor *>(malloc(mem_size));
468
469                 copy_color_table(image.colorTable, image.ncolors, table);
470
471                 XpmColor & color = table[ncolors_ - 1];
472                 color.symbolic = 0;
473                 color.m_color  = 0;
474                 color.g_color  = 0;
475                 color.g4_color = 0;
476                 color.string =
477                         clone_c_string(unique_color_string(image).c_str());
478                 color.c_color = clone_c_string("none");
479
480                 free_color_table(image.colorTable, image.ncolors);
481                 colorTable_.reset(table, boost::bind(free_color_table, _1, ncolors_));
482
483         } else {
484
485                 // Just move the pointer across
486                 ncolors_ = image.ncolors;
487                 colorTable_.reset(image.colorTable,
488                                   boost::bind(free_color_table, _1, ncolors_));
489                 image.colorTable = 0;
490         }
491
492         // Clean-up the remaining entries of image.
493         image.width = 0;
494         image.height = 0;
495         image.cpp = 0;
496         image.ncolors = 0;
497
498         // 2. Ensure that the color table has g_color and m_color entries
499         XpmColor * table = colorTable_.get();
500
501         for (size_t i = 0; i < ncolors_; ++i) {
502                 XpmColor & entry = table[i];
503                 if (!entry.c_color)
504                         continue;
505
506                 // libXpm cannot cope with strings of the form #rrrrggggbbbb,
507                 // #rrrgggbbb or #rgb, so convert them to #rrggbb.
508                 string c_color = entry.c_color;
509                 if (c_color[0] == '#' && c_color.size() != 7) {
510                         c_color = convertTo7chars(c_color);
511                         free(entry.c_color);
512                         entry.c_color = clone_c_string(c_color.c_str());
513                 }
514
515                 // If the c_color is defined and the equivalent
516                 // grayscale or monochrome ones are not, then define them.
517                 mapcolor(entry.c_color, &entry.g_color, &entry.m_color);
518         }
519 }
520
521
522 XpmImage ImageXPM::Data::get() const
523 {
524         XpmImage image;
525         image.width = width_;
526         image.height = height_;
527         image.cpp = cpp_;
528         image.ncolors = ncolors_;
529         image.data = data_.get();
530         image.colorTable = colorTable_.get();
531         return image;
532 }
533
534
535 void ImageXPM::Data::resetData(int w, int h, unsigned int * d)
536 {
537         width_  = w;
538         height_ = h;
539         data_.reset(d, free);
540 }
541
542
543 unsigned int * ImageXPM::Data::initialisedData(int w, int h) const
544 {
545         size_t const data_size = w * h;
546
547         size_t const mem_size  = sizeof(unsigned int) * data_size;
548         unsigned int * ptr = static_cast<unsigned int *>(malloc(mem_size));
549
550         unsigned int none_id = color_none_id();
551         std::fill(ptr, ptr + data_size, none_id);
552
553         return ptr;
554 }
555
556
557 unsigned int ImageXPM::Data::color_none_id() const
558 {
559         XpmColor * table = colorTable_.get();
560         for (size_t i = 0; i < ncolors_; ++i) {
561                 char const * const color = table[i].c_color;
562                 if (color && ascii_lowercase(color) == "none")
563                         return static_cast<unsigned int>(i);
564         }
565         return 0;
566 }
567
568 } // namespace grfx
569
570 namespace {
571
572 // libXpm cannot cope with strings of the form #rrrrggggbbbb,
573 // #rrrgggbbb or #rgb, so convert them to #rrggbb.
574 string const convertTo7chars(string const & input)
575 {
576         string::size_type size = input.size();
577         if (size != 13 && size != 10 && size != 9 && size != 4)
578                 // Can't deal with it.
579                 return input;
580
581         if (input[0] != '#')
582                 // Can't deal with it.
583                 return input;
584
585         string format(input);
586
587         switch (size) {
588         case 13: // #rrrrggggbbbb
589                 format.erase(3, 2);
590                 format.erase(5, 2);
591                 format.erase(7, 2);
592                 break;
593         case 10: // #rrrgggbbb
594                 format.erase(3, 1);
595                 format.erase(5, 1);
596                 format.erase(7, 1);
597                 break;
598         case 9: //
599                 format.erase(7);
600                 break;
601         case 4: // #rgb
602                 format.insert(2, 1, '0');
603                 format.insert(4, 1, '0');
604                 format.append(1, '0');
605                 break;
606         }
607
608         return format;
609 }
610
611
612 // Given a string of the form #ff0571 create appropriate grayscale and
613 // monochrome colors.
614 void mapcolor(char const * c_color, char ** g_color_ptr, char ** m_color_ptr)
615 {
616         if (!c_color)
617                 return;
618
619         char * g_color = *g_color_ptr;
620         char * m_color = *m_color_ptr;
621
622         if (g_color && m_color)
623                 // Already filled.
624                 return;
625
626         Display * display = fl_get_display();
627         Colormap cmap     = fl_state[fl_get_vclass()].colormap;
628         XColor xcol;
629         XColor ccol;
630         if (XLookupColor(display, cmap, c_color, &xcol, &ccol) == 0)
631                 // Unable to parse c_color.
632                 return;
633
634         // Note that X stores the RGB values in the range 0 - 65535
635         // whilst we require them in the range 0 - 255.
636         int const r = xcol.red   / 256;
637         int const g = xcol.green / 256;
638         int const b = xcol.blue  / 256;
639
640         // This gives a good match to a human's RGB to luminance conversion.
641         // (From xv's Postscript code --- Mike Ressler.)
642         int const gray = int((0.32 * r) + (0.5 * g) + (0.18 * b));
643
644         ostringstream gray_stream;
645         gray_stream << "#" << std::setbase(16) << std::setfill('0')
646                     << std::setw(2) << gray
647                     << std::setw(2) << gray
648                     << std::setw(2) << gray;
649
650         int const mono = (gray < 128) ? 0 : 255;
651         ostringstream mono_stream;
652         mono_stream << "#" << std::setbase(16) << std::setfill('0')
653                     << std::setw(2) << mono
654                     << std::setw(2) << mono
655                     << std::setw(2) << mono;
656
657         // This string is going into an XpmImage struct, so create copies that
658         // libXPM can free successfully.
659         if (!g_color)
660                 *g_color_ptr = clone_c_string(gray_stream.str().c_str());
661         if (!m_color)
662                 *m_color_ptr = clone_c_string(mono_stream.str().c_str());
663 }
664
665
666 void copy_color_table(XpmColor const * in, size_t size, XpmColor * out)
667 {
668         for (size_t i = 0; i < size; ++i) {
669                 out[i].string   = clone_c_string(in[i].string);
670                 out[i].symbolic = clone_c_string(in[i].symbolic);
671                 out[i].m_color  = clone_c_string(in[i].m_color);
672                 out[i].g_color  = clone_c_string(in[i].g_color);
673                 out[i].g4_color = clone_c_string(in[i].g4_color);
674                 out[i].c_color  = clone_c_string(in[i].c_color);
675         }
676 }
677
678
679 void free_color_table(XpmColor * table, size_t size)
680 {
681         for (size_t i = 0; i < size; ++i) {
682                 free(table[i].string);
683                 free(table[i].symbolic);
684                 free(table[i].m_color);
685                 free(table[i].g_color);
686                 free(table[i].g4_color);
687                 free(table[i].c_color);
688         }
689         free(table);
690 }
691
692
693 char * clone_c_string(char const * in)
694 {
695         if (!in)
696                 return 0;
697
698         // Don't forget the '\0'
699         char * out = static_cast<char *>(malloc(strlen(in) + 1));
700         return strcpy(out, in);
701 }
702
703
704 bool contains_color_none(XpmImage const & image)
705 {
706         for (size_t i = 0; i < image.ncolors; ++i) {
707                 char const * const color = image.colorTable[i].c_color;
708                 if (color && ascii_lowercase(color) == "none")
709                         return true;
710         }
711         return false;
712 }
713
714
715 string const unique_color_string(XpmImage const & image)
716 {
717         string id(image.cpp, ' ');
718
719         for(;;) {
720                 bool found_it = false;
721                 for (size_t i = 0; i < image.ncolors; ++i) {
722                         string const c_id = image.colorTable[i].string;
723                         if (c_id == id) {
724                                 found_it = true;
725                                 break;
726                         }
727                 }
728
729                 if (!found_it)
730                         return id;
731
732                 // Loop over the printable characters in the ASCII table.
733                 // Ie, count from char 32 (' ') to char 126 ('~')
734                 // A base 94 counter!
735                 string::size_type current_index = id.size() - 1;
736                 bool continue_loop = true;
737                 while(continue_loop) {
738                         continue_loop = false;
739
740                         if (id[current_index] == 126) {
741                                 continue_loop = true;
742                                 if (current_index == 0)
743                                         // Unable to find a unique string
744                                         return image.colorTable[0].string;
745
746                                 id[current_index] = 32;
747                                 current_index -= 1;
748                         } else {
749                                 id[current_index] += 1;
750                                 // Note that '"' is an illegal char in this
751                                 // context
752                                 if (id[current_index] == '"')
753                                         id[current_index] += 1;
754                         }
755                 }
756                 if (continue_loop)
757                         // Unable to find a unique string
758                         return string();
759         }
760 }
761
762 } // namespace anon