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