]> git.lyx.org Git - lyx.git/blob - src/graphics/GraphicsImageXPM.C
Remove diagnostic message
[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 "ColorHandler.h"
19 #include "debug.h"
20 #include "frontends/GUIRunTime.h" // x11Display
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_(other.pixmap_),
58           pixmap_status_(other.pixmap_status_)
59 {}
60         
61
62 GImageXPM::~GImageXPM()
63 {
64         if (pixmap_ && pixmap_status_ == PIXMAP_SUCCESS)
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->emit(false);
99                 return;
100         }
101
102         if (!image_.empty()) {
103                 lyxerr[Debug::GRAPHICS]
104                         << "Image is loaded already!" << std::endl;
105                 on_finish->emit(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->emit(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;
192         xpm_col.name = 0;
193         xpm_col.value = "none";
194         xpm_col.pixel = lyxColorHandler->colorPixel(LColor::graphicsbg);
195
196         attrib.numsymbols = 1;
197         attrib.colorsymbols = &xpm_col;
198         attrib.valuemask |= XpmColorSymbols;
199
200         // Load up the pixmap
201         XpmImage xpm_image = image_.get();
202         int const status =
203                 XpmCreatePixmapFromXpmImage(display, 
204                                             XRootWindowOfScreen(screen), 
205                                             &xpm_image, 
206                                             &pixmap, &mask, &attrib);
207
208         XpmFreeAttributes(&attrib);
209
210         if (status != XpmSuccess) {
211                 lyxerr << "Error creating pixmap from xpm_image '" 
212                        << XpmGetErrorString(status) << "'"
213                        << std::endl;
214                 pixmap_status_ = PIXMAP_FAILED;
215                 return false;
216         }
217
218         pixmap_ = pixmap;
219         pixmap_status_ = PIXMAP_SUCCESS;
220         return true;
221 }
222
223
224 void GImageXPM::clip(GParams const & params)
225 {
226         if (image_.empty())
227                 return;
228
229         if (params.bb.empty())
230                 // No clipping is necessary.
231                 return;
232
233         unsigned int const new_width  = params.bb.xr - params.bb.xl;
234         unsigned int const new_height = params.bb.yt - params.bb.yb;
235
236         if (new_width > image_.width() || new_height > image_.height())
237                 // Bounds are invalid.
238                 return;
239
240         if (new_width  == image_.width() && new_height == image_.height())
241                 // Bounds are unchanged.
242                 return;
243
244         unsigned int * new_data = image_.initialisedData(new_width, new_height);
245         unsigned int const * old_data = image_.data();
246
247         unsigned int * it = new_data;
248         unsigned int const * start_row = old_data;
249         for (int row = params.bb.yb; row < params.bb.yt; ++row) {
250                 unsigned int const * begin = start_row + params.bb.xl;
251                 unsigned int const * end   = start_row + params.bb.xr;
252                 it = std::copy(begin, end, it);
253                 start_row += image_.width();
254         }
255
256         image_.resetData(new_width, new_height, new_data);
257 }
258
259
260 void GImageXPM::rotate(GParams const & params)
261 {
262         if (image_.empty())
263                 return ;
264
265         if (!params.angle)
266                 // No rotation is necessary.
267                 return;
268
269         // Ascertain the bounding box of the rotated image
270         // Rotate about the bottom-left corner
271         static double const pi = 3.14159265358979323846;
272         double const angle = double(params.angle) * pi / 180.0;
273         double const cos_a = cos(angle);
274         double const sin_a = sin(angle);
275
276         // (0, 0)
277         double max_x = 0; double min_x = 0;
278         double max_y = 0; double min_y = 0;
279
280         // (old_xpm->width, 0)
281         double x_rot = cos_a * image_.width();
282         double y_rot = sin_a * image_.width();
283         max_x = std::max(max_x, x_rot); min_x = std::min(min_x, x_rot);
284         max_y = std::max(max_y, y_rot); min_y = std::min(min_y, y_rot);
285
286         // (image_.width, image_.height)
287         x_rot = cos_a * image_.width() - sin_a * image_.height();
288         y_rot = sin_a * image_.width() + cos_a * image_.height();
289         max_x = std::max(max_x, x_rot); min_x = std::min(min_x, x_rot);
290         max_y = std::max(max_y, y_rot); min_y = std::min(min_y, y_rot);
291
292         // (0, image_.height)
293         x_rot = - sin_a * image_.height();
294         y_rot =   cos_a * image_.height();
295         max_x = std::max(max_x, x_rot); min_x = std::min(min_x, x_rot);
296         max_y = std::max(max_y, y_rot); min_y = std::min(min_y, y_rot);
297
298         unsigned int const new_width  = 1 + int(max_x - min_x); // round up!
299         unsigned int const new_height = 1 + int(max_y - min_y);
300
301         unsigned int * new_data = image_.initialisedData(new_width, new_height);
302         unsigned int const * old_data = image_.data();
303
304         // rotate the data
305         for (int y_old = 0; y_old < image_.height(); ++y_old) {
306                 for (int x_old = 0; x_old < image_.width(); ++x_old) {
307                         int x_new = int(cos_a * x_old - sin_a * y_old - min_x);
308                         int y_new = int(sin_a * x_old + cos_a * y_old - min_y);
309
310                         // ensure that there are no rounding errors
311                         y_new = std::min(int(new_height - 1), y_new);
312                         y_new = std::max(0, y_new);
313                         x_new = std::min(int(new_width  - 1), x_new);
314                         x_new = std::max(0, x_new);
315
316                         int const old_id = x_old + image_.width() * y_old;
317                         int const new_id = x_new + new_width * y_new;
318
319                         new_data[new_id] = old_data[old_id];
320                 }
321         }
322
323         image_.resetData(new_width, new_height, new_data);
324 }
325
326
327 void GImageXPM::scale(GParams const & params)
328 {
329         if (image_.empty())
330                 return;
331
332         // boost::tie produces horrible compilation errors on my machine
333         // Angus 25 Feb 2002
334         std::pair<unsigned int, unsigned int> d = getScaledDimensions(params);
335         unsigned int const new_width  = d.first;
336         unsigned int const new_height = d.second;
337         if (new_width == getWidth() && new_height == getHeight())
338                 // No scaling needed
339                 return;
340
341         unsigned int * new_data = image_.initialisedData(new_width, new_height);
342         unsigned int const * old_data = image_.data();
343         
344         double const x_scale = double(image_.width())  / double(new_width);
345         double const y_scale = double(image_.height()) / double(new_height);
346
347         // A very simple scaling routine.
348         // Ascertain the old pixel corresponding to the new one.
349         // There is no dithering at all here.
350         for (int x_new = 0; x_new < new_width; ++x_new) {
351                 int x_old = int(x_new * x_scale);
352                 for (int y_new = 0; y_new < new_height; ++y_new) {
353                         int y_old = int(y_new * y_scale);
354
355                         int const old_id = x_old + image_.width() * y_old;
356                         int const new_id = x_new + new_width * y_new;
357
358                         new_data[new_id] = old_data[old_id];
359                 }
360         }
361         
362         image_.resetData(new_width, new_height, new_data);
363 }
364
365 } // namespace grfx
366
367
368 namespace {
369
370 void free_color_table(XpmColor * colorTable, size_t size);
371
372 void copy_color_table(XpmColor const * in, size_t size, XpmColor * out);
373         
374 bool contains_color_none(XpmImage const & image);
375
376 string const unique_color_string(XpmImage const & image);
377  
378 // create a copy (using malloc and strcpy). If (!in) return 0; 
379 char * clone_c_string(char const * in);
380  
381 // Given a string of the form #ff0571 create appropriate grayscale and
382 // monochrome colors.
383 void mapcolor(char const * c_color, char ** g_color_ptr, char ** m_color_ptr);
384
385 } // namespace anon
386
387
388 namespace grfx {
389
390
391 GImageXPM::Data::Data()
392         : width_(0), height_(0), cpp_(0), ncolors_(0)
393 {}
394
395
396 GImageXPM::Data::~Data()
397 {
398         if (colorTable_.unique())
399                 free_color_table(colorTable_.get(), ncolors_);
400 }
401
402
403 void GImageXPM::Data::reset(XpmImage & image)
404 {
405         width_ = image.width;
406         height_ = image.height;
407         cpp_ = image.cpp;
408
409         // Move the data ptr into this store and free up image.data
410         data_.reset(image.data);
411         image.data = 0;
412
413         // Don't just store the color table, but check first that it contains
414         // all that we require of it.
415         // The idea is to store the color table in a shared_ptr and for all
416         // modified images to use the same table.
417         // It must, therefore, have a c_color "none" entry and g_color and
418         // m_color entries corresponding to each and every c_color entry
419         // (except "none"!)
420
421         // 1. Create a copy of the color table.
422         // Add a c_color "none" entry to the table if it isn't already there.
423         bool const add_color = !contains_color_none(image);
424         
425         if (add_color) {
426
427                 ncolors_ = 1 + image.ncolors;
428                 size_t const mem_size = sizeof(XpmColor) * ncolors_;
429                 XpmColor * table = static_cast<XpmColor *>(malloc(mem_size));
430
431                 copy_color_table(image.colorTable, image.ncolors, table);
432
433                 XpmColor & color = table[ncolors_ - 1];
434                 color.symbolic = 0;
435                 color.m_color  = 0;
436                 color.g_color  = 0;
437                 color.g4_color = 0;
438                 color.string =
439                         clone_c_string(unique_color_string(image).c_str());
440                 color.c_color = clone_c_string("none");
441
442                 free_color_table(image.colorTable, image.ncolors);
443                 colorTable_.reset(table);
444
445         } else {
446
447                 // Just move the pointer across
448                 ncolors_ = image.ncolors;
449                 colorTable_.reset(image.colorTable);
450                 image.colorTable = 0;
451         }
452
453         // Clean-up the remaining entries of image.
454         image.width = 0;
455         image.height = 0;
456         image.cpp = 0;
457         image.ncolors = 0;
458
459         // 2. Ensure that the color table has g_color and m_color entries
460         XpmColor * table = colorTable_.get();
461         string buggy_color;
462
463         for (size_t i = 0; i < ncolors_; ++i) {
464                 XpmColor & entry = table[i];
465                 if (!entry.c_color)
466                         continue;
467
468                 // A work-around for buggy XPM files that may be created by
469                 // ImageMagick's convert.
470                 string c_color = entry.c_color;
471                 if (c_color[0] == '#' && c_color.size() > 7) {
472                         if (buggy_color.empty())
473                                 buggy_color = c_color;
474
475                         c_color = c_color.substr(0, 7);
476                         free(entry.c_color);
477                         entry.c_color = clone_c_string(c_color.c_str());
478                 }
479
480                 // If the c_color is defined and the equivalent
481                 // grayscale or monochrome ones are not, then define them.
482                 mapcolor(entry.c_color, &entry.g_color, &entry.m_color);
483         }
484
485         if (!buggy_color.empty()) {
486                 lyxerr << "The XPM file contains silly colors, "
487                        << "an example being \""
488                        << buggy_color << "\".\n"
489                        << "This was cropped to \""
490                        << buggy_color.substr(0, 7)
491                        << "\" so you can see something!\n"
492                        << "If this file was created by ImageMagick's convert,\n"
493                        << "then upgrading may cure the problem."
494                        << std::endl;
495         }
496 }
497
498
499 XpmImage GImageXPM::Data::get() const
500 {
501         XpmImage image;
502         image.width = width_;
503         image.height = height_;
504         image.cpp = cpp_;
505         image.ncolors = ncolors_;
506         image.data = data_.get();
507         image.colorTable = colorTable_.get();
508         return image;
509 }
510
511
512 void GImageXPM::Data::resetData(int w, int h, unsigned int * d)
513 {
514         width_  = w;
515         height_ = h;
516         data_.reset(d);
517 }
518
519 unsigned int * GImageXPM::Data::initialisedData(int w, int h) const
520 {
521         size_t const data_size = w * h;
522
523         size_t const mem_size  = sizeof(unsigned int) * data_size;
524         unsigned int * ptr = static_cast<unsigned int *>(malloc(mem_size));
525
526         unsigned int none_id = color_none_id();
527         std::fill(ptr, ptr + data_size, none_id);
528
529         return ptr;
530 }
531
532
533 unsigned int GImageXPM::Data::color_none_id() const
534 {
535         XpmColor * table = colorTable_.get();
536         for (size_t i = 0; i < ncolors_; ++i) {
537                 char const * const color = table[i].c_color;
538                 if (color && lowercase(color) == "none")
539                         return uint(i);
540         }
541         return 0;
542 }
543
544 } // namespace grfx
545
546 namespace {
547
548 // Given a string of the form #ff0571 create appropriate grayscale and
549 // monochrome colors.
550 void mapcolor(char const * c_color, char ** g_color_ptr, char ** m_color_ptr)
551 {
552         if (!c_color)
553                 return;
554
555         char * g_color = *g_color_ptr;
556         char * m_color = *m_color_ptr;
557
558         if (g_color && m_color)
559                 // Already filled.
560                 return;
561         
562         Display * display = GUIRunTime::x11Display();
563         Colormap cmap     = GUIRunTime::x11Colormap();
564         XColor xcol;
565         XColor ccol;
566         if (XLookupColor(display, cmap, c_color, &xcol, &ccol) == 0)
567                 // Unable to parse c_color.
568                 return;
569
570         // Note that X stores the RGB values in the range 0 - 65535
571         // whilst we require them in the range 0 - 255.
572         int const r = xcol.red   / 256;
573         int const g = xcol.green / 256;
574         int const b = xcol.blue  / 256;
575
576         // This gives a good match to a human's RGB to luminance conversion.
577         // (From xv's Postscript code --- Mike Ressler.)
578         int const gray = int((0.32 * r) + (0.5 * g) + (0.18 * b));
579
580         ostringstream gray_stream;
581         gray_stream << "#" << std::setbase(16) << std::setfill('0')
582                     << std::setw(2) << gray
583                     << std::setw(2) << gray
584                     << std::setw(2) << gray;
585
586         int const mono = (gray < 128) ? 0 : 255;
587         ostringstream mono_stream;
588         mono_stream << "#" << std::setbase(16) << std::setfill('0')
589                     << std::setw(2) << mono
590                     << std::setw(2) << mono
591                     << std::setw(2) << mono;
592
593         // This string is going into an XpmImage struct, so create copies that
594         // libXPM can free successfully.
595         if (!g_color)
596                 *g_color_ptr = clone_c_string(gray_stream.str().c_str());
597         if (!m_color)
598                 *m_color_ptr = clone_c_string(mono_stream.str().c_str());
599 }
600
601
602 void copy_color_table(XpmColor const * in, size_t size, XpmColor * out)
603 {
604         for (size_t i = 0; i < size; ++i) {
605                 out[i].string   = clone_c_string(in[i].string);
606                 out[i].symbolic = clone_c_string(in[i].symbolic);
607                 out[i].m_color  = clone_c_string(in[i].m_color);
608                 out[i].g_color  = clone_c_string(in[i].g_color);
609                 out[i].g4_color = clone_c_string(in[i].g4_color);
610                 out[i].c_color  = clone_c_string(in[i].c_color);
611         }
612 }
613
614
615 void free_color_table(XpmColor * table, size_t size)
616 {
617         for (size_t i = 0; i < size; ++i) {
618                 free(table[i].string);
619                 free(table[i].symbolic);
620                 free(table[i].m_color);
621                 free(table[i].g_color);
622                 free(table[i].g4_color);
623                 free(table[i].c_color);
624         }
625         // Don't free the table itself. Let the shared_c_ptr do that.
626         // free(table);
627 }
628
629
630 char * clone_c_string(char const * in)
631 {
632         if (!in)
633                 return 0;
634
635         // Don't forget the '\0'
636         char * out = static_cast<char *>(malloc(strlen(in) + 1));
637         return strcpy(out, in);
638 }
639
640
641 bool contains_color_none(XpmImage const & image)
642 {
643         for (size_t i = 0; i < image.ncolors; ++i) {
644                 char const * const color = image.colorTable[i].c_color;
645                 if (color && lowercase(color) == "none")
646                         return true;
647         }
648         return false;
649 }
650
651
652 string const unique_color_string(XpmImage const & image)
653 {
654         string id(image.cpp, ' ');
655
656         for(;;) {
657                 bool found_it = false;
658                 for (size_t i = 0; i < image.ncolors; ++i) {
659                         string const c_id = image.colorTable[i].string;
660                         if (c_id == id) {
661                                 found_it = true;
662                                 break;
663                         }
664                 }
665
666                 if (!found_it)
667                         return id;
668
669                 // Loop over the printable characters in the ASCII table.
670                 // Ie, count from char 32 (' ') to char 126 ('~')
671                 // A base 94 counter!
672                 string::size_type current_index = id.size() - 1;
673                 bool continue_loop = true;
674                 while(continue_loop) {
675                         continue_loop = false;
676
677                         
678                         if (id[current_index] == 126) {
679                                 continue_loop = true;
680                                 if (current_index == 0)
681                                         // Unable to find a unique string
682                                         return image.colorTable[0].string;
683
684                                 id[current_index] = 32;
685                                 current_index -= 1;
686                         } else {
687                                 id[current_index] += 1;
688                                 // Note that '"' is an illegal char in this
689                                 // context
690                                 if (id[current_index] == '"')
691                                         id[current_index] += 1;
692                         }
693                 }
694                 if (continue_loop)
695                         // Unable to find a unique string
696                         return string();
697         }
698 }
699
700 } // namespace anon