]> git.lyx.org Git - lyx.git/blob - src/graphics/GraphicsImageXPM.C
* GraphicsImageXPM.[Ch]: more rigorous use of types (signed/unsigned).
[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_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         if (pixmap_status_ == PIXMAP_FAILED) {
156                 return false;
157         }
158
159         if (pixmap_status_ == PIXMAP_SUCCESS) {
160                 return true;
161         }
162
163         using namespace grfx;
164         Display * display = GUIRunTime::x11Display();
165
166         //(BE 2000-08-05)
167         // This might be a dirty thing, but I dont know any other solution.
168         Screen * screen = ScreenOfDisplay(display, GUIRunTime::x11Screen());
169
170         Pixmap pixmap;
171         Pixmap mask;
172
173         XpmAttributes attrib;
174
175         // Allow libXPM lots of leeway when trying to allocate colors.
176         attrib.closeness = 10000;
177         attrib.valuemask = XpmCloseness;
178
179         // The XPM file format allows multiple pixel colours to be defined
180         // as c_color, g_color or m_color.
181         switch (params.display) {
182         case GParams::MONOCHROME:
183                 attrib.color_key = XPM_MONO;
184                 break;
185         case GParams::GRAYSCALE:
186                 attrib.color_key = XPM_GRAY;
187                 break;
188         case GParams::COLOR:
189         default: // NONE cannot happen!
190                 attrib.color_key = XPM_COLOR;
191                 break;
192         }
193
194         attrib.valuemask |= XpmColorKey;
195
196         // Set the color "none" entry to the color of the background.
197         XpmColorSymbol xpm_col;
198         xpm_col.name = 0;
199         xpm_col.value = "none";
200         xpm_col.pixel = lyxColorHandler->colorPixel(LColor::graphicsbg);
201
202         attrib.numsymbols = 1;
203         attrib.colorsymbols = &xpm_col;
204         attrib.valuemask |= XpmColorSymbols;
205
206         // Load up the pixmap
207         XpmImage xpm_image = image_.get();
208         int const status =
209                 XpmCreatePixmapFromXpmImage(display, 
210                                             XRootWindowOfScreen(screen), 
211                                             &xpm_image, 
212                                             &pixmap, &mask, &attrib);
213
214         XpmFreeAttributes(&attrib);
215
216         if (status != XpmSuccess) {
217                 lyxerr << "Error creating pixmap from xpm_image '" 
218                        << XpmGetErrorString(status) << "'"
219                        << std::endl;
220                 pixmap_status_ = PIXMAP_FAILED;
221                 return false;
222         }
223
224         pixmap_ = pixmap;
225         pixmap_status_ = PIXMAP_SUCCESS;
226         return true;
227 }
228
229
230 void GImageXPM::clip(GParams const & params)
231 {
232         if (image_.empty())
233                 return;
234
235         if (params.bb.empty())
236                 // No clipping is necessary.
237                 return;
238
239         unsigned int const new_width  = params.bb.xr - params.bb.xl;
240         unsigned int const new_height = params.bb.yt - params.bb.yb;
241
242         if (new_width > image_.width() || new_height > image_.height())
243                 // Bounds are invalid.
244                 return;
245
246         if (new_width  == image_.width() && new_height == image_.height())
247                 // Bounds are unchanged.
248                 return;
249
250         unsigned int * new_data = image_.initialisedData(new_width, new_height);
251         unsigned int const * old_data = image_.data();
252
253         unsigned int * it = new_data;
254         unsigned int const * start_row = old_data;
255         for (int row = params.bb.yb; row < params.bb.yt; ++row) {
256                 unsigned int const * begin = start_row + params.bb.xl;
257                 unsigned int const * end   = start_row + params.bb.xr;
258                 it = std::copy(begin, end, it);
259                 start_row += image_.width();
260         }
261
262         image_.resetData(new_width, new_height, new_data);
263 }
264
265
266 void GImageXPM::rotate(GParams const & params)
267 {
268         if (image_.empty())
269                 return ;
270
271         if (!params.angle)
272                 // No rotation is necessary.
273                 return;
274
275         // Ascertain the bounding box of the rotated image
276         // Rotate about the bottom-left corner
277         static double const pi = 3.14159265358979323846;
278         double const angle = double(params.angle) * pi / 180.0;
279         double const cos_a = cos(angle);
280         double const sin_a = sin(angle);
281
282         // (0, 0)
283         double max_x = 0; double min_x = 0;
284         double max_y = 0; double min_y = 0;
285
286         // (old_xpm->width, 0)
287         double x_rot = cos_a * image_.width();
288         double y_rot = sin_a * image_.width();
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         // (image_.width, image_.height)
293         x_rot = cos_a * image_.width() - sin_a * image_.height();
294         y_rot = sin_a * image_.width() + 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         // (0, image_.height)
299         x_rot = - sin_a * image_.height();
300         y_rot =   cos_a * image_.height();
301         max_x = std::max(max_x, x_rot); min_x = std::min(min_x, x_rot);
302         max_y = std::max(max_y, y_rot); min_y = std::min(min_y, y_rot);
303
304         unsigned int const new_width  = 1 + int(max_x - min_x); // round up!
305         unsigned int const new_height = 1 + int(max_y - min_y);
306
307         unsigned int * new_data = image_.initialisedData(new_width, new_height);
308         unsigned int const * old_data = image_.data();
309
310         // rotate the data
311         for (int y_old = 0; y_old < image_.height(); ++y_old) {
312                 for (int x_old = 0; x_old < image_.width(); ++x_old) {
313                         int x_new = int(cos_a * x_old - sin_a * y_old - min_x);
314                         int y_new = int(sin_a * x_old + cos_a * y_old - min_y);
315
316                         // ensure that there are no rounding errors
317                         y_new = std::min(int(new_height - 1), y_new);
318                         y_new = std::max(0, y_new);
319                         x_new = std::min(int(new_width  - 1), x_new);
320                         x_new = std::max(0, x_new);
321
322                         int const old_id = x_old + image_.width() * y_old;
323                         int const new_id = x_new + new_width * y_new;
324
325                         new_data[new_id] = old_data[old_id];
326                 }
327         }
328
329         image_.resetData(new_width, new_height, new_data);
330 }
331
332
333 void GImageXPM::scale(GParams const & params)
334 {
335         if (image_.empty())
336                 return;
337
338         // boost::tie produces horrible compilation errors on my machine
339         // Angus 25 Feb 2002
340         std::pair<unsigned int, unsigned int> d = getScaledDimensions(params);
341         unsigned int const new_width  = d.first;
342         unsigned int const new_height = d.second;
343         if (new_width == getWidth() && new_height == getHeight())
344                 // No scaling needed
345                 return;
346
347         unsigned int * new_data = image_.initialisedData(new_width, new_height);
348         unsigned int const * old_data = image_.data();
349         
350         double const x_scale = double(image_.width())  / double(new_width);
351         double const y_scale = double(image_.height()) / double(new_height);
352
353         // A very simple scaling routine.
354         // Ascertain the old pixel corresponding to the new one.
355         // There is no dithering at all here.
356         for (int x_new = 0; x_new < new_width; ++x_new) {
357                 int x_old = int(x_new * x_scale);
358                 for (int y_new = 0; y_new < new_height; ++y_new) {
359                         int y_old = int(y_new * y_scale);
360
361                         int const old_id = x_old + image_.width() * y_old;
362                         int const new_id = x_new + new_width * y_new;
363
364                         new_data[new_id] = old_data[old_id];
365                 }
366         }
367         
368         image_.resetData(new_width, new_height, new_data);
369 }
370
371 } // namespace grfx
372
373
374 namespace {
375
376 void free_color_table(XpmColor * colorTable, size_t size);
377
378 void copy_color_table(XpmColor const * in, size_t size, XpmColor * out);
379         
380 bool contains_color_none(XpmImage const & image);
381
382 string const unique_color_string(XpmImage const & image);
383  
384 // create a copy (using malloc and strcpy). If (!in) return 0; 
385 char * clone_c_string(char const * in);
386  
387 // Given a string of the form #ff0571 create a string for the appropriate
388 // grayscale or monochrome color.
389 char * mapcolor(char * color, bool toGray);
390
391 } // namespace anon
392
393
394 namespace grfx {
395
396
397 GImageXPM::Data::Data()
398         : width_(0), height_(0), cpp_(0), ncolors_(0)
399 {}
400
401
402 GImageXPM::Data::~Data()
403 {
404         // Introduce temporary memory leak to fix crash.
405 //      if (colorTable_.unique())
406 //              free_color_table(colorTable_.get(), ncolors_);
407 }
408
409
410 void GImageXPM::Data::reset(XpmImage & image)
411 {
412         width_ = image.width;
413         height_ = image.height;
414         cpp_ = image.cpp;
415
416         // Move the data ptr into this store and free up image.data
417         data_.reset(image.data);
418         image.data = 0;
419
420         // Don't just store the color table, but check first that it contains
421         // all that we require of it.
422         // The idea is to store the color table in a shared_ptr and for all
423         // modified images to use the same table.
424         // It must, therefore, have a c_color "none" entry and g_color and
425         // m_color entries corresponding to each and every c_color entry
426         // (except "none"!)
427
428         // 1. Create a copy of the color table.
429         // Add a c_color "none" entry to the table if it isn't already there.
430         bool const add_color = !contains_color_none(image);
431         
432         if (add_color) {
433
434                 ncolors_ = 1 + image.ncolors;
435                 size_t const mem_size = sizeof(XpmColor) * ncolors_;
436                 XpmColor * table = static_cast<XpmColor *>(malloc(mem_size));
437
438                 copy_color_table(image.colorTable, image.ncolors, table);
439
440                 XpmColor & color = table[ncolors_ - 1];
441                 color.symbolic = 0;
442                 color.m_color  = 0;
443                 color.g_color  = 0;
444                 color.g4_color = 0;
445                 color.string =
446                         clone_c_string(unique_color_string(image).c_str());
447                 color.c_color = clone_c_string("none");
448
449                 free_color_table(image.colorTable, image.ncolors);
450                 colorTable_.reset(table);
451
452         } else {
453
454                 // Just move the pointer across
455                 ncolors_ = image.ncolors;
456                 colorTable_.reset(image.colorTable);
457                 image.colorTable = 0;
458         }
459
460         // Clean-up the remaining entries of image.
461         image.width = 0;
462         image.height = 0;
463         image.cpp = 0;
464         image.ncolors = 0;
465
466         // 2. Ensure that the color table has g_color and m_color entries
467         XpmColor * table = colorTable_.get();
468
469         for (size_t i = 0; i < ncolors_; ++i) {
470                 // If the c_color is defined and the equivalent
471                 // grayscale one is not, then define it.
472                 if (table[i].c_color && !table[i].g_color)
473                         table[i].g_color = mapcolor(table[i].c_color, true);
474
475                 // If the c_color is defined and the equivalent
476                 // monochrome one is not, then define it.
477                 if (table[i].c_color && !table[i].m_color)
478                         table[i].m_color = mapcolor(table[i].c_color, false);
479         }
480 }
481
482
483 XpmImage GImageXPM::Data::get() const
484 {
485         XpmImage image;
486         image.width = width_;
487         image.height = height_;
488         image.cpp = cpp_;
489         image.ncolors = ncolors_;
490         image.data = data_.get();
491         image.colorTable = colorTable_.get();
492         return image;
493 }
494
495
496 void GImageXPM::Data::resetData(int w, int h, unsigned int * d)
497 {
498         width_  = w;
499         height_ = h;
500         data_.reset(d);
501 }
502
503 unsigned int * GImageXPM::Data::initialisedData(int w, int h) const
504 {
505         size_t const data_size = w * h;
506
507         size_t const mem_size  = sizeof(unsigned int) * data_size;
508         unsigned int * ptr = static_cast<unsigned int *>(malloc(mem_size));
509
510         unsigned int none_id = color_none_id();
511         std::fill(ptr, ptr + data_size, none_id);
512
513         return ptr;
514 }
515
516
517 unsigned int GImageXPM::Data::color_none_id() const
518 {
519         XpmColor * table = colorTable_.get();
520         for (size_t i = 0; i < ncolors_; ++i) {
521                 char const * const color = table[i].c_color;
522                 if (color && lowercase(color) == "none")
523                         return uint(i);
524         }
525         return 0;
526 }
527
528 } // namespace grfx
529
530 namespace {
531
532 // Given a string of the form #ff0571 create a string for the appropriate
533 // grayscale or monochrome color.
534 char * mapcolor(char * color, bool toGray)
535 {
536         if (!color)
537                 return 0;
538         
539         Display * display = GUIRunTime::x11Display();
540         Colormap cmap     = GUIRunTime::x11Colormap();
541         XColor xcol;
542         XColor ccol;
543         if (XLookupColor(display, cmap, color, &xcol, &ccol) == 0)
544                 return 0;
545
546         // Note that X stores the RGB values in the range 0 - 65535
547         // whilst we require them in the range 0 - 255.
548         int const r = xcol.red   / 256;
549         int const g = xcol.green / 256;
550         int const b = xcol.blue  / 256;
551
552         // This gives a good match to a human's RGB to luminance conversion.
553         // (From xv's Postscript code --- Mike Ressler.)
554         int mapped_color = int((0.32 * r) + (0.5 * g) + (0.18 * b));
555         if (!toGray) // monochrome
556                 mapped_color = (mapped_color < 128) ? 0 : 255;
557
558         ostringstream ostr;
559
560         ostr << "#" << std::setbase(16) << std::setfill('0')
561              << std::setw(2) << mapped_color
562              << std::setw(2) << mapped_color
563              << std::setw(2) << mapped_color;
564
565         // This string is going into an XpmImage struct, so create a copy that
566         // libXPM can free successfully.
567         return clone_c_string(ostr.str().c_str());
568 }
569
570
571 void copy_color_table(XpmColor const * in, size_t size, XpmColor * out)
572 {
573         for (size_t i = 0; i < size; ++i) {
574                 out[i].string   = clone_c_string(in[i].string);
575                 out[i].symbolic = clone_c_string(in[i].symbolic);
576                 out[i].m_color  = clone_c_string(in[i].m_color);
577                 out[i].g_color  = clone_c_string(in[i].g_color);
578                 out[i].g4_color = clone_c_string(in[i].g4_color);
579                 out[i].c_color  = clone_c_string(in[i].c_color);
580         }
581 }
582
583
584 void free_color_table(XpmColor * table, size_t size)
585 {
586         for (size_t i = 0; i < size; ++i) {
587                 free(table[i].string);
588                 free(table[i].symbolic);
589                 free(table[i].m_color);
590                 free(table[i].g_color);
591                 free(table[i].g4_color);
592                 free(table[i].c_color);
593         }
594         free(table);
595 }
596
597
598 char * clone_c_string(char const * in)
599 {
600         if (!in)
601                 return 0;
602
603         // Don't forget the '\0'
604         char * out = static_cast<char *>(malloc(strlen(in) + 1));
605         return strcpy(out, in);
606 }
607
608
609 bool contains_color_none(XpmImage const & image)
610 {
611         for (size_t i = 0; i < image.ncolors; ++i) {
612                 char const * const color = image.colorTable[i].c_color;
613                 if (color && lowercase(color) == "none")
614                         return true;
615         }
616         return false;
617 }
618
619
620 string const unique_color_string(XpmImage const & image)
621 {
622         string id(image.cpp, 'A');
623
624         for(;;) {
625                 bool found_it = false;
626                 for (size_t i = 0; i < image.ncolors; ++i) {
627                         string const c_id = image.colorTable[i].string;
628                         if (c_id == id) {
629                                 found_it = true;
630                                 break;
631                         }
632                 }
633
634                 if (!found_it)
635                         return id;
636
637                 // A base 57 counter!
638                 // eg AAAz+1 = AABA, AABz+1 = AACA, AAzz+1 = ABAA
639                 string::size_type current_index = id.size() - 1;
640                 bool continue_loop = true;
641                 while(continue_loop) {
642                         continue_loop = false;
643                         
644                         if (id[current_index] == 'z') {
645                                 continue_loop = true;
646                                 if (current_index == 0) // failed!
647                                         break;
648
649                                 id[current_index] = 'A';
650                                 current_index -= 1;
651                         } else {
652                                 id[current_index] += 1;
653                         }
654                 }
655                 if (continue_loop)
656                         // Unable to find a unique string
657                         return string();
658         }
659 }
660
661 } // namespace anon