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