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