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