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