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